Browse Source

pm-24210-v3 (#6148) + test merge conflict fix in base request validator tests

Jared Snider 5 months ago committed by Jared Snider
parent
commit
40f04ab832
No known key found for this signature in database
GPG Key ID: A149DDD612516286
  1. 7
      src/Identity/IdentityServer/CustomValidatorRequestContext.cs
  2. 18
      src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs
  3. 6
      src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs
  4. 10
      src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs
  5. 6
      src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs
  6. 100
      test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
  7. 6
      test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs

7
src/Identity/IdentityServer/CustomValidatorRequestContext.cs

@ -1,6 +1,7 @@
// FIXME: Update this file to be null safe and then delete the line below // FIXME: Update this file to be null safe and then delete the line below
#nullable disable #nullable disable
using Bit.Core.Auth.Entities;
using Bit.Core.Entities; using Bit.Core.Entities;
using Duende.IdentityServer.Validation; using Duende.IdentityServer.Validation;
@ -41,4 +42,10 @@ public class CustomValidatorRequestContext
/// This will be null if the authentication request is successful. /// This will be null if the authentication request is successful.
/// </summary> /// </summary>
public Dictionary<string, object> CustomResponse { get; set; } public Dictionary<string, object> CustomResponse { get; set; }
/// <summary>
/// A validated auth request
/// <see cref="AuthRequest.IsValidForAuthentication"/>
/// </summary>
public AuthRequest ValidatedAuthRequest { get; set; }
} }

18
src/Identity/IdentityServer/RequestValidators/BaseRequestValidator.cs

@ -35,6 +35,7 @@ public abstract class BaseRequestValidator<T> where T : class
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly GlobalSettings _globalSettings; private readonly GlobalSettings _globalSettings;
private readonly IUserRepository _userRepository; private readonly IUserRepository _userRepository;
private readonly IAuthRequestRepository _authRequestRepository;
protected ICurrentContext CurrentContext { get; } protected ICurrentContext CurrentContext { get; }
protected IPolicyService PolicyService { get; } protected IPolicyService PolicyService { get; }
@ -59,7 +60,9 @@ public abstract class BaseRequestValidator<T> where T : class
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IPolicyRequirementQuery policyRequirementQuery) IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository
)
{ {
_userManager = userManager; _userManager = userManager;
_userService = userService; _userService = userService;
@ -76,6 +79,7 @@ public abstract class BaseRequestValidator<T> where T : class
SsoConfigRepository = ssoConfigRepository; SsoConfigRepository = ssoConfigRepository;
UserDecryptionOptionsBuilder = userDecryptionOptionsBuilder; UserDecryptionOptionsBuilder = userDecryptionOptionsBuilder;
PolicyRequirementQuery = policyRequirementQuery; PolicyRequirementQuery = policyRequirementQuery;
_authRequestRepository = authRequestRepository;
} }
protected async Task ValidateAsync(T context, ValidatedTokenRequest request, protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
@ -190,6 +194,14 @@ public abstract class BaseRequestValidator<T> where T : class
return; return;
} }
// TODO: PM-24324 - This should be its own validator at some point.
// 6. Auth request handling
if (validatorContext.ValidatedAuthRequest != null)
{
validatorContext.ValidatedAuthRequest.AuthenticationDate = DateTime.UtcNow;
await _authRequestRepository.ReplaceAsync(validatorContext.ValidatedAuthRequest);
}
await BuildSuccessResultAsync(user, context, validatorContext.Device, returnRememberMeToken); await BuildSuccessResultAsync(user, context, validatorContext.Device, returnRememberMeToken);
} }
@ -404,8 +416,8 @@ public abstract class BaseRequestValidator<T> where T : class
/// <summary> /// <summary>
/// Builds the custom response that will be sent to the client upon successful authentication, which /// Builds the custom response that will be sent to the client upon successful authentication, which
/// includes the information needed for the client to initialize the user's account in state. /// includes the information needed for the client to initialize the user's account in state.
/// </summary> /// </summary>
/// <param name="user">The authenticated user.</param> /// <param name="user">The authenticated user.</param>
/// <param name="context">The current request context.</param> /// <param name="context">The current request context.</param>
/// <param name="device">The device used for authentication.</param> /// <param name="device">The device used for authentication.</param>
/// <param name="sendRememberToken">Whether to send a 2FA remember token.</param> /// <param name="sendRememberToken">Whether to send a 2FA remember token.</param>

6
src/Identity/IdentityServer/RequestValidators/CustomTokenRequestValidator.cs

@ -45,7 +45,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IUpdateInstallationCommand updateInstallationCommand, IUpdateInstallationCommand updateInstallationCommand,
IPolicyRequirementQuery policyRequirementQuery) IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository)
: base( : base(
userManager, userManager,
userService, userService,
@ -61,7 +62,8 @@ public class CustomTokenRequestValidator : BaseRequestValidator<CustomTokenReque
featureService, featureService,
ssoConfigRepository, ssoConfigRepository,
userDecryptionOptionsBuilder, userDecryptionOptionsBuilder,
policyRequirementQuery) policyRequirementQuery,
authRequestRepository)
{ {
_userManager = userManager; _userManager = userManager;
_updateInstallationCommand = updateInstallationCommand; _updateInstallationCommand = updateInstallationCommand;

10
src/Identity/IdentityServer/RequestValidators/ResourceOwnerPasswordValidator.cs

@ -56,7 +56,8 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
featureService, featureService,
ssoConfigRepository, ssoConfigRepository,
userDecryptionOptionsBuilder, userDecryptionOptionsBuilder,
policyRequirementQuery) policyRequirementQuery,
authRequestRepository)
{ {
_userManager = userManager; _userManager = userManager;
_currentContext = currentContext; _currentContext = currentContext;
@ -108,8 +109,11 @@ public class ResourceOwnerPasswordValidator : BaseRequestValidator<ResourceOwner
// Auth request is non-null so validate it // Auth request is non-null so validate it
if (authRequest.IsValidForAuthentication(validatorContext.User.Id, context.Password)) if (authRequest.IsValidForAuthentication(validatorContext.User.Id, context.Password))
{ {
authRequest.AuthenticationDate = DateTime.UtcNow; // We save the validated auth request so that we can set it's authentication date
await _authRequestRepository.ReplaceAsync(authRequest); // later on only upon successful authentication.
// For example, 2FA requires a resubmission so we can't mark the auth request
// as authenticated here.
validatorContext.ValidatedAuthRequest = authRequest;
return true; return true;
} }

6
src/Identity/IdentityServer/RequestValidators/WebAuthnGrantValidator.cs

@ -48,7 +48,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
IFeatureService featureService, IFeatureService featureService,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand, IAssertWebAuthnLoginCredentialCommand assertWebAuthnLoginCredentialCommand,
IPolicyRequirementQuery policyRequirementQuery) IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository)
: base( : base(
userManager, userManager,
userService, userService,
@ -64,7 +65,8 @@ public class WebAuthnGrantValidator : BaseRequestValidator<ExtensionGrantValidat
featureService, featureService,
ssoConfigRepository, ssoConfigRepository,
userDecryptionOptionsBuilder, userDecryptionOptionsBuilder,
policyRequirementQuery) policyRequirementQuery,
authRequestRepository)
{ {
_assertionOptionsDataProtector = assertionOptionsDataProtector; _assertionOptionsDataProtector = assertionOptionsDataProtector;
_assertWebAuthnLoginCredentialCommand = assertWebAuthnLoginCredentialCommand; _assertWebAuthnLoginCredentialCommand = assertWebAuthnLoginCredentialCommand;

100
test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs

@ -3,6 +3,8 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.Services; using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Repositories; using Bit.Core.Auth.Repositories;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Entities; using Bit.Core.Entities;
@ -42,6 +44,7 @@ public class BaseRequestValidatorTests
private readonly ISsoConfigRepository _ssoConfigRepository; private readonly ISsoConfigRepository _ssoConfigRepository;
private readonly IUserDecryptionOptionsBuilder _userDecryptionOptionsBuilder; private readonly IUserDecryptionOptionsBuilder _userDecryptionOptionsBuilder;
private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IAuthRequestRepository _authRequestRepository;
private readonly BaseRequestValidatorTestWrapper _sut; private readonly BaseRequestValidatorTestWrapper _sut;
@ -62,6 +65,7 @@ public class BaseRequestValidatorTests
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>(); _ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
_userDecryptionOptionsBuilder = Substitute.For<IUserDecryptionOptionsBuilder>(); _userDecryptionOptionsBuilder = Substitute.For<IUserDecryptionOptionsBuilder>();
_policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>(); _policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>();
_authRequestRepository = Substitute.For<IAuthRequestRepository>();
_sut = new BaseRequestValidatorTestWrapper( _sut = new BaseRequestValidatorTestWrapper(
_userManager, _userManager,
@ -78,7 +82,8 @@ public class BaseRequestValidatorTests
_featureService, _featureService,
_ssoConfigRepository, _ssoConfigRepository,
_userDecryptionOptionsBuilder, _userDecryptionOptionsBuilder,
_policyRequirementQuery); _policyRequirementQuery,
_authRequestRepository);
} }
/* Logic path /* Logic path
@ -175,6 +180,99 @@ public class BaseRequestValidatorTests
Assert.False(context.GrantResult.IsError); Assert.False(context.GrantResult.IsError);
} }
[Theory, BitAutoData]
public async Task ValidateAsync_ValidatedAuthRequest_ConsumedOnSuccess(
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
CustomValidatorRequestContext requestContext,
GrantValidationResult grantResult)
{
// Arrange
var context = CreateContext(tokenRequest, requestContext, grantResult);
// 1 -> to pass
_sut.isValid = true;
var authRequest = new AuthRequest
{
Type = AuthRequestType.AuthenticateAndUnlock,
RequestDeviceIdentifier = "",
RequestIpAddress = "1.1.1.1",
AccessCode = "password",
PublicKey = "test_public_key",
CreationDate = DateTime.UtcNow.AddMinutes(-5),
ResponseDate = DateTime.UtcNow.AddMinutes(-2),
Approved = true,
AuthenticationDate = null, // unused
UserId = requestContext.User.Id,
};
requestContext.ValidatedAuthRequest = authRequest;
// 2 -> will result to false with no extra configuration
// 3 -> set two factor to be false
_twoFactorAuthenticationValidator
.RequiresTwoFactorAsync(Arg.Any<User>(), tokenRequest)
.Returns(Task.FromResult(new Tuple<bool, Organization>(false, null)));
// 4 -> set up device validator to pass
_deviceValidator.ValidateRequestDeviceAsync(Arg.Any<ValidatedTokenRequest>(), Arg.Any<CustomValidatorRequestContext>())
.Returns(Task.FromResult(true));
// 5 -> not legacy user
_userService.IsLegacyUser(Arg.Any<string>())
.Returns(false);
// Act
await _sut.ValidateAsync(context);
// Assert
Assert.False(context.GrantResult.IsError);
// Check that the auth request was consumed
await _authRequestRepository.Received(1).ReplaceAsync(Arg.Is<AuthRequest>(ar =>
ar.AuthenticationDate.HasValue));
}
[Theory, BitAutoData]
public async Task ValidateAsync_ValidatedAuthRequest_NotConsumed_When2faRequired(
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
CustomValidatorRequestContext requestContext,
GrantValidationResult grantResult)
{
// Arrange
var context = CreateContext(tokenRequest, requestContext, grantResult);
// 1 -> to pass
_sut.isValid = true;
var authRequest = new AuthRequest
{
Type = AuthRequestType.AuthenticateAndUnlock,
RequestDeviceIdentifier = "",
RequestIpAddress = "1.1.1.1",
AccessCode = "password",
PublicKey = "test_public_key",
CreationDate = DateTime.UtcNow.AddMinutes(-5),
ResponseDate = DateTime.UtcNow.AddMinutes(-2),
Approved = true,
AuthenticationDate = null, // unused
UserId = requestContext.User.Id,
};
requestContext.ValidatedAuthRequest = authRequest;
// 2 -> will result to false with no extra configuration
// 3 -> set two factor to be required
_twoFactorAuthenticationValidator
.RequiresTwoFactorAsync(Arg.Any<User>(), tokenRequest)
.Returns(Task.FromResult(new Tuple<bool, Organization>(true, null)));
// Act
await _sut.ValidateAsync(context);
// Assert we errored for 2fa requirement
Assert.True(context.GrantResult.IsError);
// Assert that the auth request was NOT consumed
await _authRequestRepository.DidNotReceive().ReplaceAsync(Arg.Any<AuthRequest>());
}
// Test grantTypes that require SSO when a user is in an organization that requires it // Test grantTypes that require SSO when a user is in an organization that requires it
[Theory] [Theory]
[BitAutoData("password")] [BitAutoData("password")]

6
test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs

@ -62,7 +62,8 @@ IBaseRequestValidatorTestWrapper
IFeatureService featureService, IFeatureService featureService,
ISsoConfigRepository ssoConfigRepository, ISsoConfigRepository ssoConfigRepository,
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder, IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder,
IPolicyRequirementQuery policyRequirementQuery) : IPolicyRequirementQuery policyRequirementQuery,
IAuthRequestRepository authRequestRepository) :
base( base(
userManager, userManager,
userService, userService,
@ -78,7 +79,8 @@ IBaseRequestValidatorTestWrapper
featureService, featureService,
ssoConfigRepository, ssoConfigRepository,
userDecryptionOptionsBuilder, userDecryptionOptionsBuilder,
policyRequirementQuery) policyRequirementQuery,
authRequestRepository)
{ {
} }

Loading…
Cancel
Save