1
0
mirror of https://github.com/bitwarden/server synced 2026-01-30 00:03:48 +00:00

Initial refactor

This commit is contained in:
Anders Åberg
2025-09-25 10:28:48 +02:00
parent d2c2ae5b4d
commit ba0723c0ed
6 changed files with 39 additions and 17 deletions

View File

@@ -26,7 +26,7 @@ public class UserDecryptionOptions : ResponseModel
/// Gets or sets the WebAuthn PRF decryption keys.
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public WebAuthnPrfDecryptionOption? WebAuthnPrfOption { get; set; }
public WebAuthnPrfDecryptionOption[]? WebAuthnPrfOptions { get; set; }
/// <summary>
/// Gets or sets information regarding this users trusted device decryption setup.
@@ -45,13 +45,19 @@ public class WebAuthnPrfDecryptionOption
{
public string EncryptedPrivateKey { get; }
public string EncryptedUserKey { get; }
public string CredentialId { get; }
public string[] Transports { get; }
public WebAuthnPrfDecryptionOption(
string encryptedPrivateKey,
string encryptedUserKey)
string encryptedUserKey,
string credentialId,
string[]? transports = null)
{
EncryptedPrivateKey = encryptedPrivateKey;
EncryptedUserKey = encryptedUserKey;
CredentialId = credentialId;
Transports = transports ?? [];
}
}

View File

@@ -11,6 +11,6 @@ public interface IUserDecryptionOptionsBuilder
IUserDecryptionOptionsBuilder ForUser(User user);
IUserDecryptionOptionsBuilder WithDevice(Device device);
IUserDecryptionOptionsBuilder WithSso(SsoConfig ssoConfig);
IUserDecryptionOptionsBuilder WithWebAuthnLoginCredential(WebAuthnCredential credential);
IUserDecryptionOptionsBuilder WithWebAuthnLoginCredentials(IEnumerable<WebAuthnCredential> credentials);
Task<UserDecryptionOptions> BuildAsync();
}

View File

@@ -30,6 +30,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
private readonly IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> _assertionOptionsDataProtector;
private readonly IAssertWebAuthnLoginCredentialCommand _assertWebAuthnLoginCredentialCommand;
private readonly IDeviceValidator _deviceValidator;
private readonly IWebAuthnCredentialRepository _webAuthnCredentialRepository;
public WebAuthnGrantValidator(
UserManager<User> userManager,
@@ -50,7 +51,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand,
IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository,
IMailService mailService)
IMailService mailService,
IWebAuthnCredentialRepository webAuthnCredentialRepository)
: base(
userManager,
userService,
@@ -73,6 +75,7 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
_assertionOptionsDataProtector = assertionOptionsDataProtector;
_assertWebAuthnLoginCredentialCommand = assertWebAuthnLoginCredentialCommand;
_deviceValidator = deviceValidator;
_webAuthnCredentialRepository = webAuthnCredentialRepository;
}
string IExtensionGrantValidator.GrantType => "webauthn";
@@ -98,7 +101,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
}
var (user, credential) = await _assertWebAuthnLoginCredentialCommand.AssertWebAuthnLoginCredential(token.Options, deviceResponse);
UserDecryptionOptionsBuilder.WithWebAuthnLoginCredential(credential);
var allCredentials = await _webAuthnCredentialRepository.GetManyByUserIdAsync(user.Id);
UserDecryptionOptionsBuilder.WithWebAuthnLoginCredentials(allCredentials);
await ValidateAsync(context, context.Request, new CustomValidatorRequestContext { User = user });
}

View File

@@ -57,15 +57,25 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder
public IUserDecryptionOptionsBuilder WithDevice(Device device)
{
_device = device;
_device = device;>
return this;
}
public IUserDecryptionOptionsBuilder WithWebAuthnLoginCredential(WebAuthnCredential credential)
public IUserDecryptionOptionsBuilder WithWebAuthnLoginCredentials(IEnumerable<WebAuthnCredential> credentials)
{
if (credential.GetPrfStatus() == WebAuthnPrfStatus.Enabled)
var prfEnabledCredentials = credentials
.Where(c => c.GetPrfStatus() == WebAuthnPrfStatus.Enabled)
.Select(c => new WebAuthnPrfDecryptionOption(
c.EncryptedPrivateKey,
c.EncryptedUserKey,
c.CredentialId,
[] // Stored credentials currently lack Transports, just send an empty array for now
))
.ToArray();
if (prfEnabledCredentials.Length > 0)
{
_options.WebAuthnPrfOption = new WebAuthnPrfDecryptionOption(credential.EncryptedPrivateKey, credential.EncryptedUserKey);
_options.WebAuthnPrfOptions = prfEnabledCredentials;
}
return this;
}

View File

@@ -585,7 +585,7 @@ public class BaseRequestValidatorTests
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithWebAuthnLoginCredential(Arg.Any<WebAuthnCredential>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithWebAuthnLoginCredentials(Arg.Any<IEnumerable<WebAuthnCredential>>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.BuildAsync().Returns(Task.FromResult(new UserDecryptionOptions
{
HasMasterPassword = false,
@@ -626,7 +626,7 @@ public class BaseRequestValidatorTests
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithWebAuthnLoginCredential(Arg.Any<WebAuthnCredential>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.WithWebAuthnLoginCredentials(Arg.Any<IEnumerable<WebAuthnCredential>>()).Returns(_userDecryptionOptionsBuilder);
_userDecryptionOptionsBuilder.BuildAsync().Returns(Task.FromResult(new UserDecryptionOptions
{
HasMasterPassword = true,

View File

@@ -41,7 +41,7 @@ public class UserDecryptionOptionsBuilderTests
[BitAutoData(true, false, true)] // EncryptedPrivateKey and EncryptedUserKey are non-null, EncryptedPublicKey is null
[BitAutoData(true, true, false)] // EncryptedPrivateKey and EncryptedPublicKey are non-null, EncryptedUserKey is null
[BitAutoData(false, true, true)] // EncryptedPublicKey and EncryptedUserKey are non-null, EncryptedPrivateKey is null
public async Task WithWebAuthnLoginCredential_VariousKeyCombinations_ShouldReturnCorrectPrfOption(
public async Task WithWebAuthnLoginCredentials_VariousKeyCombinations_ShouldReturnCorrectPrfOption(
bool hasEncryptedPrivateKey,
bool hasEncryptedPublicKey,
bool hasEncryptedUserKey,
@@ -51,17 +51,19 @@ public class UserDecryptionOptionsBuilderTests
credential.EncryptedPublicKey = hasEncryptedPublicKey ? "encryptedPublicKey" : null;
credential.EncryptedUserKey = hasEncryptedUserKey ? "encryptedUserKey" : null;
var result = await _builder.WithWebAuthnLoginCredential(credential).BuildAsync();
var result = await _builder.WithWebAuthnLoginCredentials([credential]).BuildAsync();
if (credential.GetPrfStatus() == WebAuthnPrfStatus.Enabled)
{
Assert.NotNull(result.WebAuthnPrfOption);
Assert.Equal(credential.EncryptedPrivateKey, result.WebAuthnPrfOption!.EncryptedPrivateKey);
Assert.Equal(credential.EncryptedUserKey, result.WebAuthnPrfOption!.EncryptedUserKey);
Assert.NotNull(result.WebAuthnPrfOptions);
Assert.Single(result.WebAuthnPrfOptions);
Assert.Equal(credential.EncryptedPrivateKey, result.WebAuthnPrfOptions![0].EncryptedPrivateKey);
Assert.Equal(credential.CredentialId, result.WebAuthnPrfOptions![0].CredentialId);
Assert.Equal(credential.EncryptedUserKey, result.WebAuthnPrfOptions![0].EncryptedUserKey);
}
else
{
Assert.Null(result.WebAuthnPrfOption);
Assert.Null(result.WebAuthnPrfOptions);
}
}