mirror of
https://github.com/bitwarden/server
synced 2025-12-31 07:33:43 +00:00
[PM-27280] Support v2 encryption on key-connector signups (#6712)
* account v2 registration for key connector * use new user repository functions * test coverage * integration test coverage * documentation * code review * missing test coverage * fix failing test * failing test * incorrect ticket number * moved back request model to Api, created dedicated data class in Core * sql stored procedure type mismatch, simplification * key connector authorization handler
This commit is contained in:
@@ -47,6 +47,7 @@ public class AccountsKeyManagementController : Controller
|
||||
_webauthnKeyValidator;
|
||||
private readonly IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>> _deviceValidator;
|
||||
private readonly IKeyConnectorConfirmationDetailsQuery _keyConnectorConfirmationDetailsQuery;
|
||||
private readonly ISetKeyConnectorKeyCommand _setKeyConnectorKeyCommand;
|
||||
|
||||
public AccountsKeyManagementController(IUserService userService,
|
||||
IFeatureService featureService,
|
||||
@@ -62,8 +63,10 @@ public class AccountsKeyManagementController : Controller
|
||||
emergencyAccessValidator,
|
||||
IRotationValidator<IEnumerable<ResetPasswordWithOrgIdRequestModel>, IReadOnlyList<OrganizationUser>>
|
||||
organizationUserValidator,
|
||||
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>> webAuthnKeyValidator,
|
||||
IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>> deviceValidator)
|
||||
IRotationValidator<IEnumerable<WebAuthnLoginRotateKeyRequestModel>, IEnumerable<WebAuthnLoginRotateKeyData>>
|
||||
webAuthnKeyValidator,
|
||||
IRotationValidator<IEnumerable<OtherDeviceKeysUpdateRequestModel>, IEnumerable<Device>> deviceValidator,
|
||||
ISetKeyConnectorKeyCommand setKeyConnectorKeyCommand)
|
||||
{
|
||||
_userService = userService;
|
||||
_featureService = featureService;
|
||||
@@ -79,6 +82,7 @@ public class AccountsKeyManagementController : Controller
|
||||
_webauthnKeyValidator = webAuthnKeyValidator;
|
||||
_deviceValidator = deviceValidator;
|
||||
_keyConnectorConfirmationDetailsQuery = keyConnectorConfirmationDetailsQuery;
|
||||
_setKeyConnectorKeyCommand = setKeyConnectorKeyCommand;
|
||||
}
|
||||
|
||||
[HttpPost("key-management/regenerate-keys")]
|
||||
@@ -146,18 +150,28 @@ public class AccountsKeyManagementController : Controller
|
||||
throw new UnauthorizedAccessException();
|
||||
}
|
||||
|
||||
var result = await _userService.SetKeyConnectorKeyAsync(model.ToUser(user), model.Key, model.OrgIdentifier);
|
||||
if (result.Succeeded)
|
||||
if (model.IsV2Request())
|
||||
{
|
||||
return;
|
||||
// V2 account registration
|
||||
await _setKeyConnectorKeyCommand.SetKeyConnectorKeyForUserAsync(user, model.ToKeyConnectorKeysData());
|
||||
}
|
||||
|
||||
foreach (var error in result.Errors)
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
// V1 account registration
|
||||
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
var result = await _userService.SetKeyConnectorKeyAsync(model.ToUser(user), model.Key, model.OrgIdentifier);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new BadRequestException(ModelState);
|
||||
foreach (var error in result.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
|
||||
throw new BadRequestException(ModelState);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("convert-to-key-connector")]
|
||||
|
||||
@@ -1,36 +1,112 @@
|
||||
// FIXME: Update this file to be null safe and then delete the line below
|
||||
#nullable disable
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Api.KeyManagement.Models.Requests;
|
||||
|
||||
public class SetKeyConnectorKeyRequestModel
|
||||
public class SetKeyConnectorKeyRequestModel : IValidatableObject
|
||||
{
|
||||
[Required]
|
||||
public string Key { get; set; }
|
||||
[Required]
|
||||
public KeysRequestModel Keys { get; set; }
|
||||
[Required]
|
||||
public KdfType Kdf { get; set; }
|
||||
[Required]
|
||||
public int KdfIterations { get; set; }
|
||||
public int? KdfMemory { get; set; }
|
||||
public int? KdfParallelism { get; set; }
|
||||
[Required]
|
||||
public string OrgIdentifier { get; set; }
|
||||
// TODO will be removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
[Obsolete("Use KeyConnectorKeyWrappedUserKey instead")]
|
||||
public string? Key { get; set; }
|
||||
|
||||
[Obsolete("Use AccountKeys instead")]
|
||||
public KeysRequestModel? Keys { get; set; }
|
||||
[Obsolete("Not used anymore")]
|
||||
public KdfType? Kdf { get; set; }
|
||||
[Obsolete("Not used anymore")]
|
||||
public int? KdfIterations { get; set; }
|
||||
[Obsolete("Not used anymore")]
|
||||
public int? KdfMemory { get; set; }
|
||||
[Obsolete("Not used anymore")]
|
||||
public int? KdfParallelism { get; set; }
|
||||
|
||||
[EncryptedString]
|
||||
public string? KeyConnectorKeyWrappedUserKey { get; set; }
|
||||
public AccountKeysRequestModel? AccountKeys { get; set; }
|
||||
|
||||
[Required]
|
||||
public required string OrgIdentifier { get; init; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
if (IsV2Request())
|
||||
{
|
||||
// V2 registration
|
||||
yield break;
|
||||
}
|
||||
|
||||
// V1 registration
|
||||
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
if (string.IsNullOrEmpty(Key))
|
||||
{
|
||||
yield return new ValidationResult("Key must be supplied.");
|
||||
}
|
||||
|
||||
if (Keys == null)
|
||||
{
|
||||
yield return new ValidationResult("Keys 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsV2Request()
|
||||
{
|
||||
return !string.IsNullOrEmpty(KeyConnectorKeyWrappedUserKey) && AccountKeys != null;
|
||||
}
|
||||
|
||||
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
public User ToUser(User existingUser)
|
||||
{
|
||||
existingUser.Kdf = Kdf;
|
||||
existingUser.KdfIterations = KdfIterations;
|
||||
existingUser.Kdf = Kdf!.Value;
|
||||
existingUser.KdfIterations = KdfIterations!.Value;
|
||||
existingUser.KdfMemory = KdfMemory;
|
||||
existingUser.KdfParallelism = KdfParallelism;
|
||||
existingUser.Key = Key;
|
||||
Keys.ToUser(existingUser);
|
||||
Keys!.ToUser(existingUser);
|
||||
return existingUser;
|
||||
}
|
||||
|
||||
public KeyConnectorKeysData ToKeyConnectorKeysData()
|
||||
{
|
||||
// TODO remove validation with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
if (string.IsNullOrEmpty(KeyConnectorKeyWrappedUserKey) || AccountKeys == null)
|
||||
{
|
||||
throw new BadRequestException("KeyConnectorKeyWrappedUserKey and AccountKeys must be supplied.");
|
||||
}
|
||||
|
||||
return new KeyConnectorKeysData
|
||||
{
|
||||
KeyConnectorKeyWrappedUserKey = KeyConnectorKeyWrappedUserKey,
|
||||
AccountKeys = AccountKeys,
|
||||
OrgIdentifier = OrgIdentifier
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,6 +212,7 @@ public static class FeatureFlagKeys
|
||||
public const string ConsolidatedSessionTimeoutComponent = "pm-26056-consolidated-session-timeout-component";
|
||||
public const string V2RegistrationTDEJIT = "pm-27279-v2-registration-tde-jit";
|
||||
public const string DataRecoveryTool = "pm-28813-data-recovery-tool";
|
||||
public const string EnableAccountEncryptionV2KeyConnectorRegistration = "enable-account-encryption-v2-key-connector-registration";
|
||||
|
||||
/* Mobile Team */
|
||||
public const string AndroidImportLoginsFlow = "import-logins-flow";
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Authorization;
|
||||
|
||||
public class KeyConnectorAuthorizationHandler : AuthorizationHandler<KeyConnectorOperationsRequirement, User>
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
|
||||
public KeyConnectorAuthorizationHandler(ICurrentContext currentContext)
|
||||
{
|
||||
_currentContext = currentContext;
|
||||
}
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
|
||||
KeyConnectorOperationsRequirement requirement,
|
||||
User user)
|
||||
{
|
||||
var authorized = requirement switch
|
||||
{
|
||||
not null when requirement == KeyConnectorOperations.Use => CanUse(user),
|
||||
_ => throw new ArgumentException("Unsupported operation requirement type provided.", nameof(requirement))
|
||||
};
|
||||
|
||||
if (authorized)
|
||||
{
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private bool CanUse(User user)
|
||||
{
|
||||
// User cannot use Key Connector if they already use it
|
||||
if (user.UsesKeyConnector)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// User cannot use Key Connector if they are an owner or admin of any organization
|
||||
if (_currentContext.Organizations.Any(u =>
|
||||
u.Type is OrganizationUserType.Owner or OrganizationUserType.Admin))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization.Infrastructure;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Authorization;
|
||||
|
||||
public class KeyConnectorOperationsRequirement : OperationAuthorizationRequirement
|
||||
{
|
||||
public KeyConnectorOperationsRequirement(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class KeyConnectorOperations
|
||||
{
|
||||
public static readonly KeyConnectorOperationsRequirement Use = new(nameof(Use));
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Creates the user key and account cryptographic state for a new user registering
|
||||
/// with Key Connector SSO configuration.
|
||||
/// </summary>
|
||||
public interface ISetKeyConnectorKeyCommand
|
||||
{
|
||||
Task SetKeyConnectorKeyForUserAsync(User user, KeyConnectorKeysData keyConnectorKeysData);
|
||||
}
|
||||
60
src/Core/KeyManagement/Commands/SetKeyConnectorKeyCommand.cs
Normal file
60
src/Core/KeyManagement/Commands/SetKeyConnectorKeyCommand.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Authorization;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Commands;
|
||||
|
||||
public class SetKeyConnectorKeyCommand : ISetKeyConnectorKeyCommand
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IEventService _eventService;
|
||||
private readonly IAcceptOrgUserCommand _acceptOrgUserCommand;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IUserRepository _userRepository;
|
||||
|
||||
public SetKeyConnectorKeyCommand(
|
||||
IAuthorizationService authorizationService,
|
||||
ICurrentContext currentContext,
|
||||
IEventService eventService,
|
||||
IAcceptOrgUserCommand acceptOrgUserCommand,
|
||||
IUserService userService,
|
||||
IUserRepository userRepository)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_currentContext = currentContext;
|
||||
_eventService = eventService;
|
||||
_acceptOrgUserCommand = acceptOrgUserCommand;
|
||||
_userService = userService;
|
||||
_userRepository = userRepository;
|
||||
}
|
||||
|
||||
public async Task SetKeyConnectorKeyForUserAsync(User user, KeyConnectorKeysData keyConnectorKeysData)
|
||||
{
|
||||
var authorizationResult = await _authorizationService.AuthorizeAsync(_currentContext.HttpContext.User, user,
|
||||
KeyConnectorOperations.Use);
|
||||
if (!authorizationResult.Succeeded)
|
||||
{
|
||||
throw new BadRequestException("Cannot use Key Connector");
|
||||
}
|
||||
|
||||
var setKeyConnectorUserKeyTask =
|
||||
_userRepository.SetKeyConnectorUserKey(user.Id, keyConnectorKeysData.KeyConnectorKeyWrappedUserKey);
|
||||
|
||||
await _userRepository.SetV2AccountCryptographicStateAsync(user.Id,
|
||||
keyConnectorKeysData.AccountKeys.ToAccountKeysData(), [setKeyConnectorUserKeyTask]);
|
||||
|
||||
await _eventService.LogUserEventAsync(user.Id, EventType.User_MigratedKeyToKeyConnector);
|
||||
|
||||
await _acceptOrgUserCommand.AcceptOrgUserByOrgSsoIdAsync(keyConnectorKeysData.OrgIdentifier, user,
|
||||
_userService);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Authorization;
|
||||
using Bit.Core.KeyManagement.Commands;
|
||||
using Bit.Core.KeyManagement.Commands.Interfaces;
|
||||
using Bit.Core.KeyManagement.Kdf;
|
||||
using Bit.Core.KeyManagement.Kdf.Implementations;
|
||||
using Bit.Core.KeyManagement.Queries;
|
||||
using Bit.Core.KeyManagement.Queries.Interfaces;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Bit.Core.KeyManagement;
|
||||
@@ -12,15 +14,22 @@ public static class KeyManagementServiceCollectionExtensions
|
||||
{
|
||||
public static void AddKeyManagementServices(this IServiceCollection services)
|
||||
{
|
||||
services.AddKeyManagementAuthorizationHandlers();
|
||||
services.AddKeyManagementCommands();
|
||||
services.AddKeyManagementQueries();
|
||||
services.AddSendPasswordServices();
|
||||
}
|
||||
|
||||
private static void AddKeyManagementAuthorizationHandlers(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IAuthorizationHandler, KeyConnectorAuthorizationHandler>();
|
||||
}
|
||||
|
||||
private static void AddKeyManagementCommands(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IRegenerateUserAsymmetricKeysCommand, RegenerateUserAsymmetricKeysCommand>();
|
||||
services.AddScoped<IChangeKdfCommand, ChangeKdfCommand>();
|
||||
services.AddScoped<ISetKeyConnectorKeyCommand, SetKeyConnectorKeyCommand>();
|
||||
}
|
||||
|
||||
private static void AddKeyManagementQueries(this IServiceCollection services)
|
||||
|
||||
12
src/Core/KeyManagement/Models/Data/KeyConnectorKeysData.cs
Normal file
12
src/Core/KeyManagement/Models/Data/KeyConnectorKeysData.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
|
||||
namespace Bit.Core.KeyManagement.Models.Data;
|
||||
|
||||
public class KeyConnectorKeysData
|
||||
{
|
||||
public required string KeyConnectorKeyWrappedUserKey { get; set; }
|
||||
|
||||
public required AccountKeysRequestModel AccountKeys { get; set; }
|
||||
|
||||
public required string OrgIdentifier { get; init; }
|
||||
}
|
||||
@@ -72,6 +72,8 @@ public interface IUserRepository : IRepository<User, Guid>
|
||||
UserAccountKeysData accountKeysData,
|
||||
IEnumerable<UpdateUserData>? updateUserDataActions = null);
|
||||
Task DeleteManyAsync(IEnumerable<User> users);
|
||||
|
||||
UpdateUserData SetKeyConnectorUserKey(Guid userId, string keyConnectorWrappedUserKey);
|
||||
}
|
||||
|
||||
public delegate Task UpdateUserData(Microsoft.Data.SqlClient.SqlConnection? connection = null,
|
||||
|
||||
@@ -33,6 +33,8 @@ public interface IUserService
|
||||
Task<IdentityResult> ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword,
|
||||
string token, string key);
|
||||
Task<IdentityResult> ChangePasswordAsync(User user, string masterPassword, string newMasterPassword, string passwordHint, string key);
|
||||
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
[Obsolete("Use ISetKeyConnectorKeyCommand instead. This method will be removed in a future version.")]
|
||||
Task<IdentityResult> SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier);
|
||||
Task<IdentityResult> ConvertToKeyConnectorAsync(User user);
|
||||
Task<IdentityResult> AdminResetPasswordAsync(OrganizationUserType type, Guid orgId, Guid id, string newMasterPassword, string key);
|
||||
|
||||
@@ -621,6 +621,7 @@ public class UserService : UserManager<User>, IUserService
|
||||
return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch());
|
||||
}
|
||||
|
||||
// TODO removed with https://bitwarden.atlassian.net/browse/PM-27328
|
||||
public async Task<IdentityResult> SetKeyConnectorKeyAsync(User user, string key, string orgIdentifier)
|
||||
{
|
||||
var identityResult = CheckCanUseKeyConnector(user);
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Premium.Models;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.UserKey;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -401,6 +402,32 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
|
||||
return result.SingleOrDefault();
|
||||
}
|
||||
|
||||
public UpdateUserData SetKeyConnectorUserKey(Guid userId, string keyConnectorWrappedUserKey)
|
||||
{
|
||||
return async (connection, transaction) =>
|
||||
{
|
||||
var timestamp = DateTime.UtcNow;
|
||||
|
||||
await connection!.ExecuteAsync(
|
||||
"[dbo].[User_UpdateKeyConnectorUserKey]",
|
||||
new
|
||||
{
|
||||
Id = userId,
|
||||
Key = keyConnectorWrappedUserKey,
|
||||
// Key Connector does not use KDF, so we set some defaults
|
||||
Kdf = KdfType.Argon2id,
|
||||
KdfIterations = AuthConstants.ARGON2_ITERATIONS.Default,
|
||||
KdfMemory = AuthConstants.ARGON2_MEMORY.Default,
|
||||
KdfParallelism = AuthConstants.ARGON2_PARALLELISM.Default,
|
||||
UsesKeyConnector = true,
|
||||
RevisionDate = timestamp,
|
||||
AccountRevisionDate = timestamp
|
||||
},
|
||||
transaction: transaction,
|
||||
commandType: CommandType.StoredProcedure);
|
||||
};
|
||||
}
|
||||
|
||||
private async Task ProtectDataAndSaveAsync(User user, Func<Task> saveTask)
|
||||
{
|
||||
if (user == null)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using AutoMapper;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Premium.Models;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.KeyManagement.Models.Data;
|
||||
using Bit.Core.KeyManagement.UserKey;
|
||||
using Bit.Core.Models.Data;
|
||||
@@ -479,6 +481,35 @@ public class UserRepository : Repository<Core.Entities.User, User, Guid>, IUserR
|
||||
}
|
||||
}
|
||||
|
||||
public UpdateUserData SetKeyConnectorUserKey(Guid userId, string keyConnectorWrappedUserKey)
|
||||
{
|
||||
return async (_, _) =>
|
||||
{
|
||||
using var scope = ServiceScopeFactory.CreateScope();
|
||||
var dbContext = GetDatabaseContext(scope);
|
||||
|
||||
var userEntity = await dbContext.Users.FindAsync(userId);
|
||||
if (userEntity == null)
|
||||
{
|
||||
throw new ArgumentException("User not found", nameof(userId));
|
||||
}
|
||||
|
||||
var timestamp = DateTime.UtcNow;
|
||||
|
||||
userEntity.Key = keyConnectorWrappedUserKey;
|
||||
// Key Connector does not use KDF, so we set some defaults
|
||||
userEntity.Kdf = KdfType.Argon2id;
|
||||
userEntity.KdfIterations = AuthConstants.ARGON2_ITERATIONS.Default;
|
||||
userEntity.KdfMemory = AuthConstants.ARGON2_MEMORY.Default;
|
||||
userEntity.KdfParallelism = AuthConstants.ARGON2_PARALLELISM.Default;
|
||||
userEntity.UsesKeyConnector = true;
|
||||
userEntity.RevisionDate = timestamp;
|
||||
userEntity.AccountRevisionDate = timestamp;
|
||||
|
||||
await dbContext.SaveChangesAsync();
|
||||
};
|
||||
}
|
||||
|
||||
private static void MigrateDefaultUserCollectionsToShared(DatabaseContext dbContext, IEnumerable<Guid> userIds)
|
||||
{
|
||||
var defaultCollections = (from c in dbContext.Collections
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
CREATE PROCEDURE [dbo].[User_UpdateKeyConnectorUserKey]
|
||||
@Id UNIQUEIDENTIFIER,
|
||||
@Key VARCHAR(MAX),
|
||||
@Kdf TINYINT,
|
||||
@KdfIterations INT,
|
||||
@KdfMemory INT,
|
||||
@KdfParallelism INT,
|
||||
@UsesKeyConnector BIT,
|
||||
@RevisionDate DATETIME2(7),
|
||||
@AccountRevisionDate DATETIME2(7)
|
||||
AS
|
||||
BEGIN
|
||||
SET NOCOUNT ON
|
||||
|
||||
UPDATE
|
||||
[dbo].[User]
|
||||
SET
|
||||
[Key] = @Key,
|
||||
[Kdf] = @Kdf,
|
||||
[KdfIterations] = @KdfIterations,
|
||||
[KdfMemory] = @KdfMemory,
|
||||
[KdfParallelism] = @KdfParallelism,
|
||||
[UsesKeyConnector] = @UsesKeyConnector,
|
||||
[RevisionDate] = @RevisionDate,
|
||||
[AccountRevisionDate] = @AccountRevisionDate
|
||||
WHERE
|
||||
[Id] = @Id
|
||||
END
|
||||
Reference in New Issue
Block a user