1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -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

@ -12,6 +12,7 @@ using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.UserKey;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Billing.Services;
@ -44,6 +45,7 @@ public class AccountsControllerTests : IDisposable
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly IFeatureService _featureService;
private readonly ISubscriberService _subscriberService;
private readonly IReferenceEventService _referenceEventService;
@ -72,6 +74,7 @@ public class AccountsControllerTests : IDisposable
_policyService = Substitute.For<IPolicyService>();
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
_featureService = Substitute.For<IFeatureService>();
_subscriberService = Substitute.For<ISubscriberService>();
_referenceEventService = Substitute.For<IReferenceEventService>();
@ -97,6 +100,7 @@ public class AccountsControllerTests : IDisposable
_userService,
_policyService,
_setInitialMasterPasswordCommand,
_tdeOffboardingPasswordCommand,
_rotateUserKeyCommand,
_featureService,
_subscriberService,

View File

@ -0,0 +1,99 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.UserMasterPassword;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Auth.UserFeatures.UserMasterPassword;
[SutProviderCustomize]
public class TdeOffboardingPasswordTests
{
[Theory]
[BitAutoData]
public async Task TdeOffboardingPasswordCommand_Success(SutProvider<TdeOffboardingPasswordCommand> sutProvider,
User user, string masterPassword, string key, string hint, OrganizationUserOrganizationDetails orgUserDetails, SsoUser ssoUser)
{
// Arrange
user.MasterPassword = null;
sutProvider.GetDependency<IUserService>()
.UpdatePasswordHash(Arg.Any<User>(), Arg.Any<string>())
.Returns(IdentityResult.Success);
orgUserDetails.UseSso = true;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByUserAsync(user.Id)
.Returns(new List<OrganizationUserOrganizationDetails> { orgUserDetails });
sutProvider.GetDependency<ISsoUserRepository>()
.GetByUserIdOrganizationIdAsync(orgUserDetails.OrganizationId, user.Id)
.Returns(ssoUser);
var ssoConfig = new SsoConfig();
var ssoConfigData = ssoConfig.GetData();
ssoConfigData.MemberDecryptionType = MemberDecryptionType.MasterPassword;
ssoConfig.SetData(ssoConfigData);
sutProvider.GetDependency<ISsoConfigRepository>()
.GetByOrganizationIdAsync(orgUserDetails.OrganizationId)
.Returns(ssoConfig);
// Act
var result = await sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint);
// Assert
Assert.Equal(IdentityResult.Success, result);
}
[Theory]
[BitAutoData]
public async Task TdeOffboardingPasswordCommand_RejectWithTdeEnabled(SutProvider<TdeOffboardingPasswordCommand> sutProvider,
User user, string masterPassword, string key, string hint, OrganizationUserOrganizationDetails orgUserDetails, SsoUser ssoUser)
{
// Arrange
user.MasterPassword = null;
sutProvider.GetDependency<IUserService>()
.UpdatePasswordHash(Arg.Any<User>(), Arg.Any<string>(), true, false)
.Returns(IdentityResult.Success);
orgUserDetails.UseSso = true;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyDetailsByUserAsync(user.Id)
.Returns(new List<OrganizationUserOrganizationDetails> { orgUserDetails });
sutProvider.GetDependency<ISsoUserRepository>()
.GetByUserIdOrganizationIdAsync(orgUserDetails.OrganizationId, user.Id)
.Returns(ssoUser);
var ssoConfig = new SsoConfig();
var ssoConfigData = ssoConfig.GetData();
ssoConfigData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
ssoConfig.SetData(ssoConfigData);
sutProvider.GetDependency<ISsoConfigRepository>()
.GetByOrganizationIdAsync(orgUserDetails.OrganizationId)
.Returns(ssoConfig);
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint));
}
[Theory]
[BitAutoData]
public async Task TdeOffboardingPasswordCommand_RejectWithMasterPassword(SutProvider<TdeOffboardingPasswordCommand> sutProvider,
User user, string masterPassword, string key, string hint)
{
// the user already has a master password, so the off-boarding request should fail, since off-boarding only applies to passwordless TDE users
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateTdeOffboardingPasswordAsync(user, masterPassword, key, hint));
}
}

View File

@ -178,6 +178,11 @@ public class IdentityServerSsoTests
{
Assert.Equal("HasManageResetPasswordPermission", p.Name);
Assert.Equal(JsonValueKind.False, p.Value.ValueKind);
},
p =>
{
Assert.Equal("IsTdeOffboarding", p.Name);
Assert.Equal(JsonValueKind.False, p.Value.ValueKind);
});
}
@ -219,6 +224,7 @@ public class IdentityServerSsoTests
// "HasAdminApproval": true,
// "HasLoginApprovingDevice": true,
// "HasManageResetPasswordPermission": false
// "IsTdeOffboarding": false
// }
// }
@ -242,6 +248,11 @@ public class IdentityServerSsoTests
{
Assert.Equal("HasManageResetPasswordPermission", p.Name);
Assert.Equal(JsonValueKind.False, p.Value.ValueKind);
},
p =>
{
Assert.Equal("IsTdeOffboarding", p.Name);
Assert.Equal(JsonValueKind.False, p.Value.ValueKind);
});
}