mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-10319] - Revoke Non Complaint Users for 2FA and Single Org Policy Enablement (#5037)
- Revoking users when enabling single org and 2fa policies. - Updated emails sent when users are revoked via 2FA or Single Organization policy enablement Co-authored-by: Matt Bishop <mbishop@bitwarden.com> Co-authored-by: Rui Tomé <108268980+r-tome@users.noreply.github.com>
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
@ -28,7 +29,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 +59,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 +87,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 +122,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 +163,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 +177,14 @@ public class VerifyOrganizationDomainCommandTests
|
||||
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.UserId.Returns(userId);
|
||||
|
||||
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyService>()
|
||||
.Received(1)
|
||||
.SaveAsync(Arg.Is<Policy>(x => x.Type == PolicyType.SingleOrg && x.OrganizationId == domain.OrganizationId && x.Enabled), null);
|
||||
.SaveAsync(Arg.Is<Policy>(x => x.Type == PolicyType.SingleOrg && x.OrganizationId == domain.OrganizationId && x.Enabled), userId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -176,6 +199,9 @@ 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);
|
||||
@ -189,7 +215,7 @@ public class VerifyOrganizationDomainCommandTests
|
||||
|
||||
[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,6 +225,9 @@ 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);
|
||||
@ -223,6 +252,9 @@ 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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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,79 @@ 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
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
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
|
||||
{
|
||||
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.Any<RevokeOrganizationUsersRequest>());
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRevokedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@example.com");
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RemovesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
@ -116,6 +191,13 @@ public class SingleOrgPolicyValidatorTests
|
||||
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>()
|
||||
@ -126,4 +208,73 @@ public class SingleOrgPolicyValidatorTests
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@example.com");
|
||||
}
|
||||
|
||||
[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,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
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
|
||||
{
|
||||
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()
|
||||
.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,
|
||||
|
Reference in New Issue
Block a user