1
0
mirror of https://github.com/bitwarden/server.git synced 2025-03-12 13:29:14 +01: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 committed by GitHub
parent 6bc579f51e
commit 6ca98df721
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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.");
}
var managedUserValidationResult = await _userService.ValidateManagedUserDomainAsync(user, model.NewEmail);
if (!managedUserValidationResult.Succeeded)
{
throw new BadRequestException(managedUserValidationResult.Errors);
}
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.");
}
var result = await _userService.ChangeEmailAsync(user, model.MasterPasswordHash, model.NewEmail,
model.NewMasterPasswordHash, model.Token, model.Key);
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;
@ -29,5 +30,16 @@ public class BadRequestException : Exception
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; }
}

View File

@ -136,6 +136,16 @@ public interface IUserService
/// </returns>
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>
/// Gets the organizations that manage the user.
/// </summary>

View File

@ -545,7 +545,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
var managedUserValidationResult = await ValidateManagedUserAsync(user, newEmail);
var managedUserValidationResult = await ValidateManagedUserDomainAsync(user, newEmail);
if (!managedUserValidationResult.Succeeded)
{
@ -617,7 +617,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
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);

View File

@ -134,29 +134,43 @@ public class AccountsControllerTests : IDisposable
[Fact]
public async Task PostEmailToken_ShouldInitiateEmailChange()
{
// Arrange
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(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 });
// Assert
await _userService.Received(1).InitiateEmailChangeAsync(user, newEmail);
}
[Fact]
public async Task PostEmailToken_WithAccountDeprovisioningEnabled_WhenUserIsNotManagedByAnOrganization_ShouldInitiateEmailChange()
public async Task PostEmailToken_WhenValidateManagedUserDomainAsyncFails_ShouldReturnError()
{
// Arrange
var user = GenerateExampleUser();
ConfigureUserServiceToReturnValidPrincipalFor(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]