1
0
mirror of https://github.com/bitwarden/server synced 2026-02-27 18:03:17 +00: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

@@ -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;
}
}