mirror of
https://github.com/bitwarden/server.git
synced 2024-11-21 12:05:42 +01:00
[PM-11406] Account Management: Prevent a verified user from deleting their account (#4878)
* Add check for managed user before purging account * Rename IOrganizationRepository.GetByClaimedUserDomainAsync to GetByVerifiedUserEmailDomainAsync and refactor to return a list. Remove ManagedByOrganizationId from ProfileResponseMode. Add ManagesActiveUser to ProfileOrganizationResponseModel * Rename the property ManagesActiveUser to UserIsManagedByOrganization * Remove whole class #nullable enable and add it to specific places * [PM-11405] Account Deprovisioning: Prevent a verified user from changing their email address * Remove unnecessary .ToList() * Refactor IUserService methods GetOrganizationsManagingUserAsync and IsManagedByAnyOrganizationAsync to not return nullable objects. Update ProfileOrganizationResponseModel.UserIsManagedByOrganization to not be nullable * Prevent deletion of accounts managed by an organization when Account Deprovisioning is enabled * Add CannotDeleteManagedAccountViewModel and email templates - Added CannotDeleteManagedAccountViewModel class to handle emails related to preventing deletion of accounts managed by an organization. - Added HTML and text email templates for sending notifications about the inability to delete an account owned by an organization. - Updated IMailService interface with a new method to send the cannot delete managed account email. - Implemented the SendCannotDeleteManagedAccountEmailAsync method in HandlebarsMailService. - Added a check in UserService to send the cannot delete managed account email if the user is managed by any organization. - Added a no-op implementation for SendCannotDeleteManagedAccountEmailAsync in NoopMailService. * Update error message when unable to purge vault for managed account * Update error message when unable to change email for managed account * Update error message when unable to delete account when managed by organization * Update error message in test for deleting organization-owned accounts
This commit is contained in:
parent
a2654ce2ee
commit
4b76008245
@ -580,6 +580,13 @@ public class AccountsController : Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
// If Account Deprovisioning is enabled, we need to check if the user is managed by any organization.
|
||||
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||
&& await _userService.IsManagedByAnyOrganizationAsync(user.Id))
|
||||
{
|
||||
throw new BadRequestException("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details.");
|
||||
}
|
||||
|
||||
var result = await _userService.DeleteAsync(user);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
|
@ -0,0 +1,7 @@
|
||||
using Bit.Core.Models.Mail;
|
||||
|
||||
namespace Bit.Core.Auth.Models.Mail;
|
||||
|
||||
public class CannotDeleteManagedAccountViewModel : BaseMailModel
|
||||
{
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
{{#>FullHtmlLayout}}
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0 0 10px; -webkit-text-size-adjust: none; text-align: left;" valign="top" align="center">
|
||||
You have requested to delete your account. This action cannot be completed because your account is owned by an organization.
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;">
|
||||
<td class="content-block last" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 16px; color: #333; line-height: 25px; margin: 0; -webkit-font-smoothing: antialiased; padding: 0; -webkit-text-size-adjust: none; text-align: left;" valign="top" align="center">
|
||||
Please contact your organization administrator for additional details.
|
||||
<br style="margin: 0; box-sizing: border-box; color: #333; line-height: 25px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none;" />
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{/FullHtmlLayout}}
|
@ -0,0 +1,6 @@
|
||||
{{#>BasicTextLayout}}
|
||||
You have requested to delete your account. This action cannot be completed because your account is owned by an organization.
|
||||
|
||||
Please contact your organization administrator for additional details.
|
||||
|
||||
{{/BasicTextLayout}}
|
@ -18,6 +18,7 @@ public interface IMailService
|
||||
ProductTierType productTier,
|
||||
IEnumerable<ProductType> products);
|
||||
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
||||
Task SendCannotDeleteManagedAccountEmailAsync(string email);
|
||||
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
||||
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
|
||||
Task SendTwoFactorEmailAsync(string email, string token);
|
||||
|
@ -112,6 +112,19 @@ public class HandlebarsMailService : IMailService
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendCannotDeleteManagedAccountEmailAsync(string email)
|
||||
{
|
||||
var message = CreateDefaultMessage("Delete Your Account", email);
|
||||
var model = new CannotDeleteManagedAccountViewModel
|
||||
{
|
||||
WebVaultUrl = _globalSettings.BaseServiceUri.VaultWithHash,
|
||||
SiteName = _globalSettings.SiteName,
|
||||
};
|
||||
await AddMessageContentAsync(message, "AdminConsole.CannotDeleteManagedAccount", model);
|
||||
message.Category = "CannotDeleteManagedAccount";
|
||||
await _mailDeliveryService.SendEmailAsync(message);
|
||||
}
|
||||
|
||||
public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail)
|
||||
{
|
||||
var message = CreateDefaultMessage("Your Email Change", toEmail);
|
||||
|
@ -297,6 +297,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (await IsManagedByAnyOrganizationAsync(user.Id))
|
||||
{
|
||||
await _mailService.SendCannotDeleteManagedAccountEmailAsync(user.Email);
|
||||
return;
|
||||
}
|
||||
|
||||
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
||||
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ public class NoopMailService : IMailService
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendCannotDeleteManagedAccountEmailAsync(string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
|
||||
{
|
||||
return Task.FromResult(0);
|
||||
|
@ -534,6 +534,34 @@ public class AccountsControllerTests : IDisposable
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostSetPasswordAsync(model));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_WhenAccountDeprovisioningIsEnabled_WithUserManagedByAnOrganization_ThrowsBadRequestException()
|
||||
{
|
||||
var user = GenerateExampleUser();
|
||||
ConfigureUserServiceToReturnValidPrincipalFor(user);
|
||||
ConfigureUserServiceToAcceptPasswordFor(user);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
_userService.IsManagedByAnyOrganizationAsync(user.Id).Returns(true);
|
||||
|
||||
var result = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Delete(new SecretVerificationRequestModel()));
|
||||
|
||||
Assert.Equal("Cannot delete accounts owned by an organization. Contact your organization administrator for additional details.", result.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Delete_WhenAccountDeprovisioningIsEnabled_WithUserNotManagedByAnOrganization_ShouldSucceed()
|
||||
{
|
||||
var user = GenerateExampleUser();
|
||||
ConfigureUserServiceToReturnValidPrincipalFor(user);
|
||||
ConfigureUserServiceToAcceptPasswordFor(user);
|
||||
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
|
||||
_userService.IsManagedByAnyOrganizationAsync(user.Id).Returns(false);
|
||||
_userService.DeleteAsync(user).Returns(IdentityResult.Success);
|
||||
|
||||
await _sut.Delete(new SecretVerificationRequestModel());
|
||||
|
||||
await _userService.Received(1).DeleteAsync(user);
|
||||
}
|
||||
|
||||
// Below are helper functions that currently belong to this
|
||||
// test class, but ultimately may need to be split out into
|
||||
|
Loading…
Reference in New Issue
Block a user