1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-04 17:42:49 -05:00

[PM-3797 Part 1] Layout new key rotation methods (#3425)

* layout new key rotation methods
- add endpoint with request model
- add command with data model
- add repository method

* layout new key rotation methods
- add endpoint with request model
- add command with data model
- add repository method

* formatting

* rename account recovery to reset password

* fix tests

* remove extra endpoint

* rename account recovery to reset password

* fix tests and formatting

* register db calls in command, removing list from user repo

* formatting
This commit is contained in:
Jake Fink
2023-11-09 14:56:08 -05:00
committed by GitHub
parent 4cf2142b68
commit b716a925f8
12 changed files with 340 additions and 38 deletions

View File

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Tools.Models.Request;
using Bit.Api.Vault.Models.Request;
@ -10,12 +11,13 @@ public class UpdateKeyRequestModel
[StringLength(300)]
public string MasterPasswordHash { get; set; }
[Required]
public IEnumerable<CipherWithIdRequestModel> Ciphers { get; set; }
[Required]
public IEnumerable<FolderWithIdRequestModel> Folders { get; set; }
public IEnumerable<SendWithIdRequestModel> Sends { get; set; }
public string Key { get; set; }
[Required]
public string PrivateKey { get; set; }
[Required]
public string Key { get; set; }
public IEnumerable<CipherWithIdRequestModel> Ciphers { get; set; }
public IEnumerable<FolderWithIdRequestModel> Folders { get; set; }
public IEnumerable<SendWithIdRequestModel> Sends { get; set; }
public IEnumerable<EmergencyAccessUpdateRequestModel> EmergencyAccessKeys { get; set; }
public IEnumerable<OrganizationUserUpdateRequestModel> ResetPasswordKeys { get; set; }
}

View File

@ -9,9 +9,12 @@ using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Api.Response.Accounts;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Services;
using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Auth.Utilities;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Api.Response;
@ -27,6 +30,7 @@ using Bit.Core.Utilities;
using Bit.Core.Vault.Entities;
using Bit.Core.Vault.Repositories;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace Bit.Api.Controllers;
@ -49,6 +53,9 @@ public class AccountsController : Controller
private readonly ICaptchaValidationService _captchaValidationService;
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
private readonly IFeatureService _featureService;
private readonly ICurrentContext _currentContext;
public AccountsController(
@ -65,7 +72,10 @@ public class AccountsController : Controller
ISendService sendService,
ICaptchaValidationService captchaValidationService,
IPolicyService policyService,
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
IRotateUserKeyCommand rotateUserKeyCommand,
IFeatureService featureService,
ICurrentContext currentContext
)
{
_cipherRepository = cipherRepository;
@ -82,6 +92,9 @@ public class AccountsController : Controller
_captchaValidationService = captchaValidationService;
_policyService = policyService;
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
_rotateUserKeyCommand = rotateUserKeyCommand;
_featureService = featureService;
_currentContext = currentContext;
}
#region DEPRECATED (Moved to Identity Service)
@ -379,38 +392,59 @@ public class AccountsController : Controller
throw new UnauthorizedAccessException();
}
var ciphers = new List<Cipher>();
if (model.Ciphers.Any())
IdentityResult result;
if (_featureService.IsEnabled(FeatureFlagKeys.KeyRotationImprovements, _currentContext))
{
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
ciphers.AddRange(existingCiphers
.Join(model.Ciphers, c => c.Id, c => c.Id, (existing, c) => c.ToCipher(existing)));
var dataModel = new RotateUserKeyData
{
MasterPasswordHash = model.MasterPasswordHash,
Key = model.Key,
PrivateKey = model.PrivateKey,
// Ciphers = await _cipherValidator.ValidateAsync(user, model.Ciphers),
// Folders = await _folderValidator.ValidateAsync(user, model.Folders),
// Sends = await _sendValidator.ValidateAsync(user, model.Sends),
// EmergencyAccessKeys = await _emergencyAccessValidator.ValidateAsync(user, model.EmergencyAccessKeys),
// ResetPasswordKeys = await _accountRecoveryValidator.ValidateAsync(user, model.ResetPasswordKeys),
};
result = await _rotateUserKeyCommand.RotateUserKeyAsync(dataModel);
}
else
{
var ciphers = new List<Cipher>();
if (model.Ciphers.Any())
{
var existingCiphers = await _cipherRepository.GetManyByUserIdAsync(user.Id);
ciphers.AddRange(existingCiphers
.Join(model.Ciphers, c => c.Id, c => c.Id, (existing, c) => c.ToCipher(existing)));
}
var folders = new List<Folder>();
if (model.Folders.Any())
{
var existingFolders = await _folderRepository.GetManyByUserIdAsync(user.Id);
folders.AddRange(existingFolders
.Join(model.Folders, f => f.Id, f => f.Id, (existing, f) => f.ToFolder(existing)));
}
var sends = new List<Send>();
if (model.Sends?.Any() == true)
{
var existingSends = await _sendRepository.GetManyByUserIdAsync(user.Id);
sends.AddRange(existingSends
.Join(model.Sends, s => s.Id, s => s.Id, (existing, s) => s.ToSend(existing, _sendService)));
}
result = await _userService.UpdateKeyAsync(
user,
model.MasterPasswordHash,
model.Key,
model.PrivateKey,
ciphers,
folders,
sends);
}
var folders = new List<Folder>();
if (model.Folders.Any())
{
var existingFolders = await _folderRepository.GetManyByUserIdAsync(user.Id);
folders.AddRange(existingFolders
.Join(model.Folders, f => f.Id, f => f.Id, (existing, f) => f.ToFolder(existing)));
}
var sends = new List<Send>();
if (model.Sends?.Any() == true)
{
var existingSends = await _sendRepository.GetManyByUserIdAsync(user.Id);
sends.AddRange(existingSends
.Join(model.Sends, s => s.Id, s => s.Id, (existing, s) => s.ToSend(existing, _sendService)));
}
var result = await _userService.UpdateKeyAsync(
user,
model.MasterPasswordHash,
model.Key,
model.PrivateKey,
ciphers,
folders,
sends);
if (result.Succeeded)
{

View File

@ -15,6 +15,8 @@ using Bit.SharedWeb.Utilities;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserKey.Implementations;
using Bit.Core.OrganizationFeatures.OrganizationSubscriptions;
#if !OSS
@ -131,6 +133,9 @@ public class Startup
services.AddScoped<AuthenticatorTokenProvider>();
// Key Rotation
services.AddScoped<IRotateUserKeyCommand, RotateUserKeyCommand>();
// Services
services.AddBaseServices(globalSettings);
services.AddDefaultServices(globalSettings);