1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 20:50:21 -05:00

Ac/pm 17449/add managed user validation to email token (#5437)

This commit is contained in:
Jimmy Vo 2025-02-24 10:42:04 -05:00
parent aeee9c2e91
commit 94b7d0268f
No known key found for this signature in database
GPG Key ID: 7CB834D6F4FFCA11
5 changed files with 52 additions and 11 deletions

View File

@ -149,6 +149,12 @@ public class AccountsController : Controller
throw new BadRequestException("MasterPasswordHash", "Invalid password."); throw new BadRequestException("MasterPasswordHash", "Invalid password.");
} }
var managedUserValidationResult = await _userService.ValidateManagedUserDomainAsync(user, model.NewEmail);
if (!managedUserValidationResult.Succeeded)
{
throw new BadRequestException(managedUserValidationResult.Errors);
}
await _userService.InitiateEmailChangeAsync(user, model.NewEmail); await _userService.InitiateEmailChangeAsync(user, model.NewEmail);
} }
@ -167,7 +173,6 @@ public class AccountsController : Controller
throw new BadRequestException("You cannot change your email when using Key Connector."); throw new BadRequestException("You cannot change your email when using Key Connector.");
} }
var result = await _userService.ChangeEmailAsync(user, model.MasterPasswordHash, model.NewEmail, var result = await _userService.ChangeEmailAsync(user, model.MasterPasswordHash, model.NewEmail,
model.NewMasterPasswordHash, model.Token, model.Key); model.NewMasterPasswordHash, model.Token, model.Key);
if (result.Succeeded) if (result.Succeeded)

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Bit.Core.Exceptions; namespace Bit.Core.Exceptions;
@ -29,5 +30,16 @@ public class BadRequestException : Exception
ModelState = modelState; ModelState = modelState;
} }
public BadRequestException(IEnumerable<IdentityError> identityErrors)
: base("The model state is invalid.")
{
ModelState = new ModelStateDictionary();
foreach (var error in identityErrors)
{
ModelState.AddModelError(error.Code, error.Description);
}
}
public ModelStateDictionary ModelState { get; set; } public ModelStateDictionary ModelState { get; set; }
} }

View File

@ -136,6 +136,16 @@ public interface IUserService
/// </returns> /// </returns>
Task<bool> IsManagedByAnyOrganizationAsync(Guid userId); Task<bool> IsManagedByAnyOrganizationAsync(Guid userId);
/// <summary>
/// Verify whether the new email domain meets the requirements for managed users.
/// </summary>
/// <remarks>
/// </remarks>
/// <returns>
/// IdentityResult
/// </returns>
Task<IdentityResult> ValidateManagedUserDomainAsync(User user, string newEmail);
/// <summary> /// <summary>
/// Gets the organizations that manage the user. /// Gets the organizations that manage the user.
/// </summary> /// </summary>

View File

@ -545,7 +545,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
} }
var managedUserValidationResult = await ValidateManagedUserAsync(user, newEmail); var managedUserValidationResult = await ValidateManagedUserDomainAsync(user, newEmail);
if (!managedUserValidationResult.Succeeded) if (!managedUserValidationResult.Succeeded)
{ {
@ -617,7 +617,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return IdentityResult.Success; return IdentityResult.Success;
} }
private async Task<IdentityResult> ValidateManagedUserAsync(User user, string newEmail) public async Task<IdentityResult> ValidateManagedUserDomainAsync(User user, string newEmail)
{ {
var managingOrganizations = await GetOrganizationsManagingUserAsync(user.Id); var managingOrganizations = await GetOrganizationsManagingUserAsync(user.Id);

View File

@ -134,29 +134,43 @@ public class AccountsControllerTests : IDisposable
[Fact] [Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange() public async Task PostEmailToken_ShouldInitiateEmailChange()
{ {
// Arrange
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user); ConfigureUserServiceToAcceptPasswordFor(user);
var newEmail = "example@user.com"; const string newEmail = "example@user.com";
_userService.ValidateManagedUserDomainAsync(user, newEmail).Returns(IdentityResult.Success);
// Act
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail }); await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail });
// Assert
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail); await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
} }
[Fact] [Fact]
public async Task PostEmailToken_WithAccountDeprovisioningEnabled_WhenUserIsNotManagedByAnOrganization_ShouldInitiateEmailChange() public async Task PostEmailToken_WhenValidateManagedUserDomainAsyncFails_ShouldReturnError()
{ {
// Arrange
var user = GenerateExampleUser(); var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(user); ConfigureUserServiceToReturnValidPrincipalFor(user);
ConfigureUserServiceToAcceptPasswordFor(user); ConfigureUserServiceToAcceptPasswordFor(user);
_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true);
_userService.IsManagedByAnyOrganizationAsync(user.Id).Returns(false);
var newEmail = "example@user.com";
await _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail }); const string newEmail = "example@user.com";
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail); _userService.ValidateManagedUserDomainAsync(user, newEmail)
.Returns(IdentityResult.Failed(new IdentityError
{
Code = "TestFailure",
Description = "This is a test."
}));
// Act
// Assert
await Assert.ThrowsAsync<BadRequestException>(
() => _sut.PostEmailToken(new EmailTokenRequestModel { NewEmail = newEmail })
);
} }
[Fact] [Fact]