1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-05 05:00:19 -05:00

Remove design patterns

This commit is contained in:
Jonas Hendrickx 2025-03-26 16:30:45 +01:00
parent 6c797567f5
commit a010c090a9
No known key found for this signature in database
GPG Key ID: C4B27F601CE4317D
8 changed files with 136 additions and 572 deletions

View File

@ -1,49 +1,108 @@
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Extensions;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces; using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.Interfaces;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise; namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise;
public class CreateSponsorshipCommand : ICreateSponsorshipCommand public class CreateSponsorshipCommand(
ICurrentContext currentContext,
IFeatureService featureService,
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService) : ICreateSponsorshipCommand
{ {
private readonly IOrganizationSponsorshipRepository _organizationSponsorshipRepository; public async Task<OrganizationSponsorship> CreateSponsorshipAsync(Organization sponsoringOrganization,
OrganizationUser sponsoringMember, PlanSponsorshipType sponsorshipType, string sponsoredEmail,
private readonly BaseCreateSponsorshipHandler _createSponsorshipHandler; string friendlyName, string notes)
public CreateSponsorshipCommand(
IFeatureService featureService,
IOrganizationSponsorshipRepository organizationSponsorshipRepository,
IUserService userService,
ICurrentContext currentContext)
{ {
_organizationSponsorshipRepository = organizationSponsorshipRepository; var sponsoringUser = await userService.GetUserByIdAsync(sponsoringMember.UserId!.Value);
var adminInitiatedSponsorshipHandler = new CreateAdminInitiatedSponsorshipHandler(currentContext, featureService); if (sponsoringUser == null || string.Equals(sponsoringUser.Email, sponsoredEmail, StringComparison.InvariantCultureIgnoreCase))
_createSponsorshipHandler = new CreateSponsorshipHandler(userService, organizationSponsorshipRepository); {
_createSponsorshipHandler.SetNext(adminInitiatedSponsorshipHandler); throw new BadRequestException("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.");
} }
public async Task<OrganizationSponsorship> CreateSponsorshipAsync(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, var requiredSponsoringProductType = StaticStore.GetSponsoredPlan(sponsorshipType)?.SponsoringProductTierType;
PlanSponsorshipType sponsorshipType, string sponsoredEmail, string friendlyName, string notes) var sponsoringOrgProductTier = sponsoringOrganization.PlanType.GetProductTier();
{
var createSponsorshipRequest = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser, sponsorshipType, sponsoredEmail, friendlyName, notes); if (requiredSponsoringProductType == null ||
var sponsorship = await _createSponsorshipHandler.HandleAsync(createSponsorshipRequest); sponsoringOrgProductTier != requiredSponsoringProductType.Value)
{
throw new BadRequestException("Specified Organization cannot sponsor other organizations.");
}
if (sponsoringMember.Status != OrganizationUserStatusType.Confirmed)
{
throw new BadRequestException("Only confirmed users can sponsor other organizations.");
}
var existingOrgSponsorship = await organizationSponsorshipRepository
.GetBySponsoringOrganizationUserIdAsync(sponsoringMember.Id);
if (existingOrgSponsorship?.SponsoredOrganizationId != null)
{
throw new BadRequestException("Can only sponsor one organization per Organization User.");
}
var sponsorship = new OrganizationSponsorship();
sponsorship.SponsoringOrganizationId = sponsoringOrganization.Id;
sponsorship.SponsoringOrganizationUserId = sponsoringMember.Id;
sponsorship.FriendlyName = friendlyName;
sponsorship.OfferedToEmail = sponsoredEmail;
sponsorship.PlanSponsorshipType = sponsorshipType;
if (existingOrgSponsorship != null)
{
// Replace existing invalid offer with our new sponsorship offer
sponsorship.Id = existingOrgSponsorship.Id;
}
var isAdminInitiated = false;
if (currentContext.UserId != sponsoringMember.UserId)
{
if (!featureService.IsEnabled(FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
{
throw new BadRequestException("Feature 'pm-17772-admin-initiated-sponsorships' is not enabled.");
}
var organization = currentContext.Organizations.First(x => x.Id == sponsoringOrganization.Id);
OrganizationUserType[] allowedUserTypes =
[
OrganizationUserType.Admin,
OrganizationUserType.Owner
];
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 (!sponsoringOrganization.UseAdminSponsoredFamilies)
{
throw new BadRequestException("Sponsoring organization cannot sponsor other Family organizations.");
}
isAdminInitiated = true;
}
sponsorship.IsAdminInitiated = isAdminInitiated;
sponsorship.Notes = notes;
try try
{ {
await _organizationSponsorshipRepository.UpsertAsync(sponsorship); await organizationSponsorshipRepository.UpsertAsync(sponsorship);
return sponsorship; return sponsorship;
} }
catch catch
{ {
if (sponsorship.Id != Guid.Empty) if (sponsorship.Id != Guid.Empty)
{ {
await _organizationSponsorshipRepository.DeleteAsync(sponsorship); await organizationSponsorshipRepository.DeleteAsync(sponsorship);
} }
throw; throw;
} }

View File

@ -1,23 +0,0 @@
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

@ -1,54 +0,0 @@
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Services;
namespace Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
/// <summary>
/// Responsible for validating a request and building the <see cref="OrganizationSponsorship" /> entity to create a
/// sponsorship initiated by organization members with specific permissions to manage members/users.
/// </summary>
public class CreateAdminInitiatedSponsorshipHandler(
ICurrentContext currentContext,
IFeatureService featureService) : BaseCreateSponsorshipHandler
{
public override async Task<OrganizationSponsorship> HandleAsync(CreateSponsorshipRequest request)
{
var isAdminInitiated = false;
if (currentContext.UserId != request.SponsoringMember.UserId)
{
if (!featureService.IsEnabled(FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
{
throw new BadRequestException("Feature 'pm-17772-admin-initiated-sponsorships' is not enabled.");
}
var organization = currentContext.Organizations.First(x => x.Id == request.SponsoringOrganization.Id);
OrganizationUserType[] allowedUserTypes =
[
OrganizationUserType.Admin,
OrganizationUserType.Owner
];
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;
sponsorship.Notes = request.Notes;
return sponsorship;
}
}

View File

@ -1,65 +0,0 @@
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;
/// <summary>
/// Responsible for validating a request and building the <see cref="OrganizationSponsorship" /> entity to create a
/// sponsorship.
/// </summary>
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

@ -1,13 +0,0 @@
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,
string Notes);

View File

@ -38,28 +38,28 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipCommand> sutProvider) public async Task CreateSponsorship_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipCommand> sutProvider)
{ {
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).ReturnsNull(); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).ReturnsNull();
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null)); sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message); Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default); .CreateAsync(null!);
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider) public async Task CreateSponsorship_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipCommand> sutProvider)
{ {
user.Email = sponsoredEmail; user.Email = sponsoredEmail;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default, null)); sutProvider.Sut.CreateSponsorshipAsync(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, default, null));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message); Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default); .CreateAsync(null!);
} }
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))] [Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
@ -69,14 +69,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = sponsoringOrgPlan; org.PlanType = sponsoringOrgPlan;
orgUser.Status = OrganizationUserStatusType.Confirmed; orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null)); sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message); Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default); .CreateAsync(null!);
} }
[Theory] [Theory]
@ -88,14 +88,14 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually; org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType; orgUser.Status = statusType;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null)); sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, default, default, null));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message); Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default); .CreateAsync(null!);
} }
[Theory] [Theory]
@ -108,18 +108,48 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
org.PlanType = PlanType.EnterpriseAnnually; org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed; orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>() sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship); .GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(orgUser.UserId.Value); sutProvider.GetDependency<ICurrentContext>().UserId.Returns(orgUser.UserId.Value);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType.Value, default, default, null)); sutProvider.Sut.CreateSponsorshipAsync(org, orgUser, sponsorship.PlanSponsorshipType!.Value, null, null, null));
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message); Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs() await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(default); .CreateAsync(null!);
}
public static readonly OrganizationUserStatusType[] UnconfirmedOrganizationUsersStatuses = Enum
.GetValues<OrganizationUserStatusType>()
.Where(x => x != OrganizationUserStatusType.Confirmed)
.ToArray();
[Theory]
[BitMemberAutoData(nameof(UnconfirmedOrganizationUsersStatuses))]
public async Task CreateSponsorship_ThrowsBadRequestException_WhenMemberDoesNotHaveConfirmedStatusInOrganization(
OrganizationUserStatusType status, Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, Guid sponsorshipId,
SutProvider<CreateSponsorshipCommand> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = status;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{
var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId;
});
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(sponsoringOrgUser.UserId.Value);
var actual = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null));
Assert.Equal("Only confirmed users can sponsor other organizations.", actual.Message);
} }
[Theory] [Theory]
@ -130,8 +160,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually; sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed; sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
var sponsorship = callInfo.Arg<OrganizationSponsorship>(); var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId; sponsorship.Id = sponsorshipId;
@ -168,8 +198,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
var expectedException = new Exception(); var expectedException = new Exception();
OrganizationSponsorship createdSponsorship = null; OrganizationSponsorship createdSponsorship = null;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(default).ThrowsForAnyArgs(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().UpsertAsync(null!).ThrowsForAnyArgs(callInfo =>
{ {
createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0); createdSponsorship = callInfo.ArgAt<OrganizationSponsorship>(0);
createdSponsorship.Id = Guid.NewGuid(); createdSponsorship.Id = Guid.NewGuid();
@ -199,8 +229,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) .IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true); .Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
var sponsorship = callInfo.Arg<OrganizationSponsorship>(); var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId; sponsorship.Id = sponsorshipId;
@ -238,8 +268,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) .IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true); .Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
var sponsorship = callInfo.Arg<OrganizationSponsorship>(); var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId; sponsorship.Id = sponsorshipId;
@ -276,8 +306,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships)) .IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true); .Returns(true);
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId.Value).Returns(user); sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(default)).Do(callInfo => sutProvider.GetDependency<IOrganizationSponsorshipRepository>().WhenForAnyArgs(x => x.UpsertAsync(null!)).Do(callInfo =>
{ {
var sponsorship = callInfo.Arg<OrganizationSponsorship>(); var sponsorship = callInfo.Arg<OrganizationSponsorship>();
sponsorship.Id = sponsorshipId; sponsorship.Id = sponsorshipId;
@ -291,7 +321,7 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
} }
]); ]);
await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser, var actual = await sutProvider.Sut.CreateSponsorshipAsync(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes); PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes);
@ -307,6 +337,8 @@ public class CreateSponsorshipCommandTests : FamiliesForEnterpriseTestsBase
Notes = notes Notes = notes
}; };
Assert.True(SponsorshipValidator(expectedSponsorship, actual));
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1) await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().Received(1)
.UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship))); .UpsertAsync(Arg.Is<OrganizationSponsorship>(s => SponsorshipValidator(s, expectedSponsorship)));
} }

View File

@ -1,208 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
[SutProviderCustomize]
public class CreateAdminInitiatedSponsorshipHandlerTests : FamiliesForEnterpriseTestsBase
{
[Theory]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task HandleAsync_MissingManageUsersPermission_ThrowsUnauthorizedException(
OrganizationUserType organizationUserType,
Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, string sponsoredEmail, string friendlyName,
Guid currentUserId, SutProvider<CreateAdminInitiatedSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Permissions = new Permissions(),
Type = organizationUserType
}
]);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null);
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
await sutProvider.Sut.HandleAsync(request));
Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task HandleAsync_InvalidUserType_ThrowsUnauthorizedException(
OrganizationUserType organizationUserType,
Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, string sponsoredEmail,
string friendlyName, Guid currentUserId,
SutProvider<CreateAdminInitiatedSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Permissions = new Permissions
{
ManageUsers = false,
},
Type = organizationUserType
}
]);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null);
var actual = await Assert.ThrowsAsync<UnauthorizedAccessException>(async () =>
await sutProvider.Sut.HandleAsync(request));
Assert.Equal("You do not have permissions to send sponsorships on behalf of the organization.", actual.Message);
}
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Owner)]
public async Task HandleAsync_CreatesAdminInitiatedSponsorship(
OrganizationUserType organizationUserType, Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
string sponsoredEmail, string friendlyName, Guid currentUserId, string notes,
SutProvider<CreateAdminInitiatedSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Type = organizationUserType
}
]);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes);
var actual = await sutProvider.Sut.HandleAsync(request);
var expectedSponsorship = new OrganizationSponsorship
{
IsAdminInitiated = true,
Notes = notes
};
AssertHelper.AssertPropertyEqual(expectedSponsorship, actual);
}
[Theory]
[BitAutoData(OrganizationUserType.User)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task HandleAsync_CreatesAdminInitiatedSponsorshipWithValidPermissionsButInvalidOrganizationUserType(
OrganizationUserType organizationUserType, Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
string sponsoredEmail, string friendlyName, Guid currentUserId, string notes,
SutProvider<CreateAdminInitiatedSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Type = organizationUserType,
Permissions =
{
ManageUsers = true
}
}
]);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes);
var actual = await sutProvider.Sut.HandleAsync(request);
var expectedSponsorship = new OrganizationSponsorship
{
IsAdminInitiated = true,
Notes = notes
};
AssertHelper.AssertPropertyEqual(expectedSponsorship, actual);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_ThrowsBadRequestException_WhenFeatureFlagIsDisabled(
OrganizationUserType organizationUserType, Organization sponsoringOrg, OrganizationUser sponsoringOrgUser,
string sponsoredEmail, string friendlyName, Guid currentUserId, string notes,
SutProvider<CreateAdminInitiatedSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(Arg.Is<string>(p => p == FeatureFlagKeys.PM17772_AdminInitiatedSponsorships))
.Returns(false);
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(currentUserId);
sutProvider.GetDependency<ICurrentContext>().Organizations.Returns([
new()
{
Id = sponsoringOrg.Id,
Permissions = new Permissions
{
ManageUsers = true,
},
Type = organizationUserType
}
]);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, notes);
var actual = await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.HandleAsync(request));
Assert.Equal("Feature 'pm-17772-admin-initiated-sponsorships' is not enabled.", actual.Message);
}
}

View File

@ -1,164 +0,0 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture.OrganizationSponsorshipFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationSponsorships.FamiliesForEnterprise.SponsorshipCreation;
[SutProviderCustomize]
public class CreateSponsorshipHandlerTests : FamiliesForEnterpriseTestsBase
{
[Theory, BitAutoData]
public async Task HandleAsync_OfferedToNotFound_ThrowsBadRequest(OrganizationUser orgUser, SutProvider<CreateSponsorshipHandler> sutProvider)
{
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).ReturnsNull();
var request = new CreateSponsorshipRequest(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, null, null, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(request));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(null!);
}
[Theory, BitAutoData]
public async Task HandleAsync_OfferedToSelf_ThrowsBadRequest(OrganizationUser orgUser, string sponsoredEmail, User user, SutProvider<CreateSponsorshipHandler> sutProvider)
{
user.Email = sponsoredEmail;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var request = new CreateSponsorshipRequest(null, orgUser, PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, null, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(request));
Assert.Contains("Cannot offer a Families Organization Sponsorship to yourself. Choose a different email.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(null!);
}
public static readonly OrganizationUserStatusType[] UnconfirmedOrganizationUserStatusTypes = Enum
.GetValues<OrganizationUserStatusType>()
.Where(x => x != OrganizationUserStatusType.Confirmed).ToArray();
[Theory, BitMemberAutoData(nameof(UnconfirmedOrganizationUserStatusTypes))]
public async Task HandleAsync_UnconfirmedSponsoringMember_ThrowsBadRequest(
OrganizationUserStatusType sponsoringMemberStatus, Organization sponsoringOrg,
OrganizationUser sponsoringOrgUser, string sponsoredEmail, User user, string friendlyName,
SutProvider<CreateSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = sponsoringMemberStatus;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(request));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
}
[Theory, BitMemberAutoData(nameof(NonEnterprisePlanTypes))]
public async Task HandleAsync_BadSponsoringOrgPlan_ThrowsBadRequest(PlanType sponsoringOrgPlan,
Organization org, OrganizationUser orgUser, User user, SutProvider<CreateSponsorshipHandler> sutProvider)
{
org.PlanType = sponsoringOrgPlan;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var request = new CreateSponsorshipRequest(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, null, null, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(request));
Assert.Contains("Specified Organization cannot sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(null!);
}
[Theory]
[BitMemberAutoData(nameof(NonConfirmedOrganizationUsersStatuses))]
public async Task HandleAsync_BadSponsoringUserStatus_ThrowsBadRequest(
OrganizationUserStatusType statusType, Organization org, OrganizationUser orgUser, User user,
SutProvider<CreateSponsorshipHandler> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = statusType;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
var request = new CreateSponsorshipRequest(org, orgUser, PlanSponsorshipType.FamiliesForEnterprise, null, null, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.HandleAsync(request));
Assert.Contains("Only confirmed users can sponsor other organizations.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(null!);
}
[Theory]
[OrganizationSponsorshipCustomize]
[BitAutoData]
public async Task HandleAsync_AlreadySponsoring_Throws(Organization org,
OrganizationUser orgUser, User user, OrganizationSponsorship sponsorship,
SutProvider<CreateSponsorshipHandler> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(orgUser.UserId!.Value).Returns(user);
sutProvider.GetDependency<IOrganizationSponsorshipRepository>()
.GetBySponsoringOrganizationUserIdAsync(orgUser.Id).Returns(sponsorship);
var request = new CreateSponsorshipRequest(org, orgUser, sponsorship.PlanSponsorshipType!.Value, null, null, null);
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.HandleAsync(request));
Assert.Contains("Can only sponsor one organization per Organization User.", exception.Message);
await sutProvider.GetDependency<IOrganizationSponsorshipRepository>().DidNotReceiveWithAnyArgs()
.CreateAsync(null!);
}
[Theory]
[BitAutoData]
public async Task HandleAsync_ReturnsExpectedSponsorship(Organization sponsoringOrg, OrganizationUser sponsoringOrgUser, User user,
string sponsoredEmail, string friendlyName, SutProvider<CreateSponsorshipHandler> sutProvider)
{
sponsoringOrg.PlanType = PlanType.EnterpriseAnnually;
sponsoringOrgUser.Status = OrganizationUserStatusType.Confirmed;
sutProvider.GetDependency<IUserService>().GetUserByIdAsync(sponsoringOrgUser.UserId!.Value).Returns(user);
var request = new CreateSponsorshipRequest(sponsoringOrg, sponsoringOrgUser,
PlanSponsorshipType.FamiliesForEnterprise, sponsoredEmail, friendlyName, null);
var actual = await sutProvider.Sut.HandleAsync(request);
var expectedSponsorship = new OrganizationSponsorship
{
SponsoringOrganizationId = sponsoringOrg.Id,
SponsoringOrganizationUserId = sponsoringOrgUser.Id,
FriendlyName = friendlyName,
OfferedToEmail = sponsoredEmail,
PlanSponsorshipType = PlanSponsorshipType.FamiliesForEnterprise,
IsAdminInitiated = false,
Notes = null
};
AssertHelper.AssertPropertyEqual(expectedSponsorship, actual);
}
}