1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-02 16:42:50 -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

@ -0,0 +1,19 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Entities;
using Bit.Core.Tools.Entities;
using Bit.Core.Vault.Entities;
namespace Bit.Core.Auth.Models.Data;
public class RotateUserKeyData
{
public User User { get; set; }
public string MasterPasswordHash { get; set; }
public string Key { get; set; }
public string PrivateKey { get; set; }
public IEnumerable<Cipher> Ciphers { get; set; }
public IEnumerable<Folder> Folders { get; set; }
public IEnumerable<Send> Sends { get; set; }
public IEnumerable<EmergencyAccess> EmergencyAccessKeys { get; set; }
public IEnumerable<OrganizationUser> ResetPasswordKeys { get; set; }
}

View File

@ -0,0 +1,18 @@
using Bit.Core.Auth.Models.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.Data.SqlClient;
namespace Bit.Core.Auth.UserFeatures.UserKey;
public interface IRotateUserKeyCommand
{
/// <summary>
/// Sets a new user key and updates all encrypted data.
/// </summary>
/// <param name="model">All necessary information for rotation. Warning: Any encrypted data not included will be lost.</param>
/// <returns>An IdentityResult for verification of the master password hash</returns>
/// <exception cref="ArgumentNullException">User must be provided.</exception>
Task<IdentityResult> RotateUserKeyAsync(RotateUserKeyData model);
}
public delegate Task UpdateEncryptedDataForKeyRotation(SqlTransaction transaction = null);

View File

@ -0,0 +1,61 @@
using Bit.Core.Auth.Models.Data;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Auth.UserFeatures.UserKey.Implementations;
public class RotateUserKeyCommand : IRotateUserKeyCommand
{
private readonly IUserService _userService;
private readonly IUserRepository _userRepository;
private readonly IPushNotificationService _pushService;
private readonly IdentityErrorDescriber _identityErrorDescriber;
public RotateUserKeyCommand(IUserService userService, IUserRepository userRepository,
IPushNotificationService pushService, IdentityErrorDescriber errors)
{
_userService = userService;
_userRepository = userRepository;
_pushService = pushService;
_identityErrorDescriber = errors;
}
/// <inheritdoc />
public async Task<IdentityResult> RotateUserKeyAsync(RotateUserKeyData model)
{
if (model.User == null)
{
throw new ArgumentNullException(nameof(model.User));
}
if (!await _userService.CheckPasswordAsync(model.User, model.MasterPasswordHash))
{
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
}
var now = DateTime.UtcNow;
model.User.RevisionDate = model.User.AccountRevisionDate = now;
model.User.LastKeyRotationDate = now;
model.User.SecurityStamp = Guid.NewGuid().ToString();
model.User.Key = model.Key;
model.User.PrivateKey = model.PrivateKey;
if (model.Ciphers.Any() || model.Folders.Any() || model.Sends.Any() || model.EmergencyAccessKeys.Any() ||
model.ResetPasswordKeys.Any())
{
List<UpdateEncryptedDataForKeyRotation> saveEncryptedDataActions = new();
// if (model.Ciphers.Any())
// {
// saveEncryptedDataActions.Add(_cipherRepository.SaveRotatedData);
// }
await _userRepository.UpdateUserKeyAndEncryptedDataAsync(model.User, saveEncryptedDataActions);
}
else
{
await _userRepository.ReplaceAsync(model.User);
}
await _pushService.PushLogOutAsync(model.User.Id, excludeCurrentContextFromPush: true);
return IdentityResult.Success;
}
}

View File

@ -65,6 +65,7 @@ public static class FeatureFlagKeys
public const string ItemShare = "item-share";
public const string BillingPlansUpgrade = "billing-plans-upgrade";
public const string BillingStarterPlan = "billing-starter-plan";
public const string KeyRotationImprovements = "key-rotation-improvements";
public static List<string> GetAllKeys()
{

View File

@ -1,4 +1,5 @@
using Bit.Core.Entities;
using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Entities;
using Bit.Core.Models.Data;
namespace Bit.Core.Repositories;
@ -15,4 +16,13 @@ public interface IUserRepository : IRepository<User, Guid>
Task UpdateStorageAsync(Guid id);
Task UpdateRenewalReminderDateAsync(Guid id, DateTime renewalReminderDate);
Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids);
/// <summary>
/// Sets a new user key and updates all encrypted data.
/// <para>Warning: Any user key encrypted data not included will be lost.</para>
/// </summary>
/// <param name="user">The user to update</param>
/// <param name="updateDataActions">Registered database calls to update re-encrypted data.</param>
[Obsolete("Intended for future improvements to key rotation. Do not use.")]
Task UpdateUserKeyAndEncryptedDataAsync(User user,
IEnumerable<UpdateEncryptedDataForKeyRotation> updateDataActions);
}