mirror of
https://github.com/bitwarden/server.git
synced 2024-12-01 13:43:23 +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
e0e24db445
commit
0a1238f887
@ -580,6 +580,13 @@ public class AccountsController : Controller
|
|||||||
}
|
}
|
||||||
else
|
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);
|
var result = await _userService.DeleteAsync(user);
|
||||||
if (result.Succeeded)
|
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,
|
ProductTierType productTier,
|
||||||
IEnumerable<ProductType> products);
|
IEnumerable<ProductType> products);
|
||||||
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
Task SendVerifyDeleteEmailAsync(string email, Guid userId, string token);
|
||||||
|
Task SendCannotDeleteManagedAccountEmailAsync(string email);
|
||||||
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail);
|
||||||
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
|
Task SendChangeEmailEmailAsync(string newEmailAddress, string token);
|
||||||
Task SendTwoFactorEmailAsync(string email, string token);
|
Task SendTwoFactorEmailAsync(string email, string token);
|
||||||
|
@ -112,6 +112,19 @@ public class HandlebarsMailService : IMailService
|
|||||||
await _mailDeliveryService.SendEmailAsync(message);
|
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)
|
public async Task SendChangeEmailAlreadyExistsEmailAsync(string fromEmail, string toEmail)
|
||||||
{
|
{
|
||||||
var message = CreateDefaultMessage("Your Email Change", toEmail);
|
var message = CreateDefaultMessage("Your Email Change", toEmail);
|
||||||
|
@ -297,6 +297,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (await IsManagedByAnyOrganizationAsync(user.Id))
|
||||||
|
{
|
||||||
|
await _mailService.SendCannotDeleteManagedAccountEmailAsync(user.Email);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
var token = await base.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "DeleteAccount");
|
||||||
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
await _mailService.SendVerifyDeleteEmailAsync(user.Email, user.Id, token);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,11 @@ public class NoopMailService : IMailService
|
|||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SendCannotDeleteManagedAccountEmailAsync(string email)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
|
public Task SendPasswordlessSignInAsync(string returnUrl, string token, string email)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
|
@ -534,6 +534,34 @@ public class AccountsControllerTests : IDisposable
|
|||||||
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostSetPasswordAsync(model));
|
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
|
// Below are helper functions that currently belong to this
|
||||||
// test class, but ultimately may need to be split out into
|
// test class, but ultimately may need to be split out into
|
||||||
|
Loading…
Reference in New Issue
Block a user