mirror of
https://github.com/bitwarden/server
synced 2025-12-06 00:03:34 +00:00
pm-24210-v3 (#6148)
This commit is contained in:
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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.Entities;
|
||||||
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models.Api.Response;
|
using Bit.Core.Auth.Models.Api.Response;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@@ -48,6 +49,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;
|
||||||
|
|
||||||
@@ -68,6 +70,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,
|
||||||
@@ -84,7 +87,8 @@ public class BaseRequestValidatorTests
|
|||||||
_featureService,
|
_featureService,
|
||||||
_ssoConfigRepository,
|
_ssoConfigRepository,
|
||||||
_userDecryptionOptionsBuilder,
|
_userDecryptionOptionsBuilder,
|
||||||
_policyRequirementQuery);
|
_policyRequirementQuery,
|
||||||
|
_authRequestRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Logic path
|
/* Logic path
|
||||||
@@ -181,6 +185,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")]
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user