mirror of
https://github.com/bitwarden/server.git
synced 2025-07-02 08:32:50 -05:00
Merge branch 'km/pm-10600-full-notification-content' into km/pm-10564
# Conflicts: # src/Core/Enums/PushType.cs # src/Core/NotificationHub/NotificationHubPushNotificationService.cs # src/Core/Services/IPushNotificationService.cs # src/Core/Services/Implementations/MultiServicePushNotificationService.cs # src/Core/Services/Implementations/NotificationsApiPushNotificationService.cs # src/Core/Services/Implementations/RelayPushNotificationService.cs # src/Core/Services/NoopImplementations/NoopPushNotificationService.cs
This commit is contained in:
@ -2,6 +2,7 @@
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
@ -12,7 +13,8 @@ internal class PolicyUpdateCustomization(PolicyType type, bool enabled) : ICusto
|
||||
{
|
||||
fixture.Customize<PolicyUpdate>(composer => composer
|
||||
.With(o => o.Type, type)
|
||||
.With(o => o.Enabled, enabled));
|
||||
.With(o => o.Enabled, enabled)
|
||||
.With(o => o.PerformedBy, new StandardUser(Guid.NewGuid(), false)));
|
||||
}
|
||||
}
|
||||
|
||||
|
52
test/Core.Test/AdminConsole/Helpers/AuthorizationHelpers.cs
Normal file
52
test/Core.Test/AdminConsole/Helpers/AuthorizationHelpers.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Helpers;
|
||||
|
||||
public static class AuthorizationHelpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a new Permission object with inverted permissions.
|
||||
/// This is useful to test negative cases, e.g. "all other permissions should fail".
|
||||
/// </summary>
|
||||
/// <param name="permissions"></param>
|
||||
/// <returns></returns>
|
||||
public static Permissions Invert(this Permissions permissions)
|
||||
{
|
||||
// Get all false boolean properties of input object
|
||||
var inputsToFlip = permissions
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Where(p =>
|
||||
p.PropertyType == typeof(bool) &&
|
||||
(bool)p.GetValue(permissions, null)! == false)
|
||||
.Select(p => p.Name);
|
||||
|
||||
var result = new Permissions();
|
||||
|
||||
// Set these to true on the result object
|
||||
result
|
||||
.GetType()
|
||||
.GetProperties()
|
||||
.Where(p => inputsToFlip.Contains(p.Name))
|
||||
.ToList()
|
||||
.ForEach(p => p.SetValue(result, true));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a sequence of all possible roles and permissions represented as CurrentContextOrganization objects.
|
||||
/// Used largely for authorization testing.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<CurrentContextOrganization> AllRoles() => new List<CurrentContextOrganization>
|
||||
{
|
||||
new () { Type = OrganizationUserType.Owner },
|
||||
new () { Type = OrganizationUserType.Admin },
|
||||
new () { Type = OrganizationUserType.Custom, Permissions = new Permissions() },
|
||||
new () { Type = OrganizationUserType.Custom, Permissions = new Permissions().Invert() },
|
||||
new () { Type = OrganizationUserType.User },
|
||||
};
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
using Bit.Core.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Helpers;
|
||||
|
||||
public class AuthorizationHelpersTests
|
||||
{
|
||||
[Fact]
|
||||
public void Permissions_Invert_InvertsAllPermissions()
|
||||
{
|
||||
var sut = new Permissions
|
||||
{
|
||||
AccessEventLogs = true,
|
||||
AccessReports = true,
|
||||
DeleteAnyCollection = true,
|
||||
ManagePolicies = true,
|
||||
ManageScim = true
|
||||
};
|
||||
|
||||
var result = sut.Invert();
|
||||
|
||||
Assert.True(result is
|
||||
{
|
||||
AccessEventLogs: false,
|
||||
AccessImportExport: true,
|
||||
AccessReports: false,
|
||||
CreateNewCollections: true,
|
||||
EditAnyCollection: true,
|
||||
DeleteAnyCollection: false,
|
||||
ManageGroups: true,
|
||||
ManagePolicies: false,
|
||||
ManageSso: true,
|
||||
ManageUsers: true,
|
||||
ManageResetPassword: true,
|
||||
ManageScim: false
|
||||
});
|
||||
}
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
@ -28,7 +33,12 @@ public class VerifyOrganizationDomainCommandTests
|
||||
DomainName = "Test Domain",
|
||||
Txt = "btw+test18383838383"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
expected.SetVerifiedDate();
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
@ -53,6 +63,10 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain> { expected });
|
||||
@ -77,9 +91,14 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain>());
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(expected.DomainName, Arg.Any<string>())
|
||||
.Returns(true);
|
||||
@ -107,9 +126,14 @@ public class VerifyOrganizationDomainCommandTests
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetByIdAsync(id)
|
||||
.Returns(expected);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(expected.DomainName)
|
||||
.Returns(new List<OrganizationDomain>());
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(expected.DomainName, Arg.Any<string>())
|
||||
.Returns(false);
|
||||
@ -143,7 +167,7 @@ public class VerifyOrganizationDomainCommandTests
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenSingleOrgPolicyShouldBeEnabled(
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
@ -157,11 +181,18 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(userId);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.Received(1)
|
||||
.SaveAsync(Arg.Is<Policy>(x => x.Type == PolicyType.SingleOrg && x.OrganizationId == domain.OrganizationId && x.Enabled), null);
|
||||
.SaveAsync(Arg.Is<PolicyUpdate>(x => x.Type == PolicyType.SingleOrg &&
|
||||
x.OrganizationId == domain.OrganizationId &&
|
||||
x.Enabled &&
|
||||
x.PerformedBy is StandardUser &&
|
||||
x.PerformedBy.UserId == userId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -176,20 +207,23 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled(
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
@ -199,16 +233,18 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -223,14 +259,66 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>()
|
||||
.DidNotReceive()
|
||||
.SaveAsync(Arg.Any<Policy>(), null);
|
||||
.SaveAsync(Arg.Any<PolicyUpdate>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenEmailShouldBeSentToUsersWhoBelongToTheDomain(
|
||||
ICollection<OrganizationUserUserDetails> organizationUsers,
|
||||
OrganizationDomain domain,
|
||||
Organization organization,
|
||||
SutProvider<VerifyOrganizationDomainCommand> sutProvider)
|
||||
{
|
||||
foreach (var organizationUser in organizationUsers)
|
||||
{
|
||||
organizationUser.Email = $"{organizationUser.Name}@{domain.DomainName}";
|
||||
}
|
||||
|
||||
var mockedUsers = organizationUsers
|
||||
.Where(x => x.Status != OrganizationUserStatusType.Invited &&
|
||||
x.Status != OrganizationUserStatusType.Revoked).ToList();
|
||||
|
||||
organization.Id = domain.OrganizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
|
||||
.Returns([]);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(domain.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IDnsResolverService>()
|
||||
.ResolveAsync(domain.DomainName, domain.Txt)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(Guid.NewGuid());
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(domain.OrganizationId)
|
||||
.Returns(mockedUsers);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().Received().SendClaimedDomainUserEmailAsync(
|
||||
Arg.Is<ManagedUserDomainClaimedEmails>(x =>
|
||||
x.EmailList.Count(e => e.EndsWith(domain.DomainName)) == mockedUsers.Count &&
|
||||
x.Organization.Id == organization.Id));
|
||||
}
|
||||
}
|
||||
|
@ -258,14 +258,15 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, true }, { orgUser2.Id, true } });
|
||||
|
||||
// Act
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, new[] { orgUser1.Id, orgUser2.Id }, null);
|
||||
var userIds = new[] { orgUser1.Id, orgUser2.Id };
|
||||
var results = await sutProvider.Sut.DeleteManyUsersAsync(organizationId, userIds, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, results.Count());
|
||||
Assert.All(results, r => Assert.Empty(r.Item2));
|
||||
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user2);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).GetManyAsync(userIds);
|
||||
await sutProvider.GetDependency<IUserRepository>().Received(1).DeleteManyAsync(Arg.Is<IEnumerable<User>>(users => users.Any(u => u.Id == user1.Id) && users.Any(u => u.Id == user2.Id)));
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1
|
||||
@ -286,7 +287,9 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
Assert.Single(result);
|
||||
Assert.Equal(orgUserId, result.First().Item1);
|
||||
Assert.Contains("Member not found.", result.First().Item2);
|
||||
await sutProvider.GetDependency<IUserService>().Received(0).DeleteAsync(Arg.Any<User>());
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.DeleteManyAsync(default);
|
||||
await sutProvider.GetDependency<IEventService>().Received(0)
|
||||
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser, EventType, DateTime?)>>());
|
||||
}
|
||||
@ -484,7 +487,6 @@ public class DeleteManagedOrganizationUserAccountCommandTests
|
||||
Assert.Equal("You cannot delete a member with Invited status.", results.First(r => r.Item1 == orgUser2.Id).Item2);
|
||||
Assert.Equal("Member is not managed by the organization.", results.First(r => r.Item1 == orgUser3.Id).Item2);
|
||||
|
||||
await sutProvider.GetDependency<IUserService>().Received(1).DeleteAsync(user1);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUser, EventType, DateTime?)>>(events =>
|
||||
events.Count(e => e.Item1.Id == orgUser1.Id && e.Item2 == EventType.OrganizationUser_Deleted) == 1));
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,185 @@
|
||||
using Bit.Core.AdminConsole.Models.Data;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RevokeNonCompliantOrganizationUserCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenUnrecognizedUserType_WhenAttemptingToRevoke_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, [], new InvalidUser());
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorRequestedByWasNotValid, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeThemselves_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails revokingUser,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, revokingUser,
|
||||
new StandardUser(revokingUser?.UserId ?? Guid.NewGuid(), true));
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorCannotRevokeSelf, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeOrgUsersFromAnotherOrg_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userFromAnotherOrg,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userFromAnotherOrg.OrganizationId = Guid.NewGuid();
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userFromAnotherOrg,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorInvalidUsers, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeAllOwnersFromOrg_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(false);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorOrgMustHaveAtLeastOneOwner, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeOwnerWhenNotAnOwner_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Type = OrganizationUserType.Owner;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains(RevokeNonCompliantOrganizationUserCommand.ErrorOnlyOwnersCanRevokeOtherOwners, result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserAttemptsToRevokeUserWhoIsAlreadyRevoked_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Status = OrganizationUserStatusType.Revoked;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), true));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.Contains($"{RevokeNonCompliantOrganizationUserCommand.ErrorUserAlreadyRevoked} Id: {userToRevoke.Id}", result.ErrorMessages);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenPopulatedRequest_WhenUserHasMultipleInvalidUsers_ThenErrorShouldBeReturned(
|
||||
Guid organizationId, IEnumerable<OrganizationUserUserDetails> usersToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
var revocableUsers = usersToRevoke.ToList();
|
||||
revocableUsers.ForEach(user => user.OrganizationId = organizationId);
|
||||
revocableUsers[0].Type = OrganizationUserType.Owner;
|
||||
revocableUsers[1].Status = OrganizationUserStatusType.Revoked;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, revocableUsers,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
Assert.True(result.HasErrors);
|
||||
Assert.True(result.ErrorMessages.Count > 1);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RevokeNonCompliantOrganizationUsersAsync_GivenValidPopulatedRequest_WhenUserAttemptsToRevokeAUser_ThenUserShouldBeRevoked(
|
||||
Guid organizationId, OrganizationUserUserDetails userToRevoke,
|
||||
SutProvider<RevokeNonCompliantOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
userToRevoke.OrganizationId = organizationId;
|
||||
userToRevoke.Type = OrganizationUserType.Admin;
|
||||
|
||||
var command = new RevokeOrganizationUsersRequest(organizationId, userToRevoke,
|
||||
new StandardUser(Guid.NewGuid(), false));
|
||||
|
||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||
.HasConfirmedOwnersExceptAsync(organizationId, Arg.Any<IEnumerable<Guid>>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.RevokeNonCompliantOrganizationUsersAsync(command);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.RevokeManyByIdAsync(Arg.Any<IEnumerable<Guid>>());
|
||||
|
||||
Assert.True(result.Success);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogOrganizationUserEventsAsync(
|
||||
Arg.Is<IEnumerable<(OrganizationUserUserDetails organizationUser, EventType eventType, DateTime? time
|
||||
)>>(
|
||||
x => x.Any(y =>
|
||||
y.organizationUser.Id == userToRevoke.Id && y.eventType == EventType.OrganizationUser_Revoked)
|
||||
));
|
||||
}
|
||||
|
||||
public class InvalidUser : IActingUser
|
||||
{
|
||||
public Guid? UserId => Guid.Empty;
|
||||
public bool IsOrganizationOwnerOrProvider => false;
|
||||
public EventSystemUser? SystemUserType => null;
|
||||
}
|
||||
}
|
@ -14,34 +14,13 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||
public class UpdateOrganizationUserGroupsCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateUserGroups_Passes(
|
||||
public async Task UpdateUserGroups_ShouldUpdateUserGroupsAndLogUserEvent(
|
||||
OrganizationUser organizationUser,
|
||||
IEnumerable<Guid> groupIds,
|
||||
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, null);
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().DidNotReceiveWithAnyArgs()
|
||||
.ValidateOrganizationUserUpdatePermissions(default, default, default, default);
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_UpdatedGroups);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateUserGroups_WithSavingUserId_Passes(
|
||||
OrganizationUser organizationUser,
|
||||
IEnumerable<Guid> groupIds,
|
||||
Guid savingUserId,
|
||||
SutProvider<UpdateOrganizationUserGroupsCommand> sutProvider)
|
||||
{
|
||||
organizationUser.Permissions = null;
|
||||
|
||||
await sutProvider.Sut.UpdateUserGroupsAsync(organizationUser, groupIds, savingUserId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationService>().Received(1)
|
||||
.ValidateOrganizationUserUpdatePermissions(organizationUser.OrganizationId, organizationUser.Type, null, organizationUser.GetPermissions());
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1)
|
||||
.UpdateGroupsAsync(organizationUser.Id, groupIds);
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
|
@ -0,0 +1,238 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Business;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Organizations.OrganizationSignUp;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CloudICloudOrganizationSignUpCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == null
|
||||
&& o.SmServiceAccounts == null));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Organization.Seats &&
|
||||
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
plan,
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.UseSecretsManager
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_AssignsOwnerToDefaultCollection
|
||||
(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
|
||||
// Extract orgUserId when created
|
||||
Guid? orgUserId = null;
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
// Assert: created a Can Manage association for the default collection
|
||||
Assert.NotNull(orgUserId);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
||||
Arg.Any<Collection>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
||||
cas.Count() == 1 &&
|
||||
cas.All(c =>
|
||||
c.Id == orgUserId &&
|
||||
!c.ReadOnly &&
|
||||
!c.HidePasswords &&
|
||||
c.Manage)));
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpOrganizationAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
|
||||
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Organization.Seats &&
|
||||
referenceEvent.Storage == result.Organization.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Organization);
|
||||
Assert.NotNull(result.OrganizationUser);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
Arg.Is<Plan>(plan),
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.IsFromSecretsManagerTrial
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromProvider = true;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 0;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.Plan = PlanType.Free;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
signup.AdditionalStorageGb = 0;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 100;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<CloudOrganizationSignUpCommand> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpOrganizationAsync(signup));
|
||||
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
@ -10,6 +11,7 @@ using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Commands;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -61,6 +63,92 @@ public class SingleOrgPolicyValidatorTests
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RevokesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||
Guid savingUserId,
|
||||
Guid nonCompliantUserId,
|
||||
Organization organization, SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "user3@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||
|
||||
var otherOrganizationUser = new OrganizationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = new Guid(),
|
||||
UserId = nonCompliantUserId,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(
|
||||
Arg.Is<RevokeOrganizationUsersRequest>(r =>
|
||||
r.OrganizationId == organization.Id &&
|
||||
r.OrganizationUsers.Count() == 1 &&
|
||||
r.OrganizationUsers.First().Id == nonCompliantUser.Id));
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RemovesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
@ -71,6 +159,94 @@ public class SingleOrgPolicyValidatorTests
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "user3@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||
|
||||
var otherOrganizationUser = new OrganizationUser
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = new Guid(),
|
||||
UserId = nonCompliantUserId,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, compliantUser1.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, compliantUser2.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser1.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), compliantUser2.Email);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(), nonCompliantUser.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_WhenAccountDeprovisioningIsEnabled_ThenUsersAreRevoked(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||
Guid savingUserId,
|
||||
Guid nonCompliantUserId,
|
||||
Organization organization, SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
@ -114,16 +290,19 @@ public class SingleOrgPolicyValidatorTests
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@example.com");
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Commands;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -176,6 +178,10 @@ public class TwoFactorAuthenticationPolicyValidatorTests
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
@ -201,9 +207,151 @@ public class TwoFactorAuthenticationPolicyValidatorTests
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy));
|
||||
|
||||
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, badRequestException.Message);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>().DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_GivenUpdateTo2faPolicy_WhenAccountProvisioningIsDisabled_ThenRevokeUserCommandShouldNotBeCalled(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)]
|
||||
PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)]
|
||||
Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(false);
|
||||
|
||||
var orgUserDetailUserAcceptedWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserAcceptedWithout2Fa
|
||||
});
|
||||
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserAcceptedWithout2Fa, false),
|
||||
});
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.DidNotReceive()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_GivenUpdateTo2faPolicy_WhenAccountProvisioningIsEnabledAndUserDoesNotHaveMasterPassword_ThenNonCompliantMembersErrorMessageWillReturn(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUserDetailUserWithout2Fa]);
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWithout2Fa, false),
|
||||
});
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy));
|
||||
|
||||
Assert.Equal(TwoFactorAuthenticationPolicyValidator.NonCompliantMembersWillLoseAccessMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_WhenAccountProvisioningIsEnabledAndUserHasMasterPassword_ThenUserWillBeRevoked(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
var orgUserDetailUserWithout2Fa = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([orgUserDetailUserWithout2Fa]);
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWithout2Fa, true),
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>())
|
||||
.Returns(new CommandResult());
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(Arg.Any<RevokeOrganizationUsersRequest>());
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@test.com");
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class SavePolicyCommandTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
@ -115,7 +115,7 @@ public class SavePolicyCommandTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
|
@ -169,7 +169,6 @@ public class EventServiceTests
|
||||
new EventMessage()
|
||||
{
|
||||
IpAddress = ipAddress,
|
||||
DeviceType = DeviceType.Server,
|
||||
OrganizationId = orgUser.OrganizationId,
|
||||
UserId = orgUser.UserId,
|
||||
OrganizationUserId = orgUser.Id,
|
||||
|
@ -20,7 +20,6 @@ using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Models.Mail;
|
||||
using Bit.Core.Models.StaticStore;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions.Interface;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -200,221 +199,6 @@ public class OrganizationServiceTests
|
||||
referenceEvent.Users == expectedNewUsersCount));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_PM_Family_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var purchaseOrganizationPlan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == null
|
||||
&& o.SmServiceAccounts == null));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Item1.Seats &&
|
||||
referenceEvent.Storage == result.Item1.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
plan,
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.UseSecretsManager
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.FamiliesAnnually)]
|
||||
public async Task SignUp_AssignsOwnerToDefaultCollection
|
||||
(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.UseSecretsManager = false;
|
||||
|
||||
// Extract orgUserId when created
|
||||
Guid? orgUserId = null;
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.CreateAsync(Arg.Do<OrganizationUser>(ou => orgUserId = ou.Id));
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
// Assert: created a Can Manage association for the default collection
|
||||
Assert.NotNull(orgUserId);
|
||||
await sutProvider.GetDependency<ICollectionRepository>().Received(1).CreateAsync(
|
||||
Arg.Any<Collection>(),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas => cas == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(cas =>
|
||||
cas.Count() == 1 &&
|
||||
cas.All(c =>
|
||||
c.Id == orgUserId &&
|
||||
!c.ReadOnly &&
|
||||
!c.HidePasswords &&
|
||||
c.Manage)));
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
[BitAutoData(PlanType.EnterpriseMonthly)]
|
||||
[BitAutoData(PlanType.TeamsAnnually)]
|
||||
[BitAutoData(PlanType.TeamsMonthly)]
|
||||
public async Task SignUp_SM_Passes(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
|
||||
var plan = StaticStore.GetPlan(signup.Plan);
|
||||
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromSecretsManagerTrial = false;
|
||||
|
||||
var result = await sutProvider.Sut.SignUpAsync(signup);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<Organization>(o =>
|
||||
o.Seats == plan.PasswordManager.BaseSeats + signup.AdditionalSeats
|
||||
&& o.SmSeats == plan.SecretsManager.BaseSeats + signup.AdditionalSmSeats
|
||||
&& o.SmServiceAccounts == plan.SecretsManager.BaseServiceAccount + signup.AdditionalServiceAccounts));
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).CreateAsync(
|
||||
Arg.Is<OrganizationUser>(o => o.AccessSecretsManager == signup.UseSecretsManager));
|
||||
|
||||
await sutProvider.GetDependency<IReferenceEventService>().Received(1)
|
||||
.RaiseEventAsync(Arg.Is<ReferenceEvent>(referenceEvent =>
|
||||
referenceEvent.Type == ReferenceEventType.Signup &&
|
||||
referenceEvent.PlanName == plan.Name &&
|
||||
referenceEvent.PlanType == plan.Type &&
|
||||
referenceEvent.Seats == result.Item1.Seats &&
|
||||
referenceEvent.Storage == result.Item1.MaxStorageGb));
|
||||
// TODO: add reference events for SmSeats and Service Accounts - see AC-1481
|
||||
|
||||
Assert.NotNull(result.Item1);
|
||||
Assert.NotNull(result.Item2);
|
||||
|
||||
await sutProvider.GetDependency<IPaymentService>().Received(1).PurchaseOrganizationAsync(
|
||||
Arg.Any<Organization>(),
|
||||
signup.PaymentMethodType.Value,
|
||||
signup.PaymentToken,
|
||||
Arg.Is<Plan>(plan),
|
||||
signup.AdditionalStorageGb,
|
||||
signup.AdditionalSeats,
|
||||
signup.PremiumAccessAddon,
|
||||
signup.TaxInfo,
|
||||
false,
|
||||
signup.AdditionalSmSeats.GetValueOrDefault(),
|
||||
signup.AdditionalServiceAccounts.GetValueOrDefault(),
|
||||
signup.IsFromSecretsManagerTrial
|
||||
);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PlanType.EnterpriseAnnually)]
|
||||
public async Task SignUp_SM_Throws_WhenManagedByMSP(PlanType planType, OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.Plan = planType;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.AdditionalSeats = 15;
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalServiceAccounts = 20;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.IsFromProvider = true;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("Organizations with a Managed Service Provider do not support Secrets Manager.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SecretManager_AdditionalServiceAccounts_NotAllowedByPlan_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 0;
|
||||
signup.AdditionalSeats = 0;
|
||||
signup.Plan = PlanType.Free;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
signup.AdditionalStorageGb = 0;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("Plan does not allow additional Machine Accounts.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_SMSeatsGreatThanPMSeat_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 100;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = 10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You cannot have more Secrets Manager seats than Password Manager seats", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SignUpAsync_InvalidateServiceAccount_ShouldThrowException(OrganizationSignup signup, SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
signup.AdditionalSmSeats = 10;
|
||||
signup.AdditionalSeats = 10;
|
||||
signup.Plan = PlanType.EnterpriseAnnually;
|
||||
signup.UseSecretsManager = true;
|
||||
signup.PaymentMethodType = PaymentMethodType.Card;
|
||||
signup.PremiumAccessAddon = false;
|
||||
signup.AdditionalServiceAccounts = -10;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SignUpAsync(signup));
|
||||
Assert.Contains("You can't subtract Machine Accounts!", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SignupClientAsync_Succeeds(
|
||||
OrganizationSignup signup,
|
||||
@ -1833,11 +1617,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(true);
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user because they are a member of " +
|
||||
"another organization which forbids it", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1865,11 +1652,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until they enable " +
|
||||
"two-step login on their user account.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1924,11 +1714,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until " +
|
||||
"they leave or remove all other organizations.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the single organization policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1958,11 +1751,57 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(true);
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user because they are a member of " +
|
||||
"another organization which forbids it", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<EventSystemUser>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task RestoreUser_WithSingleOrgPolicyEnabled_And_2FA_Policy_Fails(
|
||||
Organization organization,
|
||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser,
|
||||
SutProvider<OrganizationService> sutProvider)
|
||||
{
|
||||
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
||||
secondOrganizationUser.UserId = organizationUser.UserId;
|
||||
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||
var eventService = sutProvider.GetDependency<IEventService>();
|
||||
|
||||
organizationUserRepository.GetManyByUserAsync(organizationUser.UserId.Value).Returns(new[] { organizationUser, secondOrganizationUser });
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[]
|
||||
{
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[]
|
||||
{
|
||||
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||
});
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the single organization and two-step login polciy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
@ -1986,11 +1825,14 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
|
||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
||||
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
|
||||
|
||||
var user = new User();
|
||||
user.Email = "test@bitwarden.com";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||
|
||||
Assert.Contains("you cannot restore this user until they enable " +
|
||||
"two-step login on their user account.", exception.Message.ToLowerInvariant());
|
||||
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
||||
|
||||
await organizationUserRepository.DidNotReceiveWithAnyArgs().RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||
await eventService.DidNotReceiveWithAnyArgs()
|
||||
|
@ -1,25 +1,13 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services.Implementations;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using AdminConsoleFixtures = Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using GlobalSettings = Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Services;
|
||||
@ -27,667 +15,6 @@ namespace Bit.Core.Test.AdminConsole.Services;
|
||||
[SutProviderCustomize]
|
||||
public class PolicyServiceTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.DisableSend)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
SetupOrg(sutProvider, policy.OrganizationId, null);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.DisableSend)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
var orgId = Guid.NewGuid();
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
UsePolicies = false,
|
||||
});
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("cannot use policies", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_RequireSsoEnabled_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.RequireSso)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = true }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Sign-On Authentication policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_VaultTimeoutEnabled_ThrowsBadRequest([AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.MaximumVaultTimeout)
|
||||
.Returns(new Policy { Enabled = true });
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Maximum Vault Timeout policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(PolicyType.SingleOrg)]
|
||||
[BitAutoData(PolicyType.RequireSso)]
|
||||
public async Task SaveAsync_PolicyRequiredByKeyConnector_DisablePolicy_ThrowsBadRequest(
|
||||
PolicyType policyType,
|
||||
Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
policy.Type = policyType;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
var data = new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector };
|
||||
ssoConfig.SetData(data);
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Key Connector is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequireSsoPolicy_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.RequireSso)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_NewPolicy_Created(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Id = default;
|
||||
policy.Data = null;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = true }));
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, Guid.NewGuid());
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_VaultTimeoutPolicy_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.MaximumVaultTimeout)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_UpdateTwoFactor(
|
||||
Organization organization,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
// If the policy that this is updating isn't enabled then do some work now that the current one is enabled
|
||||
|
||||
organization.UsePolicies = true;
|
||||
policy.OrganizationId = organization.Id;
|
||||
|
||||
SetupOrg(sutProvider, organization.Id, organization);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByIdAsync(policy.Id)
|
||||
.Returns(new Policy
|
||||
{
|
||||
Id = policy.Id,
|
||||
Type = PolicyType.TwoFactorAuthentication,
|
||||
Enabled = false
|
||||
});
|
||||
|
||||
var orgUserDetailUserInvited = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Invited,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserAcceptedWith2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserAcceptedWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserInvited,
|
||||
orgUserDetailUserAcceptedWith2FA,
|
||||
orgUserDetailUserAcceptedWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserInvited, false),
|
||||
(orgUserDetailUserAcceptedWith2FA, true),
|
||||
(orgUserDetailUserAcceptedWithout2FA, false),
|
||||
(orgUserDetailAdmin, false),
|
||||
});
|
||||
|
||||
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, savingUserId);
|
||||
|
||||
await removeOrganizationUserCommand.Received()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWithout2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().Received()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWithout2FA.Email);
|
||||
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserInvited.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserInvited.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWith2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWith2FA.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailAdmin.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailAdmin.Email);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_EnableTwoFactor_WithoutMasterPasswordOr2FA_ThrowsBadRequest(
|
||||
Organization organization,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
organization.UsePolicies = true;
|
||||
policy.OrganizationId = organization.Id;
|
||||
|
||||
SetupOrg(sutProvider, organization.Id, organization);
|
||||
|
||||
var orgUserDetailUserWith2FAAndMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserWith2FANoMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserWith2FAAndMP,
|
||||
orgUserDetailUserWith2FANoMP,
|
||||
orgUserDetailUserWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids =>
|
||||
ids.Contains(orgUserDetailUserWith2FANoMP.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailUserWithout2FA.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailAdmin.UserId.Value)))
|
||||
.Returns(new List<(Guid userId, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWith2FANoMP.UserId.Value, true),
|
||||
(orgUserDetailUserWithout2FA.UserId.Value, false),
|
||||
(orgUserDetailAdmin.UserId.Value, false),
|
||||
});
|
||||
|
||||
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, savingUserId));
|
||||
|
||||
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await removeOrganizationUserCommand.DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(default, default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_UpdateSingleOrg(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.TwoFactorAuthentication)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
// If the policy that this is updating isn't enabled then do some work now that the current one is enabled
|
||||
|
||||
var org = new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
Name = "TEST",
|
||||
};
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, org);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByIdAsync(policy.Id)
|
||||
.Returns(new Policy
|
||||
{
|
||||
Id = policy.Id,
|
||||
Type = PolicyType.SingleOrg,
|
||||
Enabled = false,
|
||||
});
|
||||
|
||||
var orgUserDetail = new Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "test@bitwarden.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<Core.Models.Data.Organizations.OrganizationUsers.OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetail,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUserDetail.UserId.Value)))
|
||||
.Returns(new List<(Guid userId, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetail.UserId.Value, false),
|
||||
});
|
||||
|
||||
var utcNow = DateTime.UtcNow;
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policy, savingUserId);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received()
|
||||
.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received()
|
||||
.UpsertAsync(policy);
|
||||
|
||||
Assert.True(policy.CreationDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(false, false)]
|
||||
public async Task SaveAsync_ResetPasswordPolicyRequiredByTrustedDeviceEncryption_DisablePolicyOrDisableAutomaticEnrollment_ThrowsBadRequest(
|
||||
bool policyEnabled,
|
||||
bool autoEnrollEnabled,
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = policyEnabled;
|
||||
policy.SetDataModel(new ResetPasswordDataModel
|
||||
{
|
||||
AutoEnrollEnabled = autoEnrollEnabled
|
||||
});
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Trusted device encryption is on and requires this policy.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequireSsoPolicyRequiredByTrustedDeviceEncryption_DisablePolicy_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.RequireSso)] Policy policy,
|
||||
SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policy.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Trusted device encryption is on and requires this policy.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_PolicyRequiredForAccountRecovery_NotEnabled_ThrowsBadRequestAsync(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = true;
|
||||
policy.SetDataModel(new ResetPasswordDataModel());
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
|
||||
.Returns(Task.FromResult(new Policy { Enabled = false }));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default, default);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_SingleOrg_AccountRecoveryEnabled_ThrowsBadRequest(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
policy.Enabled = false;
|
||||
|
||||
SetupOrg(sutProvider, policy.OrganizationId, new Organization
|
||||
{
|
||||
Id = policy.OrganizationId,
|
||||
UsePolicies = true,
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.ResetPassword)
|
||||
.Returns(new Policy { Enabled = true });
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy,
|
||||
Guid.NewGuid()));
|
||||
|
||||
Assert.Contains("Account recovery policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
@ -816,32 +143,4 @@ public class PolicyServiceTests
|
||||
new() { OrganizationId = Guid.NewGuid(), PolicyType = PolicyType.DisableSend, PolicyEnabled = true, OrganizationUserType = OrganizationUserType.User, OrganizationUserStatus = OrganizationUserStatusType.Invited, IsProvider = true }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_GivenOrganizationUsingPoliciesAndHasVerifiedDomains_WhenSingleOrgPolicyIsDisabled_ThenAnErrorShouldBeThrownOrganizationHasVerifiedDomains(
|
||||
[AdminConsoleFixtures.Policy(PolicyType.SingleOrg)] Policy policy, Organization org, SutProvider<PolicyService> sutProvider)
|
||||
{
|
||||
org.Id = policy.OrganizationId;
|
||||
org.UsePolicies = true;
|
||||
|
||||
policy.Enabled = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(policy.OrganizationId)
|
||||
.Returns(org);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(org.Id)
|
||||
.Returns(true);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policy, null));
|
||||
|
||||
Assert.Equal("The Single organization policy is required for organizations that have enabled domain verification.", badRequestException.Message);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
@ -338,16 +339,26 @@ public class SsoConfigServiceTests
|
||||
|
||||
await sutProvider.Sut.SaveAsync(ssoConfig, organization);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<Policy>(t => t.Type == PolicyType.SingleOrg),
|
||||
null
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.SingleOrg &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>().Received(1)
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<Policy>(t => t.Type == PolicyType.ResetPassword && t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled),
|
||||
null
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.ResetPassword &&
|
||||
t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISavePolicyCommand>().Received(1)
|
||||
.SaveAsync(
|
||||
Arg.Is<PolicyUpdate>(t => t.Type == PolicyType.RequireSso &&
|
||||
t.OrganizationId == organization.Id &&
|
||||
t.Enabled)
|
||||
);
|
||||
|
||||
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
|
||||
|
@ -1581,21 +1581,18 @@ public class SubscriberServiceTests
|
||||
|
||||
#region VerifyBankAccount
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_NullSubscriber_ThrowsArgumentNullException(
|
||||
SutProvider<SubscriberService> sutProvider) => await Assert.ThrowsAsync<ArgumentNullException>(
|
||||
() => sutProvider.Sut.VerifyBankAccount(null, (0, 0)));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_NoSetupIntentId_ThrowsBillingException(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, (1, 1)));
|
||||
SutProvider<SubscriberService> sutProvider) => await ThrowsBillingExceptionAsync(() => sutProvider.Sut.VerifyBankAccount(provider, ""));
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task VerifyBankAccount_MakesCorrectInvocations(
|
||||
Provider provider,
|
||||
SutProvider<SubscriberService> sutProvider)
|
||||
{
|
||||
const string descriptorCode = "SM1234";
|
||||
|
||||
var setupIntent = new SetupIntent
|
||||
{
|
||||
Id = "setup_intent_id",
|
||||
@ -1608,11 +1605,11 @@ public class SubscriberServiceTests
|
||||
|
||||
stripeAdapter.SetupIntentGet(setupIntent.Id).Returns(setupIntent);
|
||||
|
||||
await sutProvider.Sut.VerifyBankAccount(provider, (1, 1));
|
||||
await sutProvider.Sut.VerifyBankAccount(provider, descriptorCode);
|
||||
|
||||
await stripeAdapter.Received(1).SetupIntentVerifyMicroDeposit(setupIntent.Id,
|
||||
Arg.Is<SetupIntentVerifyMicrodepositsOptions>(
|
||||
options => options.Amounts[0] == 1 && options.Amounts[1] == 1));
|
||||
options => options.DescriptorCode == descriptorCode));
|
||||
|
||||
await stripeAdapter.Received(1).PaymentMethodAttachAsync(setupIntent.PaymentMethodId,
|
||||
Arg.Is<PaymentMethodAttachOptions>(
|
||||
|
@ -0,0 +1,197 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.KeyManagement.Commands;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RegenerateUserAsymmetricKeysCommandTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_NoCurrentContext_NotFoundException(
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsNullForAnyArgs();
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegenerateKeysAsync_UserHasNoSharedAccess_Success(
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess);
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.Received(1)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Is(userAsymmetricKeys));
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.Received(1)
|
||||
.PushSyncSettingsAsync(Arg.Is(userAsymmetricKeys.UserId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(false, false, true)]
|
||||
[BitAutoData(false, true, false)]
|
||||
[BitAutoData(false, true, true)]
|
||||
[BitAutoData(true, false, false)]
|
||||
[BitAutoData(true, false, true)]
|
||||
[BitAutoData(true, true, false)]
|
||||
[BitAutoData(true, true, true)]
|
||||
public async Task RegenerateKeysAsync_UserIdMisMatch_NotFoundException(
|
||||
bool userAsymmetricKeysMismatch,
|
||||
bool orgMismatch,
|
||||
bool emergencyAccessMismatch,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId
|
||||
.ReturnsForAnyArgs(userAsymmetricKeysMismatch ? new Guid() : userAsymmetricKeys.UserId);
|
||||
|
||||
if (!orgMismatch)
|
||||
{
|
||||
usersOrganizationAccounts =
|
||||
SetupOrganizationUserAccounts(userAsymmetricKeys.UserId, usersOrganizationAccounts);
|
||||
}
|
||||
|
||||
if (!emergencyAccessMismatch)
|
||||
{
|
||||
designatedEmergencyAccess = SetupEmergencyAccess(userAsymmetricKeys.UserId, designatedEmergencyAccess);
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserStatusType.Confirmed)]
|
||||
[BitAutoData(OrganizationUserStatusType.Revoked)]
|
||||
public async Task RegenerateKeysAsync_UserInOrganizations_BadRequestException(
|
||||
OrganizationUserStatusType organizationUserStatus,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<OrganizationUser> usersOrganizationAccounts)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
usersOrganizationAccounts = CreateInOrganizationAccounts(userAsymmetricKeys.UserId, organizationUserStatus,
|
||||
usersOrganizationAccounts);
|
||||
var designatedEmergencyAccess = new List<EmergencyAccessDetails>();
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(EmergencyAccessStatusType.Confirmed)]
|
||||
[BitAutoData(EmergencyAccessStatusType.RecoveryApproved)]
|
||||
[BitAutoData(EmergencyAccessStatusType.RecoveryInitiated)]
|
||||
public async Task RegenerateKeysAsync_UserHasDesignatedEmergencyAccess_BadRequestException(
|
||||
EmergencyAccessStatusType statusType,
|
||||
SutProvider<RegenerateUserAsymmetricKeysCommand> sutProvider,
|
||||
UserAsymmetricKeys userAsymmetricKeys,
|
||||
ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.ReturnsForAnyArgs(userAsymmetricKeys.UserId);
|
||||
designatedEmergencyAccess =
|
||||
CreateDesignatedEmergencyAccess(userAsymmetricKeys.UserId, statusType, designatedEmergencyAccess);
|
||||
var usersOrganizationAccounts = new List<OrganizationUser>();
|
||||
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.RegenerateKeysAsync(userAsymmetricKeys,
|
||||
usersOrganizationAccounts, designatedEmergencyAccess));
|
||||
|
||||
await sutProvider.GetDependency<IUserAsymmetricKeysRepository>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.RegenerateUserAsymmetricKeysAsync(Arg.Any<UserAsymmetricKeys>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>()
|
||||
.ReceivedWithAnyArgs(0)
|
||||
.PushSyncSettingsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
private static ICollection<OrganizationUser> CreateInOrganizationAccounts(Guid userId,
|
||||
OrganizationUserStatusType organizationUserStatus, ICollection<OrganizationUser> organizationUserAccounts)
|
||||
{
|
||||
foreach (var organizationUserAccount in organizationUserAccounts)
|
||||
{
|
||||
organizationUserAccount.UserId = userId;
|
||||
organizationUserAccount.Status = organizationUserStatus;
|
||||
}
|
||||
|
||||
return organizationUserAccounts;
|
||||
}
|
||||
|
||||
private static ICollection<EmergencyAccessDetails> CreateDesignatedEmergencyAccess(Guid userId,
|
||||
EmergencyAccessStatusType status, ICollection<EmergencyAccessDetails> designatedEmergencyAccess)
|
||||
{
|
||||
foreach (var designated in designatedEmergencyAccess)
|
||||
{
|
||||
designated.GranteeId = userId;
|
||||
designated.Status = status;
|
||||
}
|
||||
|
||||
return designatedEmergencyAccess;
|
||||
}
|
||||
|
||||
private static ICollection<OrganizationUser> SetupOrganizationUserAccounts(Guid userId,
|
||||
ICollection<OrganizationUser> organizationUserAccounts)
|
||||
{
|
||||
foreach (var organizationUserAccount in organizationUserAccounts)
|
||||
{
|
||||
organizationUserAccount.UserId = userId;
|
||||
}
|
||||
|
||||
return organizationUserAccounts;
|
||||
}
|
||||
|
||||
private static ICollection<EmergencyAccessDetails> SetupEmergencyAccess(Guid userId,
|
||||
ICollection<EmergencyAccessDetails> emergencyAccessDetails)
|
||||
{
|
||||
foreach (var emergencyAccessDetail in emergencyAccessDetails)
|
||||
{
|
||||
emergencyAccessDetail.GranteeId = userId;
|
||||
}
|
||||
|
||||
return emergencyAccessDetails;
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Auth.UserFeatures.UserKey.Implementations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.UserKey.Implementations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Identity;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.UserFeatures.UserKey;
|
||||
namespace Bit.Core.Test.KeyManagement.UserFeatures.UserKey;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RotateUserKeyCommandTests
|
@ -111,7 +111,8 @@ public static class OrganizationLicenseFileFixtures
|
||||
SmServiceAccounts = 8,
|
||||
MaxAutoscaleSmSeats = 101,
|
||||
MaxAutoscaleSmServiceAccounts = 102,
|
||||
LimitCollectionCreationDeletion = true,
|
||||
LimitCollectionCreation = true,
|
||||
LimitCollectionDeletion = true,
|
||||
AllowAdminAccessToAllCollectionItems = true,
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
@ -36,7 +37,7 @@ public class OrganizationLicenseTests
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationLicense.CurrentLicenseFileVersion)] // Previous version (this property is 1 behind)
|
||||
[BitAutoData(OrganizationLicense.CurrentLicenseFileVersion + 1)] // Current version
|
||||
public void OrganizationLicense_LoadedFromDisk_VerifyData_Passes(int licenseVersion)
|
||||
public void OrganizationLicense_LoadedFromDisk_VerifyData_Passes(int licenseVersion, ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var license = OrganizationLicenseFileFixtures.GetVersion(licenseVersion);
|
||||
|
||||
@ -49,7 +50,7 @@ public class OrganizationLicenseTests
|
||||
{
|
||||
Id = new Guid(OrganizationLicenseFileFixtures.InstallationId)
|
||||
});
|
||||
Assert.True(license.VerifyData(organization, globalSettings));
|
||||
Assert.True(license.VerifyData(organization, claimsPrincipal, globalSettings));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -9,9 +9,32 @@ public class NotificationStatusDetailsCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<NotificationStatusDetails>(composer => composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid()));
|
||||
fixture.Customize<NotificationStatusDetails>(composer =>
|
||||
{
|
||||
return composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomization(int count) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var customization = new NotificationStatusDetailsCustomization();
|
||||
fixture.Customize<IEnumerable<NotificationStatusDetails>>(composer => composer.FromFactory(() =>
|
||||
{
|
||||
var notifications = new List<NotificationStatusDetails>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
customization.Customize(fixture);
|
||||
var notificationStatusDetails = fixture.Create<NotificationStatusDetails>();
|
||||
notifications.Add(notificationStatusDetails);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +42,8 @@ public class NotificationStatusDetailsCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsCustomization();
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomizeAttribute(int count) : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsListCustomization(count);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries;
|
||||
@ -19,37 +20,49 @@ namespace Bit.Core.Test.NotificationCenter.Queries;
|
||||
public class GetNotificationStatusDetailsForUserQueryTest
|
||||
{
|
||||
private static void Setup(SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<INotificationRepository>().GetByUserIdAndStatusAsync(
|
||||
userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter)
|
||||
.Returns(notificationsStatusDetails);
|
||||
sutProvider.GetDependency<INotificationRepository>()
|
||||
.GetByUserIdAndStatusAsync(userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter,
|
||||
pageOptions)
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{
|
||||
Data = notificationsStatusDetails,
|
||||
ContinuationToken = continuationToken
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotLoggedIn_NotFoundException(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null);
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null, pageOptions,
|
||||
continuationToken);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter));
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotificationsFound_Returned(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid());
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid(), pageOptions,
|
||||
continuationToken);
|
||||
|
||||
var actualNotificationsStatusDetails =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter);
|
||||
var actualNotificationsStatusDetailsPagedResult =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions);
|
||||
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetails);
|
||||
Assert.NotNull(actualNotificationsStatusDetailsPagedResult);
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetailsPagedResult.Data);
|
||||
Assert.Equal(continuationToken, actualNotificationsStatusDetailsPagedResult.ContinuationToken);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -11,6 +13,7 @@ using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Stripe;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.OrganizationFeatures.OrganizationLicenses;
|
||||
@ -62,4 +65,34 @@ public class CloudGetOrganizationLicenseQueryTests
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetLicenseAsync_MSPManagedOrganization_UsesProviderSubscription(SutProvider<CloudGetOrganizationLicenseQuery> sutProvider,
|
||||
Organization organization, Guid installationId, Installation installation, SubscriptionInfo subInfo,
|
||||
byte[] licenseSignature, Provider provider)
|
||||
{
|
||||
organization.Status = OrganizationStatusType.Managed;
|
||||
organization.ExpirationDate = null;
|
||||
|
||||
subInfo.Subscription = new SubscriptionInfo.BillingSubscription(new Subscription
|
||||
{
|
||||
CurrentPeriodStart = DateTime.UtcNow,
|
||||
CurrentPeriodEnd = DateTime.UtcNow.AddMonths(1)
|
||||
});
|
||||
|
||||
installation.Enabled = true;
|
||||
sutProvider.GetDependency<IInstallationRepository>().GetByIdAsync(installationId).Returns(installation);
|
||||
sutProvider.GetDependency<IProviderRepository>().GetByOrganizationIdAsync(organization.Id).Returns(provider);
|
||||
sutProvider.GetDependency<IPaymentService>().GetSubscriptionAsync(provider).Returns(subInfo);
|
||||
sutProvider.GetDependency<ILicensingService>().SignLicense(Arg.Any<ILicense>()).Returns(licenseSignature);
|
||||
|
||||
var result = await sutProvider.Sut.GetLicenseAsync(organization, installationId);
|
||||
|
||||
Assert.Equal(LicenseType.Organization, result.LicenseType);
|
||||
Assert.Equal(organization.Id, result.Id);
|
||||
Assert.Equal(installationId, result.InstallationId);
|
||||
Assert.Equal(licenseSignature, result.SignatureBytes);
|
||||
Assert.Equal(DateTime.UtcNow.AddYears(1).Date, result.Expires!.Value.Date);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
@ -48,6 +49,9 @@ public class UpdateOrganizationLicenseCommandTests
|
||||
license.InstallationId = globalSettings.Installation.Id;
|
||||
license.LicenseType = LicenseType.Organization;
|
||||
sutProvider.GetDependency<ILicensingService>().VerifyLicense(license).Returns(true);
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.GetClaimsPrincipalFromLicense(license)
|
||||
.Returns((ClaimsPrincipal)null);
|
||||
|
||||
// Passing values for SelfHostedOrganizationDetails.CanUseLicense
|
||||
// NSubstitute cannot override non-virtual members so we have to ensure the real method passes
|
||||
@ -80,7 +84,9 @@ public class UpdateOrganizationLicenseCommandTests
|
||||
.ReplaceAndUpdateCacheAsync(Arg.Is<Organization>(
|
||||
org => AssertPropertyEqual(license, org,
|
||||
"Id", "MaxStorageGb", "Issued", "Refresh", "Version", "Trial", "LicenseType",
|
||||
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires", "ExpirationWithoutGracePeriod") &&
|
||||
"Hash", "Signature", "SignatureBytes", "InstallationId", "Expires",
|
||||
"ExpirationWithoutGracePeriod", "Token", "LimitCollectionCreationDeletion",
|
||||
"LimitCollectionCreation", "LimitCollectionDeletion", "AllowAdminAccessToAllCollectionItems") &&
|
||||
// Same property but different name, use explicit mapping
|
||||
org.ExpirationDate == license.Expires));
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -9,13 +12,17 @@ using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Billing.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -62,6 +69,9 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.VerifyLicense(userLicense)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<ILicensingService>()
|
||||
.GetClaimsPrincipalFromLicense(userLicense)
|
||||
.Returns((ClaimsPrincipal)null);
|
||||
|
||||
await sutProvider.Sut.UpdateLicenseAsync(user, userLicense);
|
||||
|
||||
@ -231,41 +241,7 @@ public class UserServiceTests
|
||||
});
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IReferenceEventService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
);
|
||||
var sut = RebuildSut(sutProvider);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
|
||||
@ -349,6 +325,262 @@ public class UserServiceTests
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WhenOrganizationHas2FAPolicyEnabled_DisablingAllProviders_RemovesUserFromOrganizationAndSendsEmail(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true }
|
||||
});
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>(), JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(organization.Id, user.Id);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WhenOrganizationHas2FAPolicyEnabled_UserHasOneProviderEnabled_DoesNotRemoveUserFromOrganization(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true },
|
||||
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||
});
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization.Id)
|
||||
.Returns(organization);
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||
}, JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(default, default);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(default, default);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DisableTwoFactorProviderAsync_WithAccountDeprovisioningEnabled_WhenOrganizationHas2FAPolicyEnabled_WhenUserIsManaged_DisablingAllProviders_RemovesOrRevokesUserAndSendsEmail(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization1, Organization organization2)
|
||||
{
|
||||
// Arrange
|
||||
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||
{
|
||||
[TwoFactorProviderType.Email] = new() { Enabled = true }
|
||||
});
|
||||
organization1.Enabled = organization2.Enabled = true;
|
||||
organization1.UseSso = organization2.UseSso = true;
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IPolicyService>()
|
||||
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)
|
||||
.Returns(
|
||||
[
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
},
|
||||
new OrganizationUserPolicyDetails
|
||||
{
|
||||
OrganizationId = organization2.Id,
|
||||
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||
PolicyEnabled = true
|
||||
}
|
||||
]);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization1.Id)
|
||||
.Returns(organization1);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByIdAsync(organization2.Id)
|
||||
.Returns(organization2);
|
||||
sutProvider.GetDependency<IOrganizationRepository>()
|
||||
.GetByVerifiedUserEmailDomainAsync(user.Id)
|
||||
.Returns(new[] { organization1 });
|
||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>(), JsonHelpers.LegacyEnumKeyResolver);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUserRepository>()
|
||||
.Received(1)
|
||||
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.Received(1)
|
||||
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||
|
||||
// Revoke the user from the first organization because they are managed by it
|
||||
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RevokeNonCompliantOrganizationUsersAsync(
|
||||
Arg.Is<RevokeOrganizationUsersRequest>(r => r.OrganizationId == organization1.Id &&
|
||||
r.OrganizationUsers.First().UserId == user.Id &&
|
||||
r.OrganizationUsers.First().OrganizationId == organization1.Id));
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForTwoFactoryPolicyEmailAsync(organization1.DisplayName(), user.Email);
|
||||
|
||||
// Remove the user from the second organization because they are not managed by it
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(organization2.Id, user.Id);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization2.DisplayName(), user.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_UserNull_SendOTPAsyncNotCalled(
|
||||
SutProvider<UserService> sutProvider, string email, string secret)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(null as User);
|
||||
|
||||
await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_SecretNotValid_SendOTPAsyncNotCalled(
|
||||
SutProvider<UserService> sutProvider, string email, string secret)
|
||||
{
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(null as User);
|
||||
|
||||
await sutProvider.Sut.ResendNewDeviceVerificationEmail(email, secret);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ResendNewDeviceVerificationEmail_SendsToken_Success(
|
||||
SutProvider<UserService> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
var testPassword = "test_password";
|
||||
var tokenProvider = SetupFakeTokenProvider(sutProvider, user);
|
||||
SetupUserAndDevice(user, true);
|
||||
|
||||
// Setup the fake password verification
|
||||
var substitutedUserPasswordStore = Substitute.For<IUserPasswordStore<User>>();
|
||||
substitutedUserPasswordStore
|
||||
.GetPasswordHashAsync(user, Arg.Any<CancellationToken>())
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return Task.FromResult("hashed_test_password");
|
||||
});
|
||||
|
||||
sutProvider.SetDependency<IUserStore<User>>(substitutedUserPasswordStore, "store");
|
||||
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>("passwordHasher")
|
||||
.VerifyHashedPassword(user, "hashed_test_password", testPassword)
|
||||
.Returns((ci) =>
|
||||
{
|
||||
return PasswordVerificationResult.Success;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(user.Email)
|
||||
.Returns(user);
|
||||
|
||||
// HACK: SutProvider is being weird about not injecting the IPasswordHasher that I configured
|
||||
var sut = RebuildSut(sutProvider);
|
||||
|
||||
await sut.ResendNewDeviceVerificationEmail(user.Email, testPassword);
|
||||
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOTPEmailAsync(user.Email, Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("")]
|
||||
[BitAutoData("null")]
|
||||
public async Task SendOTPAsync_UserEmailNull_ThrowsBadRequest(
|
||||
string email,
|
||||
SutProvider<UserService> sutProvider, User user)
|
||||
{
|
||||
user.Email = email == "null" ? null : "";
|
||||
var expectedMessage = "No user email.";
|
||||
try
|
||||
{
|
||||
await sutProvider.Sut.SendOTPAsync(user);
|
||||
}
|
||||
catch (BadRequestException ex)
|
||||
{
|
||||
Assert.Equal(ex.Message, expectedMessage);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.DidNotReceive()
|
||||
.SendOTPEmailAsync(Arg.Any<string>(), Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetupUserAndDevice(User user,
|
||||
bool shouldHavePassword)
|
||||
{
|
||||
@ -400,4 +632,44 @@ public class UserServiceTests
|
||||
|
||||
return fakeUserTwoFactorProvider;
|
||||
}
|
||||
|
||||
private IUserService RebuildSut(SutProvider<UserService> sutProvider)
|
||||
{
|
||||
return new UserService(
|
||||
sutProvider.GetDependency<IUserRepository>(),
|
||||
sutProvider.GetDependency<ICipherRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>(),
|
||||
sutProvider.GetDependency<IOrganizationRepository>(),
|
||||
sutProvider.GetDependency<IMailService>(),
|
||||
sutProvider.GetDependency<IPushNotificationService>(),
|
||||
sutProvider.GetDependency<IUserStore<User>>(),
|
||||
sutProvider.GetDependency<IOptions<IdentityOptions>>(),
|
||||
sutProvider.GetDependency<IPasswordHasher<User>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IUserValidator<User>>>(),
|
||||
sutProvider.GetDependency<IEnumerable<IPasswordValidator<User>>>(),
|
||||
sutProvider.GetDependency<ILookupNormalizer>(),
|
||||
sutProvider.GetDependency<IdentityErrorDescriber>(),
|
||||
sutProvider.GetDependency<IServiceProvider>(),
|
||||
sutProvider.GetDependency<ILogger<UserManager<User>>>(),
|
||||
sutProvider.GetDependency<ILicensingService>(),
|
||||
sutProvider.GetDependency<IEventService>(),
|
||||
sutProvider.GetDependency<IApplicationCacheService>(),
|
||||
sutProvider.GetDependency<IDataProtectionProvider>(),
|
||||
sutProvider.GetDependency<IPaymentService>(),
|
||||
sutProvider.GetDependency<IPolicyRepository>(),
|
||||
sutProvider.GetDependency<IPolicyService>(),
|
||||
sutProvider.GetDependency<IReferenceEventService>(),
|
||||
sutProvider.GetDependency<IFido2>(),
|
||||
sutProvider.GetDependency<ICurrentContext>(),
|
||||
sutProvider.GetDependency<IGlobalSettings>(),
|
||||
sutProvider.GetDependency<IAcceptOrgUserCommand>(),
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IFeatureService>(),
|
||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.ReportFeatures.Requests;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.Requests;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
|
@ -0,0 +1,104 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.ReportFeatures;
|
||||
using Bit.Core.Tools.ReportFeatures.Requests;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Tools.ReportFeatures;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeletePasswordHealthReportApplicationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_Success(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
|
||||
// only take one id from the list - we only want to drop one record
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.With(x => x.PasswordHealthReportApplicationIds,
|
||||
passwordHealthReportApplications.Select(x => x.Id).Take(1).ToList())
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(passwordHealthReportApplications);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.DeleteAsync(Arg.Is<PasswordHealthReportApplication>(_ =>
|
||||
request.PasswordHealthReportApplicationIds.Contains(_.Id)));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withValidRequest_nothingToDrop(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
var passwordHealthReportApplications = fixture.CreateMany<PasswordHealthReportApplication>(2).ToList();
|
||||
// we are passing invalid data
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.With(x => x.PasswordHealthReportApplicationIds, new List<Guid> { Guid.NewGuid() })
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(passwordHealthReportApplications);
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(0)
|
||||
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DropPasswordHealthReportApplicationAsync_withNodata_fails(
|
||||
SutProvider<DropPasswordHealthReportApplicationCommand> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var fixture = new Fixture();
|
||||
// we are passing invalid data
|
||||
var request = fixture.Build<DropPasswordHealthReportApplicationRequest>()
|
||||
.Create();
|
||||
|
||||
sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.GetByOrganizationIdAsync(Arg.Any<Guid>())
|
||||
.Returns(null as List<PasswordHealthReportApplication>);
|
||||
|
||||
// Act
|
||||
await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.DropPasswordHealthReportApplicationAsync(request));
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(1)
|
||||
.GetByOrganizationIdAsync(request.OrganizationId);
|
||||
|
||||
await sutProvider.GetDependency<IPasswordHealthReportApplicationRepository>()
|
||||
.Received(0)
|
||||
.DeleteAsync(Arg.Any<PasswordHealthReportApplication>());
|
||||
}
|
||||
}
|
25
test/Core.Test/Vault/AutoFixture/SecurityTaskFixtures.cs
Normal file
25
test/Core.Test/Vault/AutoFixture/SecurityTaskFixtures.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Enums;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.Vault.AutoFixture;
|
||||
|
||||
public class SecurityTaskFixtures : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<SecurityTask>(composer =>
|
||||
composer
|
||||
.With(task => task.Id, Guid.NewGuid())
|
||||
.With(task => task.OrganizationId, Guid.NewGuid())
|
||||
.With(task => task.Status, SecurityTaskStatus.Pending)
|
||||
.Without(x => x.CipherId)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public class SecurityTaskCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new SecurityTaskFixtures();
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
#nullable enable
|
||||
using System.Security.Claims;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Test.Vault.AutoFixture;
|
||||
using Bit.Core.Vault.Authorization;
|
||||
using Bit.Core.Vault.Commands;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Vault.Commands;
|
||||
|
||||
[SutProviderCustomize]
|
||||
[SecurityTaskCustomize]
|
||||
public class MarkTaskAsCompletedCommandTest
|
||||
{
|
||||
private static void Setup(SutProvider<MarkTaskAsCompletedCommand> sutProvider, Guid taskId, SecurityTask? securityTask, Guid? userId, bool authorizedUpdate = false)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<ISecurityTaskRepository>()
|
||||
.GetByIdAsync(taskId)
|
||||
.Returns(securityTask);
|
||||
sutProvider.GetDependency<IAuthorizationService>()
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), securityTask ?? Arg.Any<SecurityTask>(),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs =>
|
||||
reqs.Contains(SecurityTaskOperations.Update)))
|
||||
.Returns(authorizedUpdate ? AuthorizationResult.Success() : AuthorizationResult.Failed());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_NotLoggedIn_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, null, true);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_TaskNotFound_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId)
|
||||
{
|
||||
Setup(sutProvider, taskId, null, Guid.NewGuid(), true);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_AuthorizationFailed_NotFoundException(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, Guid.NewGuid());
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CompleteAsync(taskId));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task CompleteAsync_Success(
|
||||
SutProvider<MarkTaskAsCompletedCommand> sutProvider,
|
||||
Guid taskId,
|
||||
SecurityTask securityTask)
|
||||
{
|
||||
Setup(sutProvider, taskId, securityTask, Guid.NewGuid(), true);
|
||||
|
||||
await sutProvider.Sut.CompleteAsync(taskId);
|
||||
|
||||
await sutProvider.GetDependency<ISecurityTaskRepository>().Received(1).ReplaceAsync(securityTask);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Vault.Models.Data;
|
||||
using Bit.Core.Vault.Queries;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Vault.Queries;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class OrganizationCiphersQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetOrganizationCiphersInCollections_ReturnsFilteredCiphers(
|
||||
Guid organizationId, SutProvider<OrganizationCiphersQuery> sutProvider)
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
|
||||
var otherCollectionId = Guid.NewGuid();
|
||||
var targetCollectionId = Guid.NewGuid();
|
||||
|
||||
var otherCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var targetCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var bothCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
var noCipher = fixture.Create<CipherOrganizationDetails>();
|
||||
|
||||
var ciphers = new List<CipherOrganizationDetails>
|
||||
{
|
||||
otherCipher, // not in the target collection
|
||||
targetCipher, // in the target collection
|
||||
bothCipher, // in both collections
|
||||
noCipher // not in any collection
|
||||
};
|
||||
ciphers.ForEach(c =>
|
||||
{
|
||||
c.OrganizationId = organizationId;
|
||||
c.UserId = null;
|
||||
});
|
||||
|
||||
var otherCollectionCipher = new CollectionCipher
|
||||
{
|
||||
CollectionId = otherCollectionId,
|
||||
CipherId = otherCipher.Id
|
||||
};
|
||||
var targetCollectionCipher = new CollectionCipher
|
||||
{
|
||||
CollectionId = targetCollectionId,
|
||||
CipherId = targetCipher.Id
|
||||
};
|
||||
var bothCollectionCipher1 = new CollectionCipher
|
||||
{
|
||||
CollectionId = targetCollectionId,
|
||||
CipherId = bothCipher.Id
|
||||
};
|
||||
var bothCollectionCipher2 = new CollectionCipher
|
||||
{
|
||||
CollectionId = otherCollectionId,
|
||||
CipherId = bothCipher.Id
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<ICipherRepository>().GetManyOrganizationDetailsByOrganizationIdAsync(organizationId)
|
||||
.Returns(ciphers);
|
||||
|
||||
sutProvider.GetDependency<ICollectionCipherRepository>().GetManyByOrganizationIdAsync(organizationId).Returns(
|
||||
[
|
||||
targetCollectionCipher,
|
||||
otherCollectionCipher,
|
||||
bothCollectionCipher1,
|
||||
bothCollectionCipher2
|
||||
]);
|
||||
|
||||
var result = await sutProvider
|
||||
.Sut
|
||||
.GetOrganizationCiphersByCollectionIds(organizationId, [targetCollectionId]);
|
||||
result = result.ToList();
|
||||
|
||||
Assert.Equal(2, result.Count());
|
||||
Assert.Contains(result, c =>
|
||||
c.Id == targetCipher.Id &&
|
||||
c.CollectionIds.Count() == 1 &&
|
||||
c.CollectionIds.Any(cId => cId == targetCollectionId));
|
||||
Assert.Contains(result, c =>
|
||||
c.Id == bothCipher.Id &&
|
||||
c.CollectionIds.Count() == 2 &&
|
||||
c.CollectionIds.Any(cId => cId == targetCollectionId) &&
|
||||
c.CollectionIds.Any(cId => cId == otherCollectionId));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user