1
0
mirror of https://github.com/bitwarden/server.git synced 2025-07-05 18:12:48 -05:00

[PM-5963] Fix tde offboarding vault corruption (#4144)

* Attempt to fix tde to mp flow

* Move tde offboarding to dedicated flag

* Add tde offboarding password request

* Validate tde offboarding input

* Correctly check whether tde is active when building trusted device options

* Refactor Tde offboarding into a separate command

* Add unit tests for tde offboarding

* Update tde offboarding request model

* Fix tests

* Fix further tests

* Fix documentation

* Add validation for updatetdepasswordasync key/newmasterpassword

* Add comment explaining test

* Remove unrelated changes
This commit is contained in:
Bernd Schoolmann
2024-07-23 20:53:08 +02:00
committed by GitHub
parent 48f9d09f4e
commit ce185eb3df
11 changed files with 283 additions and 2 deletions

View File

@ -54,18 +54,21 @@ public class TrustedDeviceUserDecryptionOption
public bool HasAdminApproval { get; }
public bool HasLoginApprovingDevice { get; }
public bool HasManageResetPasswordPermission { get; }
public bool IsTdeOffboarding { get; }
public string? EncryptedPrivateKey { get; }
public string? EncryptedUserKey { get; }
public TrustedDeviceUserDecryptionOption(bool hasAdminApproval,
bool hasLoginApprovingDevice,
bool hasManageResetPasswordPermission,
bool isTdeOffboarding,
string? encryptedPrivateKey,
string? encryptedUserKey)
{
HasAdminApproval = hasAdminApproval;
HasLoginApprovingDevice = hasLoginApprovingDevice;
HasManageResetPasswordPermission = hasManageResetPasswordPermission;
IsTdeOffboarding = isTdeOffboarding;
EncryptedPrivateKey = encryptedPrivateKey;
EncryptedUserKey = encryptedUserKey;
}

View File

@ -0,0 +1,14 @@
using Bit.Core.Entities;
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
/// <summary>
/// <para>Manages the setting of the master password for JIT provisioned TDE <see cref="User"/> in an organization, after the organization disabled TDE.</para>
/// <para>This command is invoked, when the user first logs in after the organization has switched from TDE to master password based decryption.</para>
/// </summary>
public interface ITdeOffboardingPasswordCommand
{
public Task<IdentityResult> UpdateTdeOffboardingPasswordAsync(User user, string masterPassword, string key,
string orgSsoIdentifier);
}

View File

@ -0,0 +1,99 @@
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Microsoft.AspNetCore.Identity;
namespace Bit.Core.Auth.UserFeatures.UserMasterPassword;
public class TdeOffboardingPasswordCommand : ITdeOffboardingPasswordCommand
{
private readonly IUserService _userService;
private readonly IUserRepository _userRepository;
private readonly IEventService _eventService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ISsoUserRepository _ssoUserRepository;
private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IPushNotificationService _pushService;
public TdeOffboardingPasswordCommand(
IUserService userService,
IUserRepository userRepository,
IEventService eventService,
IOrganizationUserRepository organizationUserRepository,
ISsoUserRepository ssoUserRepository,
ISsoConfigRepository ssoConfigRepository,
IPushNotificationService pushService)
{
_userService = userService;
_userRepository = userRepository;
_eventService = eventService;
_organizationUserRepository = organizationUserRepository;
_ssoUserRepository = ssoUserRepository;
_ssoConfigRepository = ssoConfigRepository;
_pushService = pushService;
}
public async Task<IdentityResult> UpdateTdeOffboardingPasswordAsync(User user, string newMasterPassword, string key, string hint)
{
if (string.IsNullOrWhiteSpace(newMasterPassword))
{
throw new BadRequestException("Master password is required.");
}
if (string.IsNullOrWhiteSpace(key))
{
throw new BadRequestException("Key is required.");
}
if (user.HasMasterPassword())
{
throw new BadRequestException("User already has a master password.");
}
var orgUserDetails = await _organizationUserRepository.GetManyDetailsByUserAsync(user.Id);
orgUserDetails = orgUserDetails.Where(x => x.UseSso).ToList();
if (orgUserDetails.Count == 0)
{
throw new BadRequestException("User is not part of any organization that has SSO enabled.");
}
var orgSSOUsers = await Task.WhenAll(orgUserDetails.Select(async x => await _ssoUserRepository.GetByUserIdOrganizationIdAsync(x.OrganizationId, user.Id)));
if (orgSSOUsers.Length != 1)
{
throw new BadRequestException("User is part of no or multiple SSO configurations.");
}
var orgUser = orgUserDetails.First();
var orgSSOConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(orgUser.OrganizationId);
if (orgSSOConfig == null)
{
throw new BadRequestException("Organization SSO configuration not found.");
}
else if (orgSSOConfig.GetData().MemberDecryptionType != Enums.MemberDecryptionType.MasterPassword)
{
throw new BadRequestException("Organization SSO Member Decryption Type is not Master Password.");
}
var result = await _userService.UpdatePasswordHash(user, newMasterPassword);
if (!result.Succeeded)
{
return result;
}
user.RevisionDate = user.AccountRevisionDate = DateTime.UtcNow;
user.ForcePasswordReset = false;
user.Key = key;
user.MasterPasswordHint = hint;
await _userRepository.ReplaceAsync(user);
await _eventService.LogUserEventAsync(user.Id, EventType.User_UpdatedTempPassword);
await _pushService.PushLogOutAsync(user.Id);
return IdentityResult.Success;
}
}

View File

@ -2,6 +2,7 @@
using Bit.Core.Auth.UserFeatures.Registration;
using Bit.Core.Auth.UserFeatures.Registration.Implementations;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserKey.Implementations;
using Bit.Core.Auth.UserFeatures.UserMasterPassword;
@ -22,6 +23,7 @@ public static class UserServiceCollectionExtensions
services.AddUserPasswordCommands();
services.AddUserRegistrationCommands();
services.AddWebAuthnLoginCommands();
services.AddTdeOffboardingPasswordCommands();
}
public static void AddUserKeyCommands(this IServiceCollection services, IGlobalSettings globalSettings)
@ -34,6 +36,11 @@ public static class UserServiceCollectionExtensions
services.AddScoped<ISetInitialMasterPasswordCommand, SetInitialMasterPasswordCommand>();
}
private static void AddTdeOffboardingPasswordCommands(this IServiceCollection services)
{
services.AddScoped<ITdeOffboardingPasswordCommand, TdeOffboardingPasswordCommand>();
}
private static void AddUserRegistrationCommands(this IServiceCollection services)
{
services.AddScoped<ISendVerificationEmailForRegistrationCommand, SendVerificationEmailForRegistrationCommand>();

View File

@ -14,6 +14,7 @@ public enum EventType : int
User_UpdatedTempPassword = 1008,
User_MigratedKeyToKeyConnector = 1009,
User_RequestedDeviceApproval = 1010,
User_TdeOffboardingPasswordSet = 1011,
Cipher_Created = 1100,
Cipher_Updated = 1101,