1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-27 07:42:15 -05:00

WIP remove flag

This commit is contained in:
Thomas Rittson 2025-04-21 11:33:18 +10:00
parent 4379e326a5
commit 4ffeab7921
No known key found for this signature in database
GPG Key ID: CDDDA03861C35E27
17 changed files with 37 additions and 335 deletions

View File

@ -89,7 +89,7 @@ public class UsersController : Controller
var ciphers = await _cipherRepository.GetManyByUserIdAsync(id); var ciphers = await _cipherRepository.GetManyByUserIdAsync(id);
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user); var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
var verifiedDomain = await AccountDeprovisioningEnabled(user.Id); var verifiedDomain = await _userService.IsClaimedByAnyOrganizationAsync(user.Id);
return View(UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers, verifiedDomain)); return View(UserViewModel.MapViewModel(user, isTwoFactorEnabled, ciphers, verifiedDomain));
} }
@ -106,7 +106,7 @@ public class UsersController : Controller
var billingInfo = await _paymentService.GetBillingAsync(user); var billingInfo = await _paymentService.GetBillingAsync(user);
var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user); var billingHistoryInfo = await _paymentService.GetBillingHistoryAsync(user);
var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user); var isTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
var verifiedDomain = await AccountDeprovisioningEnabled(user.Id); var verifiedDomain = await _userService.IsClaimedByAnyOrganizationAsync(user.Id);
var deviceVerificationRequired = await _userService.ActiveNewDeviceVerificationException(user.Id); var deviceVerificationRequired = await _userService.ActiveNewDeviceVerificationException(user.Id);
return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain, deviceVerificationRequired)); return View(new UserEditModel(user, isTwoFactorEnabled, ciphers, billingInfo, billingHistoryInfo, _globalSettings, verifiedDomain, deviceVerificationRequired));
@ -179,12 +179,4 @@ public class UsersController : Controller
await _userService.ToggleNewDeviceVerificationException(user.Id); await _userService.ToggleNewDeviceVerificationException(user.Id);
return RedirectToAction("Edit", new { id }); return RedirectToAction("Edit", new { id });
} }
// TODO: Feature flag to be removed in PM-14207
private async Task<bool?> AccountDeprovisioningEnabled(Guid userId)
{
return _featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
? await _userService.IsClaimedByAnyOrganizationAsync(userId)
: null;
}
} }

View File

@ -575,7 +575,6 @@ public class OrganizationUsersController : Controller
new OrganizationUserBulkResponseModel(r.OrganizationUserId, r.ErrorMessage))); new OrganizationUserBulkResponseModel(r.OrganizationUserId, r.ErrorMessage)));
} }
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
[HttpDelete("{id}/delete-account")] [HttpDelete("{id}/delete-account")]
[HttpPost("{id}/delete-account")] [HttpPost("{id}/delete-account")]
public async Task DeleteAccount(Guid orgId, Guid id) public async Task DeleteAccount(Guid orgId, Guid id)
@ -594,7 +593,6 @@ public class OrganizationUsersController : Controller
await _deleteClaimedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id); await _deleteClaimedOrganizationUserAccountCommand.DeleteUserAsync(orgId, id, currentUser.Id);
} }
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
[HttpDelete("delete-account")] [HttpDelete("delete-account")]
[HttpPost("delete-account")] [HttpPost("delete-account")]
public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeleteAccount(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model) public async Task<ListResponseModel<OrganizationUserBulkResponseModel>> BulkDeleteAccount(Guid orgId, [FromBody] OrganizationUserBulkRequestModel model)
@ -719,11 +717,6 @@ public class OrganizationUsersController : Controller
private async Task<IDictionary<Guid, bool>> GetClaimedByOrganizationStatusAsync(Guid orgId, IEnumerable<Guid> userIds) private async Task<IDictionary<Guid, bool>> GetClaimedByOrganizationStatusAsync(Guid orgId, IEnumerable<Guid> userIds)
{ {
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
{
return userIds.ToDictionary(kvp => kvp, kvp => false);
}
var usersOrganizationClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgId, userIds); var usersOrganizationClaimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgId, userIds);
return usersOrganizationClaimedStatus; return usersOrganizationClaimedStatus;
} }

View File

@ -279,8 +279,7 @@ public class OrganizationsController : Controller
throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving."); throw new BadRequestException("Your organization's Single Sign-On settings prevent you from leaving.");
} }
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) if ((await _userService.GetOrganizationsClaimingUserAsync(user.Id)).Any(x => x.Id == id))
&& (await _userService.GetOrganizationsClaimingUserAsync(user.Id)).Any(x => x.Id == id))
{ {
throw new BadRequestException("Claimed user account cannot leave claiming organization. Contact your organization administrator for additional details."); throw new BadRequestException("Claimed user account cannot leave claiming organization. Contact your organization administrator for additional details.");
} }

View File

@ -2,7 +2,6 @@
using Bit.Api.AdminConsole.Models.Response.Helpers; using Bit.Api.AdminConsole.Models.Response.Helpers;
using Bit.Api.AdminConsole.Models.Response.Organizations; using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Api.Models.Response; using Bit.Api.Models.Response;
using Bit.Core;
using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
@ -79,7 +78,7 @@ public class PoliciesController : Controller
return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type }); return new PolicyDetailResponseModel(new Policy { Type = (PolicyType)type });
} }
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && policy.Type is PolicyType.SingleOrg) if (policy.Type is PolicyType.SingleOrg)
{ {
return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery); return await policy.GetSingleOrgPolicyDetailResponseAsync(_organizationHasVerifiedDomainsQuery);
} }

View File

@ -560,9 +560,8 @@ public class AccountsController : Controller
} }
else else
{ {
// If Account Deprovisioning is enabled, we need to check if the user is claimed by any organization. // Check if the user is claimed by any organization.
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) if (await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
&& await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
{ {
throw new BadRequestException("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details."); throw new BadRequestException("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details.");
} }

View File

@ -1086,9 +1086,8 @@ public class CiphersController : Controller
throw new BadRequestException(ModelState); throw new BadRequestException(ModelState);
} }
// If Account Deprovisioning is enabled, we need to check if the user is claimed by any organization. // Check if the user is claimed by any organization.
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) if (await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
&& await _userService.IsClaimedByAnyOrganizationAsync(user.Id))
{ {
throw new BadRequestException("Cannot purge accounts owned by an organization. Contact your organization administrator for additional details."); throw new BadRequestException("Cannot purge accounts owned by an organization. Contact your organization administrator for additional details.");
} }

View File

@ -125,11 +125,8 @@ public class VerifyOrganizationDomainCommand(
private async Task DomainVerificationSideEffectsAsync(OrganizationDomain domain, IActingUser actingUser) private async Task DomainVerificationSideEffectsAsync(OrganizationDomain domain, IActingUser actingUser)
{ {
if (featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser);
{ await SendVerifiedDomainUserEmailAsync(domain);
await EnableSingleOrganizationPolicyAsync(domain.OrganizationId, actingUser);
await SendVerifiedDomainUserEmailAsync(domain);
}
} }
private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) => private async Task EnableSingleOrganizationPolicyAsync(Guid organizationId, IActingUser actingUser) =>

View File

@ -159,7 +159,7 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
throw new BadRequestException(RemoveAdminByCustomUserErrorMessage); throw new BadRequestException(RemoveAdminByCustomUserErrorMessage);
} }
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null) if (deletingUserId.HasValue && eventSystemUser == null)
{ {
var claimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgUser.OrganizationId, new[] { orgUser.Id }); var claimedStatus = await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(orgUser.OrganizationId, new[] { orgUser.Id });
if (claimedStatus.TryGetValue(orgUser.Id, out var isClaimed) && isClaimed) if (claimedStatus.TryGetValue(orgUser.Id, out var isClaimed) && isClaimed)
@ -214,7 +214,7 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId); deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
} }
var claimedStatus = _featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null var claimedStatus = deletingUserId.HasValue && eventSystemUser == null
? await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(organizationId, filteredUsers.Select(u => u.Id)) ? await _getOrganizationUsersClaimedStatusQuery.GetUsersOrganizationClaimedStatusAsync(organizationId, filteredUsers.Select(u => u.Id))
: filteredUsers.ToDictionary(u => u.Id, u => false); : filteredUsers.ToDictionary(u => u.Id, u => false);
var result = new List<(OrganizationUser OrganizationUser, string ErrorMessage)>(); var result = new List<(OrganizationUser OrganizationUser, string ErrorMessage)>();

View File

@ -61,16 +61,9 @@ public class SingleOrgPolicyValidator : IPolicyValidator
{ {
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
{ {
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) var currentUser = _currentContext.UserId ?? Guid.Empty;
{ var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
var currentUser = _currentContext.UserId ?? Guid.Empty; await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
}
else
{
await RemoveNonCompliantUsersAsync(policyUpdate.OrganizationId);
}
} }
} }
@ -165,8 +158,7 @@ public class SingleOrgPolicyValidator : IPolicyValidator
return validateDecryptionErrorMessage; return validateDecryptionErrorMessage;
} }
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) if (await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId))
&& await _organizationHasVerifiedDomainsQuery.HasVerifiedDomainsAsync(policyUpdate.OrganizationId))
{ {
return ClaimedDomainSingleOrganizationRequiredErrorMessage; return ClaimedDomainSingleOrganizationRequiredErrorMessage;
} }

View File

@ -56,16 +56,9 @@ public class TwoFactorAuthenticationPolicyValidator : IPolicyValidator
{ {
if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true }) if (currentPolicy is not { Enabled: true } && policyUpdate is { Enabled: true })
{ {
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) var currentUser = _currentContext.UserId ?? Guid.Empty;
{ var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
var currentUser = _currentContext.UserId ?? Guid.Empty; await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
var isOwnerOrProvider = await _currentContext.OrganizationOwner(policyUpdate.OrganizationId);
await RevokeNonCompliantUsersAsync(policyUpdate.OrganizationId, policyUpdate.PerformedBy ?? new StandardUser(currentUser, isOwnerOrProvider));
}
else
{
await RemoveNonCompliantUsersAsync(policyUpdate.OrganizationId);
}
} }
} }

View File

@ -93,16 +93,8 @@ public class OrganizationDomainService : IOrganizationDomainService
//Send email to administrators //Send email to administrators
if (adminEmails.Count > 0) if (adminEmails.Count > 0)
{ {
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) await _mailService.SendUnclaimedOrganizationDomainEmailAsync(adminEmails,
{ domain.OrganizationId.ToString(), domain.DomainName);
await _mailService.SendUnclaimedOrganizationDomainEmailAsync(adminEmails,
domain.OrganizationId.ToString(), domain.DomainName);
}
else
{
await _mailService.SendUnverifiedOrganizationDomainEmailAsync(adminEmails,
domain.OrganizationId.ToString(), domain.DomainName);
}
} }
_logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName); _logger.LogInformation(Constants.BypassFiltersEventId, "Expired domain: {domainName}", domain.DomainName);

View File

@ -1374,11 +1374,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
public async Task<IEnumerable<Organization>> GetOrganizationsClaimingUserAsync(Guid userId) public async Task<IEnumerable<Organization>> GetOrganizationsClaimingUserAsync(Guid userId)
{ {
if (!_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning))
{
return Enumerable.Empty<Organization>();
}
// Get all organizations that have verified the user's email domain. // Get all organizations that have verified the user's email domain.
var organizationsWithVerifiedUserEmailDomain = await _organizationRepository.GetByVerifiedUserEmailDomainAsync(userId); var organizationsWithVerifiedUserEmailDomain = await _organizationRepository.GetByVerifiedUserEmailDomainAsync(userId);
@ -1443,22 +1438,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
var removeOrgUserTasks = twoFactorPolicies.Select(async p => var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
{ {
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId); var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)) await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
{ new RevokeOrganizationUsersRequest(
await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync( p.OrganizationId,
new RevokeOrganizationUsersRequest( [new OrganizationUserUserDetails { Id = p.OrganizationUserId, OrganizationId = p.OrganizationId }],
p.OrganizationId, new SystemUser(EventSystemUser.TwoFactorDisabled)));
[new OrganizationUserUserDetails { Id = p.OrganizationUserId, OrganizationId = p.OrganizationId }], await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
new SystemUser(EventSystemUser.TwoFactorDisabled)));
await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
}
else
{
await _removeOrganizationUserCommand.RemoveUserAsync(p.OrganizationId, user.Id);
await _mailService.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(
organization.DisplayName(), user.Email);
}
}).ToArray(); }).ToArray();
await Task.WhenAll(removeOrgUserTasks); await Task.WhenAll(removeOrgUserTasks);

View File

@ -238,20 +238,13 @@ public class OrganizationUsersControllerTests
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Invite(organizationAbility.Id, model)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.Invite(organizationAbility.Id, model));
} }
[Theory] [Theory, BitAutoData]
[BitAutoData(true)]
[BitAutoData(false)]
public async Task Get_ReturnsUser( public async Task Get_ReturnsUser(
bool accountDeprovisioningEnabled,
OrganizationUserUserDetails organizationUser, ICollection<CollectionAccessSelection> collections, OrganizationUserUserDetails organizationUser, ICollection<CollectionAccessSelection> collections,
SutProvider<OrganizationUsersController> sutProvider) SutProvider<OrganizationUsersController> sutProvider)
{ {
organizationUser.Permissions = null; organizationUser.Permissions = null;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(accountDeprovisioningEnabled);
sutProvider.GetDependency<ICurrentContext>() sutProvider.GetDependency<ICurrentContext>()
.ManageUsers(organizationUser.OrganizationId) .ManageUsers(organizationUser.OrganizationId)
.Returns(true); .Returns(true);
@ -267,8 +260,8 @@ public class OrganizationUsersControllerTests
var response = await sutProvider.Sut.Get(organizationUser.Id, false); var response = await sutProvider.Sut.Get(organizationUser.Id, false);
Assert.Equal(organizationUser.Id, response.Id); Assert.Equal(organizationUser.Id, response.Id);
Assert.Equal(accountDeprovisioningEnabled, response.ManagedByOrganization); Assert.True(response.ManagedByOrganization);
Assert.Equal(accountDeprovisioningEnabled, response.ClaimedByOrganization); Assert.True(response.ClaimedByOrganization);
} }
[Theory] [Theory]

View File

@ -140,7 +140,6 @@ public class OrganizationsControllerTests : IDisposable
_currentContext.OrganizationUser(orgId).Returns(true); _currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization> { null }); _userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization> { null });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId)); var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
@ -170,7 +169,6 @@ public class OrganizationsControllerTests : IDisposable
_currentContext.OrganizationUser(orgId).Returns(true); _currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization> { { foundOrg } }); _userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization> { { foundOrg } });
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId)); var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
@ -205,7 +203,6 @@ public class OrganizationsControllerTests : IDisposable
_currentContext.OrganizationUser(orgId).Returns(true); _currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig); _ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user); _userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization>()); _userService.GetOrganizationsClaimingUserAsync(user.Id).Returns(new List<Organization>());
await _sut.Leave(orgId); await _sut.Leave(orgId);

View File

@ -7,7 +7,6 @@ using Bit.Api.Auth.Models.Request.WebAuthn;
using Bit.Api.KeyManagement.Validators; using Bit.Api.KeyManagement.Validators;
using Bit.Api.Tools.Models.Request; using Bit.Api.Tools.Models.Request;
using Bit.Api.Vault.Models.Request; using Bit.Api.Vault.Models.Request;
using Bit.Core;
using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
@ -189,21 +188,6 @@ public class AccountsControllerTests : IDisposable
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default); await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
} }
[Fact]
public async Task PostEmail_WithAccountDeprovisioningEnabled_WhenUserIsNotManagedByAnOrganization_ShouldChangeUserEmail()
{
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user);
_userService.ChangeEmailAsync(user, default, default, default, default, default)
.Returns(Task.FromResult(IdentityResult.Success));
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.IsClaimedByAnyOrganizationAsync(user.Id).Returns(false);
await _sut.PostEmail(new EmailRequestModel());
await _userService.Received(1).ChangeEmailAsync(user, default, default, default, default, default);
}
[Fact] [Fact]
public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException() public async Task PostEmail_WhenNotAuthorized_ShouldThrownUnauthorizedAccessException()
{ {
@ -533,12 +517,11 @@ public class AccountsControllerTests : IDisposable
} }
[Fact] [Fact]
public async Task Delete_WhenAccountDeprovisioningIsEnabled_WithUserManagedByAnOrganization_ThrowsBadRequestException() public async Task Delete_WithUserManagedByAnOrganization_ThrowsBadRequestException()
{ {
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user); ConfigureUserServiceToAcceptPasswordFor(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.IsClaimedByAnyOrganizationAsync(user.Id).Returns(true); _userService.IsClaimedByAnyOrganizationAsync(user.Id).Returns(true);
var result = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Delete(new SecretVerificationRequestModel())); var result = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Delete(new SecretVerificationRequestModel()));
@ -547,12 +530,11 @@ public class AccountsControllerTests : IDisposable
} }
[Fact] [Fact]
public async Task Delete_WhenAccountDeprovisioningIsEnabled_WithUserNotManagedByAnOrganization_ShouldSucceed() public async Task Delete_WithUserNotManagedByAnOrganization_ShouldSucceed()
{ {
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user); ConfigureUserServiceToAcceptPasswordFor(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.IsClaimedByAnyOrganizationAsync(user.Id).Returns(false); _userService.IsClaimedByAnyOrganizationAsync(user.Id).Returns(false);
_userService.DeleteAsync(user).Returns(IdentityResult.Success); _userService.DeleteAsync(user).Returns(IdentityResult.Success);

View File

@ -166,7 +166,7 @@ public class VerifyOrganizationDomainCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenSingleOrgPolicyShouldBeEnabled( public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsVerified_ThenSingleOrgPolicyShouldBeEnabled(
OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider) OrganizationDomain domain, Guid userId, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
sutProvider.GetDependency<IOrganizationDomainRepository>() sutProvider.GetDependency<IOrganizationDomainRepository>()
@ -177,10 +177,6 @@ public class VerifyOrganizationDomainCommandTests
.ResolveAsync(domain.DomainName, domain.Txt) .ResolveAsync(domain.DomainName, domain.Txt)
.Returns(true); .Returns(true);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<ICurrentContext>() sutProvider.GetDependency<ICurrentContext>()
.UserId.Returns(userId); .UserId.Returns(userId);
@ -222,7 +218,7 @@ public class VerifyOrganizationDomainCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled( public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldNotBeEnabled(
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider) OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{ {
sutProvider.GetDependency<IOrganizationDomainRepository>() sutProvider.GetDependency<IOrganizationDomainRepository>()
@ -236,10 +232,6 @@ public class VerifyOrganizationDomainCommandTests
sutProvider.GetDependency<ICurrentContext>() sutProvider.GetDependency<ICurrentContext>()
.UserId.Returns(Guid.NewGuid()); .UserId.Returns(Guid.NewGuid());
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
_ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain); _ = await sutProvider.Sut.UserVerifyOrganizationDomainAsync(domain);
await sutProvider.GetDependency<ISavePolicyCommand>() await sutProvider.GetDependency<ISavePolicyCommand>()
@ -248,33 +240,7 @@ public class VerifyOrganizationDomainCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningDisabled_WhenDomainIsNotVerified_ThenSingleOrgPolicyShouldBeNotBeEnabled( public async Task UserVerifyOrganizationDomainAsync_WhenDomainIsVerified_ThenEmailShouldBeSentToUsersWhoBelongToTheDomain(
OrganizationDomain domain, SutProvider<VerifyOrganizationDomainCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationDomainRepository>()
.GetClaimedDomainsByDomainNameAsync(domain.DomainName)
.Returns([]);
sutProvider.GetDependency<IDnsResolverService>()
.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<ISavePolicyCommand>()
.DidNotReceive()
.SaveAsync(Arg.Any<PolicyUpdate>());
}
[Theory, BitAutoData]
public async Task UserVerifyOrganizationDomainAsync_GivenOrganizationDomainWithAccountDeprovisioningEnabled_WhenDomainIsVerified_ThenEmailShouldBeSentToUsersWhoBelongToTheDomain(
ICollection<OrganizationUserUserDetails> organizationUsers, ICollection<OrganizationUserUserDetails> organizationUsers,
OrganizationDomain domain, OrganizationDomain domain,
Organization organization, Organization organization,
@ -306,10 +272,6 @@ public class VerifyOrganizationDomainCommandTests
sutProvider.GetDependency<ICurrentContext>() sutProvider.GetDependency<ICurrentContext>()
.UserId.Returns(Guid.NewGuid()); .UserId.Returns(Guid.NewGuid());
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>() sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByOrganizationAsync(domain.OrganizationId) .GetManyDetailsByOrganizationAsync(domain.OrganizationId)
.Returns(mockedUsers); .Returns(mockedUsers);

View File

@ -40,43 +40,6 @@ public class RemoveOrganizationUserCommandTests
// Act // Act
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId); await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
// Assert
await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.DidNotReceiveWithAnyArgs()
.GetUsersOrganizationClaimedStatusAsync(default, default);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.DeleteAsync(organizationUser);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
}
[Theory, BitAutoData]
public async Task RemoveUser_WithDeletingUserId_WithAccountDeprovisioningEnabled_Success(
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
// Arrange
organizationUser.OrganizationId = deletingUser.OrganizationId;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(deletingUser.Id)
.Returns(deletingUser);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(deletingUser.OrganizationId)
.Returns(true);
// Act
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
// Assert // Assert
await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>() await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.Received(1) .Received(1)
@ -235,15 +198,12 @@ public class RemoveOrganizationUserCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task RemoveUserAsync_WithDeletingUserId_WithAccountDeprovisioningEnabled_WhenUserIsManaged_ThrowsException( public async Task RemoveUserAsync_WithDeletingUserId_WhenUserIsManaged_ThrowsException(
[OrganizationUser(status: OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser, [OrganizationUser(status: OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser,
Guid deletingUserId, Guid deletingUserId,
SutProvider<RemoveOrganizationUserCommand> sutProvider) SutProvider<RemoveOrganizationUserCommand> sutProvider)
{ {
// Arrange // Arrange
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>() sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(orgUser.Id) .GetByIdAsync(orgUser.Id)
.Returns(orgUser); .Returns(orgUser);
@ -285,34 +245,6 @@ public class RemoveOrganizationUserCommandTests
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser); .LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
} }
[Theory, BitAutoData]
public async Task RemoveUser_WithEventSystemUser_WithAccountDeprovisioningEnabled_Success(
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
EventSystemUser eventSystemUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
// Arrange
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUser.Id)
.Returns(organizationUser);
// Act
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser);
// Assert
await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.DidNotReceiveWithAnyArgs()
.GetUsersOrganizationClaimedStatusAsync(default, default);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.DeleteAsync(organizationUser);
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
}
[Theory] [Theory]
[BitAutoData] [BitAutoData]
public async Task RemoveUser_WithEventSystemUser_NotFound_ThrowsException( public async Task RemoveUser_WithEventSystemUser_NotFound_ThrowsException(
@ -474,7 +406,6 @@ public class RemoveOrganizationUserCommandTests
var sutProvider = SutProviderFactory(); var sutProvider = SutProviderFactory();
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime; var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId; orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 }; var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id); var organizationUserIds = organizationUsers.Select(u => u.Id);
@ -499,60 +430,6 @@ public class RemoveOrganizationUserCommandTests
// Act // Act
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId); var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
// Assert
Assert.Equal(2, result.Count());
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.DidNotReceiveWithAnyArgs()
.GetUsersOrganizationClaimedStatusAsync(default, default);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventsAsync(
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, DateTime? DateTime)>>(i =>
i.First().OrganizationUser.Id == orgUser1.Id
&& i.Last().OrganizationUser.Id == orgUser2.Id
&& i.All(u => u.DateTime == eventDate)));
}
[Theory, BitAutoData]
public async Task RemoveUsers_WithDeletingUserId_WithAccountDeprovisioningEnabled_Success(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2)
{
// Arrange
var sutProvider = SutProviderFactory();
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(default)
.ReturnsForAnyArgs(organizationUsers);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(deletingUser.Id)
.Returns(deletingUser);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(deletingUser.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.GetUsersOrganizationClaimedStatusAsync(
deletingUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)))
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, false }, { orgUser2.Id, false } });
// Act
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
// Assert // Assert
Assert.Equal(2, result.Count()); Assert.Equal(2, result.Count());
Assert.All(result, r => Assert.Empty(r.ErrorMessage)); Assert.All(result, r => Assert.Empty(r.ErrorMessage));
@ -638,7 +515,7 @@ public class RemoveOrganizationUserCommandTests
} }
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task RemoveUsers_WithDeletingUserId_RemovingClaimedUser_WithAccountDeprovisioningEnabled_ThrowsException( public async Task RemoveUsers_WithDeletingUserId_RemovingClaimedUser_ThrowsException(
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser, [OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser,
OrganizationUser deletingUser, OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider) SutProvider<RemoveOrganizationUserCommand> sutProvider)
@ -646,10 +523,6 @@ public class RemoveOrganizationUserCommandTests
// Arrange // Arrange
orgUser.OrganizationId = deletingUser.OrganizationId; orgUser.OrganizationId = deletingUser.OrganizationId;
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>() sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id))) .GetManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id)))
.Returns(new[] { orgUser }); .Returns(new[] { orgUser });
@ -739,51 +612,6 @@ public class RemoveOrganizationUserCommandTests
&& u.DateTime == eventDate))); && u.DateTime == eventDate)));
} }
[Theory, BitAutoData]
public async Task RemoveUsers_WithEventSystemUser_WithAccountDeprovisioningEnabled_Success(
EventSystemUser eventSystemUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
OrganizationUser orgUser2)
{
// Arrange
var sutProvider = SutProviderFactory();
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
orgUser1.OrganizationId = orgUser2.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
.Returns(true);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync(default)
.ReturnsForAnyArgs(organizationUsers);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(orgUser1.OrganizationId, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
// Act
var result = await sutProvider.Sut.RemoveUsersAsync(orgUser1.OrganizationId, organizationUserIds, eventSystemUser);
// Assert
Assert.Equal(2, result.Count());
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
await sutProvider.GetDependency<IGetOrganizationUsersClaimedStatusQuery>()
.DidNotReceiveWithAnyArgs()
.GetUsersOrganizationClaimedStatusAsync(default, default);
await sutProvider.GetDependency<IOrganizationUserRepository>()
.Received(1)
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
await sutProvider.GetDependency<IEventService>()
.Received(1)
.LogOrganizationUserEventsAsync(
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, EventSystemUser EventSystemUser, DateTime? DateTime)>>(
i => i.First().OrganizationUser.Id == orgUser1.Id
&& i.Last().OrganizationUser.Id == orgUser2.Id
&& i.All(u => u.EventSystemUser == eventSystemUser
&& u.DateTime == eventDate)));
}
[Theory, BitAutoData] [Theory, BitAutoData]
public async Task RemoveUsers_WithEventSystemUser_WithMismatchingOrganizationId_ThrowsException( public async Task RemoveUsers_WithEventSystemUser_WithMismatchingOrganizationId_ThrowsException(
EventSystemUser eventSystemUser, EventSystemUser eventSystemUser,