Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
63 changes: 45 additions & 18 deletions src/Api/Auth/Controllers/AccountsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.TdeOnboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Enums;
Expand All @@ -38,7 +39,9 @@ public class AccountsController : Controller
private readonly IProviderUserRepository _providerUserRepository;
private readonly IUserService _userService;
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommandV1 _setInitialMasterPasswordCommandV1;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly ITdeOnboardingPasswordCommand _tdeOnboardingPasswordCommand;
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly IFeatureService _featureService;
Expand All @@ -54,6 +57,8 @@ public AccountsController(
IUserService userService,
IPolicyService policyService,
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
ISetInitialMasterPasswordCommandV1 setInitialMasterPasswordCommandV1,
ITdeOnboardingPasswordCommand tdeOnboardingPasswordCommand,
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IFeatureService featureService,
Expand All @@ -69,6 +74,8 @@ IUserRepository userRepository
_userService = userService;
_policyService = policyService;
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
_setInitialMasterPasswordCommandV1 = setInitialMasterPasswordCommandV1;
_tdeOnboardingPasswordCommand = tdeOnboardingPasswordCommand;
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
_featureService = featureService;
Expand Down Expand Up @@ -208,41 +215,61 @@ public async Task PostPassword([FromBody] PasswordRequestModel model)
}

[HttpPost("set-password")]
public async Task PostSetPasswordAsync([FromBody] SetPasswordRequestModel model)
public async Task PostSetPasswordAsync([FromBody] SetInitialPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
throw new UnauthorizedAccessException();
}

try
if (model.IsV2Request())
{
user = model.ToUser(user);
await _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(user, model.ToData());
}
catch (Exception e)
else
{
ModelState.AddModelError(string.Empty, e.Message);
throw new BadRequestException(ModelState);
}
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27327
try
{
user = model.ToUser(user);
}
catch (Exception e)
{
ModelState.AddModelError(string.Empty, e.Message);
throw new BadRequestException(ModelState);
}

var result = await _setInitialMasterPasswordCommand.SetInitialMasterPasswordAsync(
user,
model.MasterPasswordHash,
model.Key,
model.OrgIdentifier);
var result = await _setInitialMasterPasswordCommandV1.SetInitialMasterPasswordAsync(
user,
model.MasterPasswordHash,
model.Key,
model.OrgIdentifier);

if (result.Succeeded)
{
return;
if (result.Succeeded)
{
return;
}

foreach (var error in result.Errors)
{
ModelState.AddModelError(string.Empty, error.Description);
}

throw new BadRequestException(ModelState);
}
}

foreach (var error in result.Errors)
[HttpPost("tde-onboard-password")]
public async Task PostTdeOnboardPasswordAsync([FromBody] TdeOnboardPasswordRequestModel model)
{
var user = await _userService.GetUserByPrincipalAsync(User);
if (user == null)
{
ModelState.AddModelError(string.Empty, error.Description);
throw new UnauthorizedAccessException();
}

throw new BadRequestException(ModelState);
await _tdeOnboardingPasswordCommand.OnboardMasterPasswordAsync(user, model.ToData());
}

[HttpPost("verify-password")]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
๏ปฟusing System.ComponentModel.DataAnnotations;
using Bit.Api.KeyManagement.Models.Requests;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.KeyManagement.Models.Api.Request;
using Bit.Core.Utilities;

namespace Bit.Api.Auth.Models.Request.Accounts;

public class SetInitialPasswordRequestModel : IValidatableObject
{
// TODO will be removed with https://bitwarden.atlassian.net/browse/PM-27327
[Obsolete("Use MasterPasswordAuthentication instead")]
[StringLength(300)]
public string? MasterPasswordHash { get; set; }

[Obsolete("Use MasterPasswordUnlock instead")]
public string? Key { get; set; }

[Obsolete("Use AccountKeys instead")]
public KeysRequestModel? Keys { get; set; }

[Obsolete("Use MasterPasswordAuthentication instead")]
public KdfType? Kdf { get; set; }

[Obsolete("Use MasterPasswordAuthentication instead")]
public int? KdfIterations { get; set; }

[Obsolete("Use MasterPasswordAuthentication instead")]
public int? KdfMemory { get; set; }

[Obsolete("Use MasterPasswordAuthentication instead")]
public int? KdfParallelism { get; set; }

public MasterPasswordAuthenticationDataRequestModel? MasterPasswordAuthentication { get; set; }
public MasterPasswordUnlockDataRequestModel? MasterPasswordUnlock { get; set; }
public AccountKeysRequestModel? AccountKeys { get; set; }

[StringLength(50)]
public string? MasterPasswordHint { get; set; }

[Required]
public required string OrgIdentifier { get; set; }

// TODO removed with https://bitwarden.atlassian.net/browse/PM-27327
public User ToUser(User existingUser)
{
existingUser.MasterPasswordHint = MasterPasswordHint;
existingUser.Kdf = Kdf!.Value;
existingUser.KdfIterations = KdfIterations!.Value;
existingUser.KdfMemory = KdfMemory;
existingUser.KdfParallelism = KdfParallelism;
existingUser.Key = Key;
Keys?.ToUser(existingUser);
return existingUser;
}

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (IsV2Request())
{
// V2 registration

// Validate Kdf
var authenticationKdf = MasterPasswordAuthentication!.Kdf.ToData();
var unlockKdf = MasterPasswordUnlock!.Kdf.ToData();

// Currently, KDF settings are not saved separately for authentication and unlock and must therefore be equal
if (!authenticationKdf.Equals(unlockKdf))
{
throw new BadRequestException("KDF settings must be equal for authentication and unlock.");
}

var authenticationValidationErrors = KdfSettingsValidator.Validate(authenticationKdf).ToList();
if (authenticationValidationErrors.Count != 0)
{
yield return authenticationValidationErrors.First();
}

var unlockValidationErrors = KdfSettingsValidator.Validate(unlockKdf).ToList();
if (unlockValidationErrors.Count != 0)
{
yield return unlockValidationErrors.First();
}

yield break;
}

// V1 registration
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27327
if (string.IsNullOrEmpty(MasterPasswordHash))
{
yield return new ValidationResult("MasterPasswordHash must be supplied.");
}

if (string.IsNullOrEmpty(Key))
{
yield return new ValidationResult("Key must be supplied.");
}

if (Kdf == null)
{
yield return new ValidationResult("Kdf must be supplied.");
}

if (KdfIterations == null)
{
yield return new ValidationResult("KdfIterations must be supplied.");
}

if (Kdf == KdfType.Argon2id)
{
if (KdfMemory == null)
{
yield return new ValidationResult("KdfMemory must be supplied when Kdf is Argon2id.");
}

if (KdfParallelism == null)
{
yield return new ValidationResult("KdfParallelism must be supplied when Kdf is Argon2id.");
}
}

var validationErrors = KdfSettingsValidator
.Validate(Kdf!.Value, KdfIterations!.Value, KdfMemory, KdfParallelism).ToList();
if (validationErrors.Count != 0)
{
yield return validationErrors.First();
}
}

public bool IsV2Request()
{
return MasterPasswordAuthentication != null && MasterPasswordUnlock != null && AccountKeys != null;
}

public SetInitialMasterPasswordDataModel ToData()
{
return new SetInitialMasterPasswordDataModel
{
MasterPasswordAuthentication = MasterPasswordAuthentication!.ToData(),
MasterPasswordUnlock = MasterPasswordUnlock!.ToData(),
OrgSsoIdentifier = OrgIdentifier,
AccountKeys = AccountKeys!.ToAccountKeysData(),
MasterPasswordHint = MasterPasswordHint
};
}
}
40 changes: 0 additions & 40 deletions src/Api/Auth/Models/Request/Accounts/SetPasswordRequestModel.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
๏ปฟusing System.ComponentModel.DataAnnotations;
using Bit.Api.KeyManagement.Models.Requests;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Exceptions;
using Bit.Core.Utilities;

namespace Bit.Api.Auth.Models.Request.Accounts;

public class TdeOnboardPasswordRequestModel : IValidatableObject
{
public required MasterPasswordAuthenticationDataRequestModel MasterPasswordAuthentication { get; set; }
public required MasterPasswordUnlockDataRequestModel MasterPasswordUnlock { get; set; }

[StringLength(50)]
public string? MasterPasswordHint { get; set; }

[Required]
public required string OrgIdentifier { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
// Validate Kdf
var authenticationKdf = MasterPasswordAuthentication.Kdf.ToData();
var unlockKdf = MasterPasswordUnlock.Kdf.ToData();

// Currently, KDF settings are not saved separately for authentication and unlock and must therefore be equal
if (!authenticationKdf.Equals(unlockKdf))
{
throw new BadRequestException("KDF settings must be equal for authentication and unlock.");
}

var authenticationValidationErrors = KdfSettingsValidator.Validate(authenticationKdf).ToList();
if (authenticationValidationErrors.Count != 0)
{
yield return authenticationValidationErrors.First();
}

var unlockValidationErrors = KdfSettingsValidator.Validate(unlockKdf).ToList();
if (unlockValidationErrors.Count != 0)
{
yield return unlockValidationErrors.First();
}
}

public TdeOnboardMasterPasswordDataModel ToData()
{
return new TdeOnboardMasterPasswordDataModel
{
MasterPasswordAuthentication = MasterPasswordAuthentication.ToData(),
MasterPasswordUnlock = MasterPasswordUnlock.ToData(),
OrgSsoIdentifier = OrgIdentifier,
MasterPasswordHint = MasterPasswordHint
};
}
}
22 changes: 22 additions & 0 deletions src/Core/Auth/Models/Data/SetInitialMasterPasswordDataModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
๏ปฟusing Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.KeyManagement.Models.Data;

namespace Bit.Core.Auth.Models.Data;

/// <summary>
/// Data model for setting an initial master password for a user.
/// See <see cref="ISetInitialMasterPasswordCommand"/> for more details.
/// </summary>
public class SetInitialMasterPasswordDataModel
{
public required MasterPasswordAuthenticationData MasterPasswordAuthentication { get; set; }
public required MasterPasswordUnlockData MasterPasswordUnlock { get; set; }

/// <summary>
/// Organization SSO identifier.
/// </summary>
public required string OrgSsoIdentifier { get; set; }

public required UserAccountKeysData AccountKeys { get; set; }
public string? MasterPasswordHint { get; set; }
}
Loading
Loading