mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-2032] Server endpoints to support authentication with a passkey (#3361)
* [PM-2032] feat: add assertion options tokenable * [PM-2032] feat: add request and response models * [PM-2032] feat: implement `assertion-options` identity endpoint * [PM-2032] feat: implement authentication with passkey * [PM-2032] chore: rename to `WebAuthnGrantValidator` * [PM-2032] fix: add missing subsitute * [PM-2032] feat: start adding builder * [PM-2032] feat: add support for KeyConnector * [PM-2032] feat: add first version of TDE * [PM-2032] chore: refactor WithSso * [PM-2023] feat: add support for TDE feature flag * [PM-2023] feat: add support for approving devices * [PM-2023] feat: add support for hasManageResetPasswordPermission * [PM-2032] feat: add support for hasAdminApproval * [PM-2032] chore: don't supply device if not necessary * [PM-2032] chore: clean up imports * [PM-2023] feat: extract interface * [PM-2023] chore: add clarifying comment * [PM-2023] feat: use new builder in production code * [PM-2032] feat: add support for PRF * [PM-2032] chore: clean-up todos * [PM-2023] chore: remove token which is no longer used * [PM-2032] chore: remove todo * [PM-2032] feat: improve assertion error handling * [PM-2032] fix: linting issues * [PM-2032] fix: revert changes to `launchSettings.json` * [PM-2023] chore: clean up assertion endpoint * [PM-2032] feat: bypass 2FA * [PM-2032] fix: rename prf option to singular * [PM-2032] fix: lint * [PM-2032] fix: typo * [PM-2032] chore: improve builder tests Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> * [PM-2032] chore: clarify why we don't require 2FA * [PM-2023] feat: move `identityProvider` constant to common class * [PM-2032] fix: lint * [PM-2023] fix: move `IdentityProvider` to core.Constants * [PM-2032] fix: missing import * [PM-2032] chore: refactor token timespan to use `TimeSpan` * [PM-2032] chore: make `StartWebAuthnLoginAssertion` sync * [PM-2032] chore: use `FromMinutes` * [PM-2032] fix: change to 17 minutes to cover webauthn assertion * [PM-2032] chore: do not use `async void` * [PM-2032] fix: comment saying wrong amount of minutes * [PM-2032] feat: put validator behind feature flag * [PM-2032] fix: lint --------- Co-authored-by: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,61 @@
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Fido2NetLib;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Auth.Models.Business.Tokenables;
|
||||
|
||||
public class WebAuthnLoginAssertionOptionsTokenableTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void Valid_TokenWithoutOptions_ReturnsFalse(WebAuthnLoginAssertionOptionsScope scope)
|
||||
{
|
||||
var token = new WebAuthnLoginAssertionOptionsTokenable(scope, null);
|
||||
|
||||
var isValid = token.Valid;
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Valid_NewlyCreatedToken_ReturnsTrue(WebAuthnLoginAssertionOptionsScope scope, AssertionOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnLoginAssertionOptionsTokenable(scope, createOptions);
|
||||
|
||||
|
||||
var isValid = token.Valid;
|
||||
|
||||
Assert.True(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_TokenWithoutOptions_ReturnsFalse(WebAuthnLoginAssertionOptionsScope scope)
|
||||
{
|
||||
var token = new WebAuthnLoginAssertionOptionsTokenable(scope, null);
|
||||
|
||||
var isValid = token.TokenIsValid(scope);
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_NonMatchingScope_ReturnsFalse(WebAuthnLoginAssertionOptionsScope scope1, WebAuthnLoginAssertionOptionsScope scope2, AssertionOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnLoginAssertionOptionsTokenable(scope1, createOptions);
|
||||
|
||||
var isValid = token.TokenIsValid(scope2);
|
||||
|
||||
Assert.False(isValid);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void ValidIsValid_SameScope_ReturnsTrue(WebAuthnLoginAssertionOptionsScope scope, AssertionOptions createOptions)
|
||||
{
|
||||
var token = new WebAuthnLoginAssertionOptionsTokenable(scope, createOptions);
|
||||
|
||||
var isValid = token.TokenIsValid(scope);
|
||||
|
||||
Assert.True(isValid);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using AutoFixture;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
@ -8,26 +9,29 @@ using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Business;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Vault.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Fakes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Fido2NetLib;
|
||||
using Fido2NetLib.Objects;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReceivedExtensions;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Services;
|
||||
@ -188,7 +192,7 @@ public class UserServiceTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async void CompleteWebAuthLoginRegistrationAsync_ExceedsExistingCredentialsLimit_ReturnsFalse(SutProvider<UserService> sutProvider, User user, CredentialCreateOptions options, AuthenticatorAttestationRawResponse response, Generator<WebAuthnCredential> credentialGenerator)
|
||||
public async Task CompleteWebAuthLoginRegistrationAsync_ExceedsExistingCredentialsLimit_ReturnsFalse(SutProvider<UserService> sutProvider, User user, CredentialCreateOptions options, AuthenticatorAttestationRawResponse response, Generator<WebAuthnCredential> credentialGenerator)
|
||||
{
|
||||
// Arrange
|
||||
var existingCredentials = credentialGenerator.Take(5).ToList();
|
||||
@ -202,6 +206,92 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().DidNotReceive();
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteWebAuthLoginAssertionAsync_InvalidUserHandle_ThrowsBadRequestException(SutProvider<UserService> sutProvider, AssertionOptions options, AuthenticatorAssertionRawResponse response)
|
||||
{
|
||||
// Arrange
|
||||
response.Response.UserHandle = Encoding.UTF8.GetBytes("invalid-user-handle");
|
||||
|
||||
// Act
|
||||
var result = async () => await sutProvider.Sut.CompleteWebAuthLoginAssertionAsync(options, response);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteWebAuthLoginAssertionAsync_UserNotFound_ThrowsBadRequestException(SutProvider<UserService> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response)
|
||||
{
|
||||
// Arrange
|
||||
response.Response.UserHandle = user.Id.ToByteArray();
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).ReturnsNull();
|
||||
|
||||
// Act
|
||||
var result = async () => await sutProvider.Sut.CompleteWebAuthLoginAssertionAsync(options, response);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteWebAuthLoginAssertionAsync_NoMatchingCredentialExists_ThrowsBadRequestException(SutProvider<UserService> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response)
|
||||
{
|
||||
// Arrange
|
||||
response.Response.UserHandle = user.Id.ToByteArray();
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { });
|
||||
|
||||
// Act
|
||||
var result = async () => await sutProvider.Sut.CompleteWebAuthLoginAssertionAsync(options, response);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteWebAuthLoginAssertionAsync_AssertionFails_ThrowsBadRequestException(SutProvider<UserService> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response, WebAuthnCredential credential, AssertionVerificationResult assertionResult)
|
||||
{
|
||||
// Arrange
|
||||
var credentialId = Guid.NewGuid().ToByteArray();
|
||||
credential.CredentialId = CoreHelpers.Base64UrlEncode(credentialId);
|
||||
response.Id = credentialId;
|
||||
response.Response.UserHandle = user.Id.ToByteArray();
|
||||
assertionResult.Status = "Not ok";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { credential });
|
||||
sutProvider.GetDependency<IFido2>().MakeAssertionAsync(response, options, Arg.Any<byte[]>(), Arg.Any<uint>(), Arg.Any<IsUserHandleOwnerOfCredentialIdAsync>())
|
||||
.Returns(assertionResult);
|
||||
|
||||
// Act
|
||||
var result = async () => await sutProvider.Sut.CompleteWebAuthLoginAssertionAsync(options, response);
|
||||
|
||||
// Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CompleteWebAuthLoginAssertionAsync_AssertionSucceeds_ReturnsUserAndCredential(SutProvider<UserService> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response, WebAuthnCredential credential, AssertionVerificationResult assertionResult)
|
||||
{
|
||||
// Arrange
|
||||
var credentialId = Guid.NewGuid().ToByteArray();
|
||||
credential.CredentialId = CoreHelpers.Base64UrlEncode(credentialId);
|
||||
response.Id = credentialId;
|
||||
response.Response.UserHandle = user.Id.ToByteArray();
|
||||
assertionResult.Status = "ok";
|
||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { credential });
|
||||
sutProvider.GetDependency<IFido2>().MakeAssertionAsync(response, options, Arg.Any<byte[]>(), Arg.Any<uint>(), Arg.Any<IsUserHandleOwnerOfCredentialIdAsync>())
|
||||
.Returns(assertionResult);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.CompleteWebAuthLoginAssertionAsync(options, response);
|
||||
|
||||
// Assert
|
||||
var (userResult, credentialResult) = result;
|
||||
Assert.Equal(user, userResult);
|
||||
Assert.Equal(credential, credentialResult);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ShouldCheck
|
||||
{
|
||||
@ -278,8 +368,7 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IProviderUserRepository>(),
|
||||
sutProvider.GetDependency<IStripeSyncService>(),
|
||||
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>(),
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginTokenable>>()
|
||||
sutProvider.GetDependency<IWebAuthnCredentialRepository>()
|
||||
);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
|
@ -38,7 +38,8 @@
|
||||
"refresh_token",
|
||||
"implicit",
|
||||
"password",
|
||||
"urn:ietf:params:oauth:grant-type:device_code"
|
||||
"urn:ietf:params:oauth:grant-type:device_code",
|
||||
"webauthn"
|
||||
],
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
@ -49,24 +50,13 @@
|
||||
"code token",
|
||||
"code id_token token"
|
||||
],
|
||||
"response_modes_supported": [
|
||||
"form_post",
|
||||
"query",
|
||||
"fragment"
|
||||
],
|
||||
"response_modes_supported": ["form_post", "query", "fragment"],
|
||||
"token_endpoint_auth_methods_supported": [
|
||||
"client_secret_basic",
|
||||
"client_secret_post"
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"RS256"
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public"
|
||||
],
|
||||
"code_challenge_methods_supported": [
|
||||
"plain",
|
||||
"S256"
|
||||
],
|
||||
"id_token_signing_alg_values_supported": ["RS256"],
|
||||
"subject_types_supported": ["public"],
|
||||
"code_challenge_methods_supported": ["plain", "S256"],
|
||||
"request_parameter_supported": true
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -6,6 +7,7 @@ using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Identity.Controllers;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -22,6 +24,7 @@ public class AccountsControllerTests : IDisposable
|
||||
private readonly IUserRepository _userRepository;
|
||||
private readonly IUserService _userService;
|
||||
private readonly ICaptchaValidationService _captchaValidationService;
|
||||
private readonly IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable> _assertionOptionsDataProtector;
|
||||
|
||||
public AccountsControllerTests()
|
||||
{
|
||||
@ -29,11 +32,13 @@ public class AccountsControllerTests : IDisposable
|
||||
_userRepository = Substitute.For<IUserRepository>();
|
||||
_userService = Substitute.For<IUserService>();
|
||||
_captchaValidationService = Substitute.For<ICaptchaValidationService>();
|
||||
_assertionOptionsDataProtector = Substitute.For<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>();
|
||||
_sut = new AccountsController(
|
||||
_logger,
|
||||
_userRepository,
|
||||
_userService,
|
||||
_captchaValidationService
|
||||
_captchaValidationService,
|
||||
_assertionOptionsDataProtector
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,172 @@
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Identity.IdentityServer;
|
||||
using Bit.Identity.Utilities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Identity.Test.IdentityServer;
|
||||
|
||||
public class UserDecryptionOptionsBuilderTests
|
||||
{
|
||||
private readonly ICurrentContext _currentContext;
|
||||
private readonly IFeatureService _featureService;
|
||||
private readonly IDeviceRepository _deviceRepository;
|
||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||
private readonly UserDecryptionOptionsBuilder _builder;
|
||||
|
||||
public UserDecryptionOptionsBuilderTests()
|
||||
{
|
||||
_currentContext = Substitute.For<ICurrentContext>();
|
||||
_featureService = Substitute.For<IFeatureService>();
|
||||
_deviceRepository = Substitute.For<IDeviceRepository>();
|
||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||
_builder = new UserDecryptionOptionsBuilder(_currentContext, _featureService, _deviceRepository, _organizationUserRepository);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(true, true, true)] // All keys are non-null
|
||||
[BitAutoData(false, false, false)] // All keys are null
|
||||
[BitAutoData(false, false, true)] // EncryptedUserKey is non-null, others are null
|
||||
[BitAutoData(false, true, false)] // EncryptedPublicKey is non-null, others are null
|
||||
[BitAutoData(true, false, false)] // EncryptedPrivateKey is non-null, others are null
|
||||
[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(
|
||||
bool hasEncryptedPrivateKey,
|
||||
bool hasEncryptedPublicKey,
|
||||
bool hasEncryptedUserKey,
|
||||
WebAuthnCredential credential)
|
||||
{
|
||||
credential.EncryptedPrivateKey = hasEncryptedPrivateKey ? "encryptedPrivateKey" : null;
|
||||
credential.EncryptedPublicKey = hasEncryptedPublicKey ? "encryptedPublicKey" : null;
|
||||
credential.EncryptedUserKey = hasEncryptedUserKey ? "encryptedUserKey" : null;
|
||||
|
||||
var result = await _builder.WithWebAuthnLoginCredential(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);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(result.WebAuthnPrfOption);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenKeyConnectorIsEnabled_ShouldReturnKeyConnectorOptions(SsoConfig ssoConfig, SsoConfigurationData configurationData)
|
||||
{
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.KeyConnector;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
|
||||
var result = await _builder.WithSso(ssoConfig).BuildAsync();
|
||||
|
||||
Assert.NotNull(result.KeyConnectorOption);
|
||||
Assert.Equal(configurationData.KeyConnectorUrl, result.KeyConnectorOption!.KeyConnectorUrl);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenTrustedDeviceIsEnabled_ShouldReturnTrustedDeviceOptions(SsoConfig ssoConfig, SsoConfigurationData configurationData, Device device)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(true);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
|
||||
var result = await _builder.WithSso(ssoConfig).WithDevice(device).BuildAsync();
|
||||
|
||||
Assert.NotNull(result.TrustedDeviceOption);
|
||||
Assert.False(result.TrustedDeviceOption!.HasAdminApproval);
|
||||
Assert.False(result.TrustedDeviceOption!.HasLoginApprovingDevice);
|
||||
Assert.False(result.TrustedDeviceOption!.HasManageResetPasswordPermission);
|
||||
}
|
||||
|
||||
// TODO: Remove when FeatureFlagKeys.TrustedDeviceEncryption is removed
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenTrustedDeviceIsEnabledButFeatureFlagIsDisabled_ShouldNotReturnTrustedDeviceOptions(SsoConfig ssoConfig, SsoConfigurationData configurationData, Device device)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(false);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
|
||||
var result = await _builder.WithSso(ssoConfig).WithDevice(device).BuildAsync();
|
||||
|
||||
Assert.Null(result.TrustedDeviceOption);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenDeviceIsTrusted_ShouldReturnKeys(SsoConfig ssoConfig, SsoConfigurationData configurationData, Device device)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(true);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
device.EncryptedPrivateKey = "encryptedPrivateKey";
|
||||
device.EncryptedPublicKey = "encryptedPublicKey";
|
||||
device.EncryptedUserKey = "encryptedUserKey";
|
||||
|
||||
var result = await _builder.WithSso(ssoConfig).WithDevice(device).BuildAsync();
|
||||
|
||||
Assert.Equal(device.EncryptedPrivateKey, result.TrustedDeviceOption?.EncryptedPrivateKey);
|
||||
Assert.Equal(device.EncryptedUserKey, result.TrustedDeviceOption?.EncryptedUserKey);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenHasLoginApprovingDevice_ShouldApprovingDeviceTrue(SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(true);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
approvingDevice.Type = LoginApprovingDeviceTypes.Types.First();
|
||||
_deviceRepository.GetManyByUserIdAsync(user.Id).Returns(new Device[] { approvingDevice });
|
||||
|
||||
var result = await _builder.ForUser(user).WithSso(ssoConfig).WithDevice(device).BuildAsync();
|
||||
|
||||
Assert.True(result.TrustedDeviceOption?.HasLoginApprovingDevice);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenManageResetPasswordPermissions_ShouldReturnHasManageResetPasswordPermissionTrue(
|
||||
SsoConfig ssoConfig,
|
||||
SsoConfigurationData configurationData,
|
||||
CurrentContextOrganization organization)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(true);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
ssoConfig.OrganizationId = organization.Id;
|
||||
_currentContext.Organizations.Returns(new List<CurrentContextOrganization>(new CurrentContextOrganization[] { organization }));
|
||||
_currentContext.ManageResetPassword(organization.Id).Returns(true);
|
||||
|
||||
var result = await _builder.WithSso(ssoConfig).BuildAsync();
|
||||
|
||||
Assert.True(result.TrustedDeviceOption?.HasManageResetPasswordPermission);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Build_WhenUserHasEnrolledIntoPasswordReset_ShouldReturnHasAdminApprovalTrue(
|
||||
SsoConfig ssoConfig,
|
||||
SsoConfigurationData configurationData,
|
||||
OrganizationUser organizationUser,
|
||||
User user)
|
||||
{
|
||||
_featureService.IsEnabled(FeatureFlagKeys.TrustedDeviceEncryption, _currentContext).Returns(true);
|
||||
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
|
||||
ssoConfig.Data = configurationData.Serialize();
|
||||
organizationUser.ResetPasswordKey = "resetPasswordKey";
|
||||
_organizationUserRepository.GetByOrganizationAsync(ssoConfig.OrganizationId, user.Id).Returns(organizationUser);
|
||||
|
||||
var result = await _builder.ForUser(user).WithSso(ssoConfig).BuildAsync();
|
||||
|
||||
Assert.True(result.TrustedDeviceOption?.HasAdminApproval);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user