1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-06 21:48:12 -05:00
This commit is contained in:
Jonas Hendrickx 2025-03-11 15:07:41 +01:00
parent 989f64d449
commit c03190d672
5 changed files with 161 additions and 70 deletions

View File

@ -1,95 +1,48 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Extensions;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
public class CreateSponsorshipCommand(
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService,
ICurrentContext currentContext)
: ICreateSponsorshipCommand
public class CreateSponsorshipCommand : ICreateSponsorshipCommand
{
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository;
private readonly BaseCreateSponsorshipHandler _createSponsorshipHandler;
public CreateSponsorshipCommand(
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService,
ICurrentContext currentContext)
{
_organizationSponsorshipRepository = organizationSponsorshipRepository;
var adminInitiatedSponsorshipHandler = new CreateAdminInitiatedSponsorshipHandler(currentContext);
_createSponsorshipHandler = new CreateSponsorshipHandler(userService, organizationSponsorshipRepository);
_createSponsorshipHandler.SetNext(adminInitiatedSponsorshipHandler);
}
public async Task<OrganizationSponsorship> CreateSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName)
{
var sponsoringUser = await userService.GetUserByIdAsync(sponsoringOrgUser.UserId.Value);
if (sponsoringUser == null || string.Equals(sponsoringUser.Email, sponsoredEmail, System.StringComparison.InvariantCultureIgnoreCase))
{
throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.");
}
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductTierType;
var sponsoringOrgProductTier = sponsoringOrg.PlanType.GetProductTier();
if (requiredSponsoringProductType == null ||
sponsoringOrgProductTier != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}
if (sponsoringOrgUser == null || sponsoringOrgUser.Status != OrganizationUserStatusType.Confirmed)
{
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
}
var isAdminInitiated = false;
if (currentContext.UserId != sponsoringOrgUser.UserId)
{
var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrg.Id);
OrganizationUserType[] allowedUserTypes =
[
OrganizationUserType.Admin,
OrganizationUserType.Owner,
OrganizationUserType.Custom
];
if (!organization.Permissions.ManageUsers || allowedUserTypes.All(x => x != organization.Type))
{
throw new UnauthorizedAccessException("You do not have permissions to send sponsorships on behalf of the organization.");
}
isAdminInitiated = true;
}
var existingOrgSponsorship = await organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(sponsoringOrgUser.Id);
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
{
throw new BadRequestException("Can only sponsor one organization per Organization User.");
}
var sponsorship = new OrganizationSponsorship
{
SponsoringOrganizationId = sponsoringOrg.Id,
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
FriendlyName = friendlyName,
OfferedToEmail = sponsoredEmail,
PlanSponsorshipType = sponsorshipType,
IsAdminInitiated = isAdminInitiated
};
if (existingOrgSponsorship != null)
{
// Replace existing invalid offer with our new sponsorship offer
sponsorship.Id = existingOrgSponsorship.Id;
}
var createSponsorshipRequest = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser, sponsorshipType, sponsoredEmail, friendlyName);
var sponsorship = await _createSponsorshipHandler.HandleAsync(createSponsorshipRequest);
try
{
await organizationSponsorshipRepository.UpsertAsync(sponsorship);
await _organizationSponsorshipRepository.UpsertAsync(sponsorship);
return sponsorship;
}
catch
{
if (sponsorship.Id != default)
if (sponsorship.Id != Guid.Empty)
{
await organizationSponsorshipRepository.DeleteAsync(sponsorship);
await _organizationSponsorshipRepository.DeleteAsync(sponsorship);
}
throw;
}

View File

@ -0,0 +1,23 @@
using Bit.Core.Entities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
public abstract class BaseCreateSponsorshipHandler
{
private BaseCreateSponsorshipHandler _next;
public BaseCreateSponsorshipHandler SetNext(BaseCreateSponsorshipHandler next)
{
_next = next;
return next;
}
public virtual async Task<OrganizationSponsorship> HandleAsync(CreateSponsorshipRequest request)
{
if (_next != null)
{
return await _next.HandleAsync(request);
}
return null;
}
}

View File

@ -0,0 +1,42 @@
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
public class CreateAdminInitiatedSponsorshipHandler(
ICurrentContext currentContext) : BaseCreateSponsorshipHandler
{
public override async Task<OrganizationSponsorship> HandleAsync(CreateSponsorshipRequest request)
{
var isAdminInitiated = false;
if (currentContext.UserId != request.SponsoringMember.UserId)
{
var organization = currentContext.Organizations.First(x => x.Id == request.SponsoringOrganization.Id);
OrganizationUserType[] allowedUserTypes =
[
OrganizationUserType.Admin,
OrganizationUserType.Owner,
OrganizationUserType.Custom
];
if (!organization.Permissions.ManageUsers || allowedUserTypes.All(x => x != organization.Type))
{
throw new UnauthorizedAccessException("You do not have permissions to send sponsorships on behalf of the organization.");
}
if (!request.SponsoringOrganization.UseAdminSponsoredFamilies)
{
throw new BadRequestException("Sponsoring organization cannot sponsor other Family organizations.");
}
isAdminInitiated = true;
}
var sponsorship = await base.HandleAsync(request) ?? new OrganizationSponsorship();
sponsorship.IsAdminInitiated = isAdminInitiated;
return sponsorship;
}
}

View File

@ -0,0 +1,61 @@
using Bit.Core.Billing.Extensions;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
public class CreateSponsorshipHandler(
IUserService userService,
IOrganizationSponsorshipRepository organizationSponsorshipRepository) : BaseCreateSponsorshipHandler
{
public override async Task<OrganizationSponsorship> HandleAsync(CreateSponsorshipRequest request)
{
var sponsoringUser = await userService.GetUserByIdAsync(request.SponsoringMember.UserId.Value);
if (sponsoringUser == null || string.Equals(sponsoringUser.Email, request.SponsoredEmail, System.StringComparison.InvariantCultureIgnoreCase))
{
throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.");
}
var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(request.SponsorshipType)?.SponsoringProductTierType;
var sponsoringOrgProductTier = request.SponsoringOrganization.PlanType.GetProductTier();
if (requiredSponsoringProductType == null ||
sponsoringOrgProductTier != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}
if (request.SponsoringMember == null || request.SponsoringMember.Status != OrganizationUserStatusType.Confirmed)
{
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
}
var existingOrgSponsorship = await organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(request.SponsoringMember.Id);
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
{
throw new BadRequestException("Can only sponsor one organization per Organization User.");
}
var sponsorship = await base.HandleAsync(request) ?? new OrganizationSponsorship();
sponsorship.SponsoringOrganizationId = request.SponsoringOrganization.Id;
sponsorship.SponsoringOrganizationUserId = request.SponsoringMember.Id;
sponsorship.FriendlyName = request.FriendlyName;
sponsorship.OfferedToEmail = request.SponsoredEmail;
sponsorship.PlanSponsorshipType = request.SponsorshipType;
if (existingOrgSponsorship != null)
{
// Replace existing invalid offer with our new sponsorship offer
sponsorship.Id = existingOrgSponsorship.Id;
}
return sponsorship;
}
}

View File

@ -0,0 +1,12 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Entities;
using Bit.Core.Enums;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
public record CreateSponsorshipRequest(
Organization SponsoringOrganization,
OrganizationUser SponsoringMember,
PlanSponsorshipType SponsorshipType,
string SponsoredEmail,
string FriendlyName);