Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions src/Api/Auth/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class AccountsController : Controller
private readonly IUserService _userService;
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommandV1 _setInitialMasterPasswordCommandV1;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IFinishSsoJitProvisionMasterPasswordCommand _finishSsoJitProvisionMasterPasswordCommand;
private readonly ITdeSetPasswordCommand _tdeSetPasswordCommand;
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
Expand All @@ -55,7 +55,7 @@ public AccountsController(
IProviderUserRepository providerUserRepository,
IUserService userService,
IPolicyService policyService,
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
IFinishSsoJitProvisionMasterPasswordCommand finishSsoJitProvisionMasterPasswordCommand,
ISetInitialMasterPasswordCommandV1 setInitialMasterPasswordCommandV1,
ITdeSetPasswordCommand tdeSetPasswordCommand,
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
Expand All @@ -72,7 +72,7 @@ IUserRepository userRepository
_providerUserRepository = providerUserRepository;
_userService = userService;
_policyService = policyService;
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
_finishSsoJitProvisionMasterPasswordCommand = finishSsoJitProvisionMasterPasswordCommand;
_setInitialMasterPasswordCommandV1 = setInitialMasterPasswordCommandV1;
_tdeSetPasswordCommand = tdeSetPasswordCommand;
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
Expand Down Expand Up @@ -230,7 +230,7 @@ public async Task PostSetPasswordAsync([FromBody] SetInitialPasswordRequestModel
}
else
{
await _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(user, model.ToData());
await _finishSsoJitProvisionMasterPasswordCommand.FinishProvisionAsync(user, model.ToData());
}
}
else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Bit.Core.Auth.UserFeatures.UserMasterPassword;

public class SetInitialMasterPasswordCommand : ISetInitialMasterPasswordCommand
public class FinishSsoJitProvisionMasterPasswordCommand : IFinishSsoJitProvisionMasterPasswordCommand
{
private readonly IUserService _userService;
private readonly IUserRepository _userRepository;
Expand All @@ -20,7 +20,7 @@ public class SetInitialMasterPasswordCommand : ISetInitialMasterPasswordCommand
private readonly IPasswordHasher<User> _passwordHasher;
private readonly IEventService _eventService;

public SetInitialMasterPasswordCommand(IUserService userService, IUserRepository userRepository,
public FinishSsoJitProvisionMasterPasswordCommand(IUserService userService, IUserRepository userRepository,
IAcceptOrgUserCommand acceptOrgUserCommand, IOrganizationUserRepository organizationUserRepository,
IOrganizationRepository organizationRepository, IPasswordHasher<User> passwordHasher,
IEventService eventService)
Expand All @@ -34,7 +34,7 @@ public SetInitialMasterPasswordCommand(IUserService userService, IUserRepository
_eventService = eventService;
}

public async Task SetInitialMasterPasswordAsync(User user,
public async Task FinishProvisionAsync(User user,
SetInitialMasterPasswordDataModel masterPasswordDataModel)
{
if (user.Key != null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ο»Ώusing Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
using Bit.Core.Exceptions;

namespace Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;

/// <summary>
/// <para>Finalizes onboarding for an organization user by setting their initial master password and account keys,
/// then accepting their organization membership.</para>
/// <para>Applies to organizations configured with Single Sign-On (SSO) and master password decryption,
/// where just-in-time (JIT) provisioned users are required to establish a master password upon first SSO login.</para>
/// </summary>
public interface IFinishSsoJitProvisionMasterPasswordCommand
{
/// <summary>
/// Sets the initial master password and account keys for the specified user and accepts their pending
/// organization membership.
/// </summary>
/// <param name="user">User to finalize onboarding for. Must not already have a master password set.</param>
/// <param name="masterPasswordDataModel">Master password, account keys, and org SSO identifier</param>
/// <returns>A task that completes when the operation succeeds</returns>
/// <exception cref="BadRequestException">
/// Thrown if the user's master password is already set, account keys are missing, the organization
/// SSO identifier is invalid, or the user is not a member of the organization.
/// </exception>
public Task FinishProvisionAsync(User user, SetInitialMasterPasswordDataModel masterPasswordDataModel);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
/// to ensure their ability to reset other users' accounts.</para>
/// </summary>
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27327
[Obsolete("Use ISetInitialMasterPasswordCommand instead")]
[Obsolete("Use IFinishSsoJitProvisionMasterPasswordCommand instead")]
public interface ISetInitialMasterPasswordCommandV1
{
public Task<IdentityResult> SetInitialMasterPasswordAsync(User user, string masterPassword, string key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public static void AddUserKeyCommands(this IServiceCollection services, IGlobalS

private static void AddUserPasswordCommands(this IServiceCollection services)
{
services.AddScoped<ISetInitialMasterPasswordCommand, SetInitialMasterPasswordCommand>();
services.AddScoped<IFinishSsoJitProvisionMasterPasswordCommand, FinishSsoJitProvisionMasterPasswordCommand>();
services.AddScoped<ISetInitialMasterPasswordCommandV1, SetInitialMasterPasswordCommandV1>();
services.AddScoped<ITdeSetPasswordCommand, TdeSetPasswordCommand>();
}
Expand Down
14 changes: 7 additions & 7 deletions test/Api.Test/Auth/Controllers/AccountsControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class AccountsControllerTests : IDisposable
private readonly IUserService _userService;
private readonly IProviderUserRepository _providerUserRepository;
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IFinishSsoJitProvisionMasterPasswordCommand _finishSsoJitProvisionMasterPasswordCommand;
private readonly ISetInitialMasterPasswordCommandV1 _setInitialMasterPasswordCommandV1;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly ITdeSetPasswordCommand _tdeSetPasswordCommand;
Expand All @@ -52,7 +52,7 @@ public AccountsControllerTests()
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_providerUserRepository = Substitute.For<IProviderUserRepository>();
_policyService = Substitute.For<IPolicyService>();
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
_finishSsoJitProvisionMasterPasswordCommand = Substitute.For<IFinishSsoJitProvisionMasterPasswordCommand>();
_setInitialMasterPasswordCommandV1 = Substitute.For<ISetInitialMasterPasswordCommandV1>();
_twoFactorIsEnabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
_tdeSetPasswordCommand = Substitute.For<ITdeSetPasswordCommand>();
Expand All @@ -69,7 +69,7 @@ public AccountsControllerTests()
_providerUserRepository,
_userService,
_policyService,
_setInitialMasterPasswordCommand,
_finishSsoJitProvisionMasterPasswordCommand,
_setInitialMasterPasswordCommandV1,
_tdeSetPasswordCommand,
_tdeOffboardingPasswordCommand,
Expand Down Expand Up @@ -870,15 +870,15 @@ public async Task PostSetPasswordAsync_V2_WhenUserExistsAndSettingPasswordSuccee
// Arrange
UpdateSetInitialPasswordRequestModelToV2(setInitialPasswordRequestModel);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(user, Arg.Any<SetInitialMasterPasswordDataModel>())
_finishSsoJitProvisionMasterPasswordCommand.FinishProvisionAsync(user, Arg.Any<SetInitialMasterPasswordDataModel>())
.Returns(Task.CompletedTask);

// Act
await _sut.PostSetPasswordAsync(setInitialPasswordRequestModel);

// Assert
await _setInitialMasterPasswordCommand.Received(1)
.SetInitialMasterPasswordAsync(
await _finishSsoJitProvisionMasterPasswordCommand.Received(1)
.FinishProvisionAsync(
Arg.Is<User>(u => u == user),
Arg.Is<SetInitialMasterPasswordDataModel>(d =>
d.MasterPasswordAuthentication != null &&
Expand Down Expand Up @@ -935,7 +935,7 @@ public async Task PostSetPasswordAsync_V2_WhenSettingPasswordFails_ShouldThrowEx
// Arrange
UpdateSetInitialPasswordRequestModelToV2(setInitialPasswordRequestModel);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(Task.FromResult(user));
_setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(user, Arg.Any<SetInitialMasterPasswordDataModel>())
_finishSsoJitProvisionMasterPasswordCommand.FinishProvisionAsync(user, Arg.Any<SetInitialMasterPasswordDataModel>())
.Returns(Task.FromException(new Exception("Setting password failed")));

// Act & Assert
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
namespace Bit.Core.Test.Auth.UserFeatures.UserMasterPassword;

[SutProviderCustomize]
public class SetInitialMasterPasswordCommandTests
public class FinishSsoJitProvisionMasterPasswordCommandTests
{
[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_Success(SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_Success(SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings,
Organization org, OrganizationUser orgUser, string serverSideHash, string masterPasswordHint)
{
Expand All @@ -49,7 +49,7 @@ public async Task SetInitialMasterPassword_Success(SutProvider<SetInitialMasterP
.Returns(mockUpdateUserData);

// Act
await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model);
await sutProvider.Sut.FinishProvisionAsync(user, model);

// Assert
await sutProvider.GetDependency<IUserRepository>().Received(1)
Expand All @@ -72,8 +72,8 @@ await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(1)

[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_UserAlreadyHasPassword_ThrowsBadRequestException(
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_UserAlreadyHasPassword_ThrowsBadRequestException(
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings, string orgSsoIdentifier, string masterPasswordHint)
{
// Arrange
Expand All @@ -82,14 +82,14 @@ public async Task SetInitialMasterPassword_UserAlreadyHasPassword_ThrowsBadReque

// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model));
async () => await sutProvider.Sut.FinishProvisionAsync(user, model));
Assert.Equal("User already has a master password set.", exception.Message);
}

[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_AccountKeysNull_ThrowsBadRequestException(
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_AccountKeysNull_ThrowsBadRequestException(
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, KdfSettings kdfSettings, string orgSsoIdentifier, string masterPasswordHint)
{
// Arrange
Expand All @@ -98,17 +98,17 @@ public async Task SetInitialMasterPassword_AccountKeysNull_ThrowsBadRequestExcep

// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model));
async () => await sutProvider.Sut.FinishProvisionAsync(user, model));
Assert.Equal("Account keys are required.", exception.Message);
}

[Theory]
[BitAutoData("wrong-salt", null)]
[BitAutoData([null, "wrong-salt"])]
[BitAutoData("wrong-salt", "different-wrong-salt")]
public async Task SetInitialMasterPassword_InvalidSalt_ThrowsBadRequestException(
public async Task FinishProvisionAsync_InvalidSalt_ThrowsBadRequestException(
string? authSaltOverride, string? unlockSaltOverride,
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings, string orgSsoIdentifier, string masterPasswordHint)
{
// Arrange
Expand All @@ -135,14 +135,14 @@ public async Task SetInitialMasterPassword_InvalidSalt_ThrowsBadRequestException

// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model));
async () => await sutProvider.Sut.FinishProvisionAsync(user, model));
Assert.Equal("Invalid master password salt.", exception.Message);
}

[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_NullSalt_UsesEmailFallback(
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_NullSalt_UsesEmailFallback(
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings,
Organization org, OrganizationUser orgUser, string serverSideHash, string masterPasswordHint)
{
Expand Down Expand Up @@ -174,7 +174,7 @@ public async Task SetInitialMasterPassword_NullSalt_UsesEmailFallback(
.Returns(mockUpdateUserData);

// Act β€” should not throw since email fallback provides a valid salt
await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model);
await sutProvider.Sut.FinishProvisionAsync(user, model);

// Assert
await sutProvider.GetDependency<IEventService>().Received(1)
Expand All @@ -183,8 +183,8 @@ await sutProvider.GetDependency<IEventService>().Received(1)

[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_InvalidOrgSsoIdentifier_ThrowsBadRequestException(
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_InvalidOrgSsoIdentifier_ThrowsBadRequestException(
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings, string orgSsoIdentifier, string masterPasswordHint)
{
// Arrange
Expand All @@ -197,14 +197,14 @@ public async Task SetInitialMasterPassword_InvalidOrgSsoIdentifier_ThrowsBadRequ

// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model));
async () => await sutProvider.Sut.FinishProvisionAsync(user, model));
Assert.Equal("Organization SSO identifier is invalid.", exception.Message);
}

[Theory]
[BitAutoData]
public async Task SetInitialMasterPassword_UserNotFoundInOrganization_ThrowsBadRequestException(
SutProvider<SetInitialMasterPasswordCommand> sutProvider,
public async Task FinishProvisionAsync_UserNotFoundInOrganization_ThrowsBadRequestException(
SutProvider<FinishSsoJitProvisionMasterPasswordCommand> sutProvider,
User user, UserAccountKeysData accountKeys, KdfSettings kdfSettings, Organization org, string masterPasswordHint)
{
// Arrange
Expand All @@ -221,7 +221,7 @@ public async Task SetInitialMasterPassword_UserNotFoundInOrganization_ThrowsBadR

// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
async () => await sutProvider.Sut.SetInitialMasterPasswordAsync(user, model));
async () => await sutProvider.Sut.FinishProvisionAsync(user, model));
Assert.Equal("User not found within organization.", exception.Message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ public async Task CreateAsync_ShouldSetMasterPasswordSaltToNullWhenNoMasterPassw
/// <summary>
/// In this test we are testing that the MasterPasswordUnlockData set's the password data correctly.
/// including setting the masterPasswordSalt.
/// <see cref="TdeSetPasswordCommand.SetMasterPasswordAsync"/> and <see cref="SetInitialMasterPasswordCommand.SetInitialMasterPasswordAsync"/> for reference.
/// <see cref="TdeSetPasswordCommand.SetMasterPasswordAsync"/> and <see cref="FinishSsoJitProvisionMasterPasswordCommand.FinishProvisionAsync"/> for reference.
/// </summary>
[Theory, DatabaseData]
public async Task UpdateMasterPassword_MasterPasswordSaltIsUpdated(
Expand Down
Loading