mirror of
https://github.com/bitwarden/server.git
synced 2024-11-27 13:05:23 +01:00
[PM-6664] Base Request Validator Unit Tests and Resource Owner integration Tests (#4582)
* intial commit * Some UnitTests for the VerifyAsync flows * WIP org two factor * removed useless tests * added ResourceOwnerValidation integration tests * fixing formatting * addressing comments * removed comment
This commit is contained in:
parent
64a7cba013
commit
fa5d6712c5
@ -101,7 +101,7 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
protected async Task ValidateAsync(T context, ValidatedTokenRequest request,
|
||||||
CustomValidatorRequestContext validatorContext)
|
CustomValidatorRequestContext validatorContext)
|
||||||
{
|
{
|
||||||
var isBot = (validatorContext.CaptchaResponse?.IsBot ?? false);
|
var isBot = validatorContext.CaptchaResponse?.IsBot ?? false;
|
||||||
if (isBot)
|
if (isBot)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(Constants.BypassFiltersEventId,
|
_logger.LogInformation(Constants.BypassFiltersEventId,
|
||||||
@ -621,6 +621,13 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// checks to see if a user is trying to log into a new device
|
||||||
|
/// and has reached the maximum number of failed login attempts.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unknownDevice">boolean</param>
|
||||||
|
/// <param name="user">current user</param>
|
||||||
|
/// <returns></returns>
|
||||||
private bool ValidateFailedAuthEmailConditions(bool unknownDevice, User user)
|
private bool ValidateFailedAuthEmailConditions(bool unknownDevice, User user)
|
||||||
{
|
{
|
||||||
var failedLoginCeiling = _globalSettings.Captcha.MaximumFailedLoginAttempts;
|
var failedLoginCeiling = _globalSettings.Captcha.MaximumFailedLoginAttempts;
|
||||||
|
@ -0,0 +1,272 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Auth.Entities;
|
||||||
|
using Bit.Core.Auth.Enums;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Identity.Models.Request.Accounts;
|
||||||
|
using Bit.IntegrationTestCommon.Factories;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Bit.Test.Common.Helpers;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Identity.IntegrationTest.RequestValidation;
|
||||||
|
|
||||||
|
public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplicationFactory>
|
||||||
|
{
|
||||||
|
private const string DefaultPassword = "master_password_hash";
|
||||||
|
private const string DefaultUsername = "test@email.qa";
|
||||||
|
private const string DefaultDeviceIdentifier = "test_identifier";
|
||||||
|
private readonly IdentityApplicationFactory _factory;
|
||||||
|
private readonly UserManager<User> _userManager;
|
||||||
|
private readonly IAuthRequestRepository _authRequestRepository;
|
||||||
|
|
||||||
|
public ResourceOwnerPasswordValidatorTests(IdentityApplicationFactory factory)
|
||||||
|
{
|
||||||
|
_factory = factory;
|
||||||
|
|
||||||
|
_userManager = _factory.GetService<UserManager<User>>();
|
||||||
|
_authRequestRepository = _factory.GetService<IAuthRequestRepository>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateAsync_Success()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await EnsureUserCreatedAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync("/connect/token",
|
||||||
|
GetFormUrlEncodedContent(),
|
||||||
|
context => context.SetAuthEmail(DefaultUsername));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var token = AssertHelper.AssertJsonProperty(root, "access_token", JsonValueKind.String).GetString();
|
||||||
|
Assert.NotNull(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateAsync_AuthEmailHeaderInvalid_InvalidGrantResponse()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await EnsureUserCreatedAsync();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync(
|
||||||
|
"/connect/token",
|
||||||
|
GetFormUrlEncodedContent()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var error = AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String).GetString();
|
||||||
|
Assert.Equal("Auth-Email header invalid.", error);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_UserNull_Failure(string username)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync("/connect/token",
|
||||||
|
GetFormUrlEncodedContent(username: username),
|
||||||
|
context => context.SetAuthEmail(username));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var errorModel = AssertHelper.AssertJsonProperty(root, "ErrorModel", JsonValueKind.Object);
|
||||||
|
var errorMessage = AssertHelper.AssertJsonProperty(errorModel, "Message", JsonValueKind.String).GetString();
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// I would have liked to spy into the IUserService but by spying into the IUserService it
|
||||||
|
/// creates a Singleton that is not available to the UserManager<User> thus causing the
|
||||||
|
/// RegisterAsync() to create a the user in a different UserStore than the one the
|
||||||
|
/// UserManager<User> has access to. This is an assumption made from observing the behavior while
|
||||||
|
/// writing theses tests. I could be wrong.
|
||||||
|
///
|
||||||
|
/// For the time being, verifying that the user is not null confirms that the failure is due to
|
||||||
|
/// a bad password.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="badPassword">random password</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_BadPassword_Failure(string badPassword)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
await EnsureUserCreatedAsync();
|
||||||
|
|
||||||
|
// Verify the User is not null to ensure the failure is due to bad password
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync("/connect/token",
|
||||||
|
GetFormUrlEncodedContent(password: badPassword),
|
||||||
|
context => context.SetAuthEmail(DefaultUsername));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.NotNull(await _userManager.FindByEmailAsync(DefaultUsername));
|
||||||
|
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var errorModel = AssertHelper.AssertJsonProperty(root, "ErrorModel", JsonValueKind.Object);
|
||||||
|
var errorMessage = AssertHelper.AssertJsonProperty(errorModel, "Message", JsonValueKind.String).GetString();
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateAsync_ValidateContextAsync_AuthRequest_NotNull_AgeLessThanOneHour_Success()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Ensure User
|
||||||
|
await EnsureUserCreatedAsync();
|
||||||
|
var user = await _userManager.FindByEmailAsync(DefaultUsername);
|
||||||
|
Assert.NotNull(user);
|
||||||
|
|
||||||
|
// Connect Request to User and set CreationDate
|
||||||
|
var authRequest = CreateAuthRequest(
|
||||||
|
user.Id,
|
||||||
|
AuthRequestType.AuthenticateAndUnlock,
|
||||||
|
DateTime.UtcNow.AddMinutes(-30)
|
||||||
|
);
|
||||||
|
await _authRequestRepository.CreateAsync(authRequest);
|
||||||
|
|
||||||
|
var expectedAuthRequest = await _authRequestRepository.GetManyByUserIdAsync(user.Id);
|
||||||
|
Assert.NotEmpty(expectedAuthRequest);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync("/connect/token",
|
||||||
|
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "scope", "api offline_access" },
|
||||||
|
{ "client_id", "web" },
|
||||||
|
{ "deviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||||
|
{ "deviceIdentifier", DefaultDeviceIdentifier },
|
||||||
|
{ "deviceName", "firefox" },
|
||||||
|
{ "grant_type", "password" },
|
||||||
|
{ "username", DefaultUsername },
|
||||||
|
{ "password", DefaultPassword },
|
||||||
|
{ "AuthRequest", authRequest.Id.ToString().ToLowerInvariant() }
|
||||||
|
}), context => context.SetAuthEmail(DefaultUsername));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var token = AssertHelper.AssertJsonProperty(root, "access_token", JsonValueKind.String).GetString();
|
||||||
|
Assert.NotNull(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ValidateAsync_ValidateContextAsync_AuthRequest_NotNull_AgeGreaterThanOneHour_Failure()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
// Ensure User
|
||||||
|
await EnsureUserCreatedAsync(_factory);
|
||||||
|
var user = await _userManager.FindByEmailAsync(DefaultUsername);
|
||||||
|
Assert.NotNull(user);
|
||||||
|
|
||||||
|
// Create AuthRequest
|
||||||
|
var authRequest = CreateAuthRequest(
|
||||||
|
user.Id,
|
||||||
|
AuthRequestType.AuthenticateAndUnlock,
|
||||||
|
DateTime.UtcNow.AddMinutes(-61)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var context = await _factory.Server.PostAsync("/connect/token",
|
||||||
|
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "scope", "api offline_access" },
|
||||||
|
{ "client_id", "web" },
|
||||||
|
{ "deviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||||
|
{ "deviceIdentifier", DefaultDeviceIdentifier },
|
||||||
|
{ "deviceName", "firefox" },
|
||||||
|
{ "grant_type", "password" },
|
||||||
|
{ "username", DefaultUsername },
|
||||||
|
{ "password", DefaultPassword },
|
||||||
|
{ "AuthRequest", authRequest.Id.ToString().ToLowerInvariant() }
|
||||||
|
}), context => context.SetAuthEmail(DefaultUsername));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
|
||||||
|
/*
|
||||||
|
An improvement on the current failure flow would be to document which part of
|
||||||
|
the flow failed since all of the failures are basically the same.
|
||||||
|
This doesn't build confidence in the tests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
|
||||||
|
var root = body.RootElement;
|
||||||
|
|
||||||
|
var errorModel = AssertHelper.AssertJsonProperty(root, "ErrorModel", JsonValueKind.Object);
|
||||||
|
var errorMessage = AssertHelper.AssertJsonProperty(errorModel, "Message", JsonValueKind.String).GetString();
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task EnsureUserCreatedAsync(IdentityApplicationFactory factory = null)
|
||||||
|
{
|
||||||
|
factory ??= _factory;
|
||||||
|
// No need to create more users than we need
|
||||||
|
if (await _userManager.FindByEmailAsync(DefaultUsername) == null)
|
||||||
|
{
|
||||||
|
// Register user
|
||||||
|
await factory.RegisterAsync(new RegisterRequestModel
|
||||||
|
{
|
||||||
|
Email = DefaultUsername,
|
||||||
|
MasterPasswordHash = DefaultPassword
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FormUrlEncodedContent GetFormUrlEncodedContent(
|
||||||
|
string deviceId = null, string username = null, string password = null)
|
||||||
|
{
|
||||||
|
return new FormUrlEncodedContent(new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "scope", "api offline_access" },
|
||||||
|
{ "client_id", "web" },
|
||||||
|
{ "deviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||||
|
{ "deviceIdentifier", deviceId ?? DefaultDeviceIdentifier },
|
||||||
|
{ "deviceName", "firefox" },
|
||||||
|
{ "grant_type", "password" },
|
||||||
|
{ "username", username ?? DefaultUsername },
|
||||||
|
{ "password", password ?? DefaultPassword },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string DeviceTypeAsString(DeviceType deviceType)
|
||||||
|
{
|
||||||
|
return ((int)deviceType).ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AuthRequest CreateAuthRequest(
|
||||||
|
Guid userId,
|
||||||
|
AuthRequestType authRequestType,
|
||||||
|
DateTime creationDate,
|
||||||
|
bool? approved = null,
|
||||||
|
DateTime? responseDate = null)
|
||||||
|
{
|
||||||
|
return new AuthRequest
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Type = authRequestType,
|
||||||
|
Approved = approved,
|
||||||
|
RequestDeviceIdentifier = DefaultDeviceIdentifier,
|
||||||
|
RequestIpAddress = "1.1.1.1",
|
||||||
|
AccessCode = DefaultPassword,
|
||||||
|
PublicKey = "test_public_key",
|
||||||
|
CreationDate = creationDate,
|
||||||
|
ResponseDate = responseDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
31
test/Identity.Test/AutoFixture/RequestValidationFixtures.cs
Normal file
31
test/Identity.Test/AutoFixture/RequestValidationFixtures.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using AutoFixture;
|
||||||
|
using AutoFixture.Xunit2;
|
||||||
|
using Duende.IdentityServer.Validation;
|
||||||
|
|
||||||
|
namespace Bit.Identity.Test.AutoFixture;
|
||||||
|
|
||||||
|
internal class ValidatedTokenRequestCustomization : ICustomization
|
||||||
|
{
|
||||||
|
public ValidatedTokenRequestCustomization()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Customize(IFixture fixture)
|
||||||
|
{
|
||||||
|
fixture.Customize<ValidatedTokenRequest>(composer => composer
|
||||||
|
.With(o => o.RefreshToken, () => null)
|
||||||
|
.With(o => o.ClientClaims, [])
|
||||||
|
.With(o => o.Options, new Duende.IdentityServer.Configuration.IdentityServerOptions()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ValidatedTokenRequestAttribute : CustomizeAttribute
|
||||||
|
{
|
||||||
|
public ValidatedTokenRequestAttribute()
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||||
|
{
|
||||||
|
return new ValidatedTokenRequestCustomization();
|
||||||
|
}
|
||||||
|
}
|
400
test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
Normal file
400
test/Identity.Test/IdentityServer/BaseRequestValidatorTests.cs
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
using Bit.Core;
|
||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
|
using Bit.Core.Auth.Identity;
|
||||||
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.Repositories;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Api;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Tokens;
|
||||||
|
using Bit.Identity.IdentityServer;
|
||||||
|
using Bit.Identity.Test.Wrappers;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Duende.IdentityServer.Validation;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
using AuthFixtures = Bit.Identity.Test.AutoFixture;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Bit.Identity.Test.IdentityServer;
|
||||||
|
|
||||||
|
public class BaseRequestValidatorTests
|
||||||
|
{
|
||||||
|
private UserManager<User> _userManager;
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
private readonly IDeviceService _deviceService;
|
||||||
|
private readonly IUserService _userService;
|
||||||
|
private readonly IEventService _eventService;
|
||||||
|
private readonly IOrganizationDuoWebTokenProvider _organizationDuoWebTokenProvider;
|
||||||
|
private readonly ITemporaryDuoWebV4SDKService _duoWebV4SDKService;
|
||||||
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
|
private readonly IMailService _mailService;
|
||||||
|
private readonly ILogger<BaseRequestValidatorTests> _logger;
|
||||||
|
private readonly ICurrentContext _currentContext;
|
||||||
|
private readonly GlobalSettings _globalSettings;
|
||||||
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly IPolicyService _policyService;
|
||||||
|
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _tokenDataFactory;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly ISsoConfigRepository _ssoConfigRepository;
|
||||||
|
private readonly IUserDecryptionOptionsBuilder _userDecryptionOptionsBuilder;
|
||||||
|
|
||||||
|
private readonly BaseRequestValidatorTestWrapper _sut;
|
||||||
|
|
||||||
|
public BaseRequestValidatorTests()
|
||||||
|
{
|
||||||
|
_deviceRepository = Substitute.For<IDeviceRepository>();
|
||||||
|
_deviceService = Substitute.For<IDeviceService>();
|
||||||
|
_userService = Substitute.For<IUserService>();
|
||||||
|
_eventService = Substitute.For<IEventService>();
|
||||||
|
_organizationDuoWebTokenProvider = Substitute.For<IOrganizationDuoWebTokenProvider>();
|
||||||
|
_duoWebV4SDKService = Substitute.For<ITemporaryDuoWebV4SDKService>();
|
||||||
|
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||||
|
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||||
|
_applicationCacheService = Substitute.For<IApplicationCacheService>();
|
||||||
|
_mailService = Substitute.For<IMailService>();
|
||||||
|
_logger = Substitute.For<ILogger<BaseRequestValidatorTests>>();
|
||||||
|
_currentContext = Substitute.For<ICurrentContext>();
|
||||||
|
_globalSettings = Substitute.For<GlobalSettings>();
|
||||||
|
_userRepository = Substitute.For<IUserRepository>();
|
||||||
|
_policyService = Substitute.For<IPolicyService>();
|
||||||
|
_tokenDataFactory = Substitute.For<IDataProtectorTokenFactory<SsoEmail2faSessionTokenable>>();
|
||||||
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
|
_ssoConfigRepository = Substitute.For<ISsoConfigRepository>();
|
||||||
|
_userDecryptionOptionsBuilder = Substitute.For<IUserDecryptionOptionsBuilder>();
|
||||||
|
_userManager = SubstituteUserManager();
|
||||||
|
|
||||||
|
_sut = new BaseRequestValidatorTestWrapper(
|
||||||
|
_userManager,
|
||||||
|
_deviceRepository,
|
||||||
|
_deviceService,
|
||||||
|
_userService,
|
||||||
|
_eventService,
|
||||||
|
_organizationDuoWebTokenProvider,
|
||||||
|
_duoWebV4SDKService,
|
||||||
|
_organizationRepository,
|
||||||
|
_organizationUserRepository,
|
||||||
|
_applicationCacheService,
|
||||||
|
_mailService,
|
||||||
|
_logger,
|
||||||
|
_currentContext,
|
||||||
|
_globalSettings,
|
||||||
|
_userRepository,
|
||||||
|
_policyService,
|
||||||
|
_tokenDataFactory,
|
||||||
|
_featureService,
|
||||||
|
_ssoConfigRepository,
|
||||||
|
_userDecryptionOptionsBuilder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> _Logger.LogInformation
|
||||||
|
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||||
|
|-> SetErrorResult
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_IsBot_UserNotNull_ShouldBuildErrorResult_ShouldLogFailedLoginEvent(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = true;
|
||||||
|
_sut.isValid = true;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await _eventService.Received(1)
|
||||||
|
.LogUserEventAsync(context.CustomValidatorRequestContext.User.Id,
|
||||||
|
Core.Enums.EventType.User_FailedLogIn);
|
||||||
|
Assert.True(context.GrantResult.IsError);
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||||
|
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||||
|
(self hosted) |-> _logger.LogWarning()
|
||||||
|
|-> SetErrorResult
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_ContextNotValid_SelfHosted_ShouldBuildErrorResult_ShouldLogWarning(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
_globalSettings.Captcha.Returns(new GlobalSettings.CaptchaSettings());
|
||||||
|
_globalSettings.SelfHosted = true;
|
||||||
|
_sut.isValid = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
_logger.Received(1).LogWarning(Constants.BypassFiltersEventId, "Failed login attempt. ");
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||||
|
|-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||||
|
|-> SetErrorResult
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_ContextNotValid_MaxAttemptLogin_ShouldSendEmail(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
// This needs to be n-1 of the max failed login attempts
|
||||||
|
context.CustomValidatorRequestContext.User.FailedLoginCount = 2;
|
||||||
|
context.CustomValidatorRequestContext.KnownDevice = false;
|
||||||
|
|
||||||
|
_globalSettings.Captcha.Returns(
|
||||||
|
new GlobalSettings.CaptchaSettings
|
||||||
|
{
|
||||||
|
MaximumFailedLoginAttempts = 3
|
||||||
|
});
|
||||||
|
_sut.isValid = false;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await _mailService.Received(1)
|
||||||
|
.SendFailedLoginAttemptsEmailAsync(
|
||||||
|
Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
||||||
|
Assert.True(context.GrantResult.IsError);
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
Assert.Equal("Username or password is incorrect. Try again.", errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildErrorResult
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_AuthCodeGrantType_DeviceNull_ShouldError(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
_sut.isValid = true;
|
||||||
|
|
||||||
|
context.ValidatedTokenRequest.GrantType = "authorization_code";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.GrantResult.IsError);
|
||||||
|
Assert.Equal("No device information provided.", errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildSuccessResultAsync
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_ClientCredentialsGrantType_ShouldSucceed(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
_sut.isValid = true;
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.User.CreationDate = DateTime.UtcNow - TimeSpan.FromDays(1);
|
||||||
|
_globalSettings.DisableEmailNewDevice = false;
|
||||||
|
|
||||||
|
context.ValidatedTokenRequest.GrantType = "client_credentials"; // This || AuthCode will allow process to continue to get device
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceType"] = "Android"; // This needs to be an actual Type
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceName"] = "DeviceName";
|
||||||
|
context.ValidatedTokenRequest.Raw["DevicePushToken"] = "DevicePushToken";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await _mailService.Received(1).SendNewDeviceLoggedInEmail(
|
||||||
|
context.CustomValidatorRequestContext.User.Email, "Android", Arg.Any<DateTime>(), Arg.Any<string>()
|
||||||
|
);
|
||||||
|
Assert.False(context.GrantResult.IsError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> IsValidAuthTypeAsync -> SaveDeviceAsync -> BuildSuccessResultAsync
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_ClientCredentialsGrantType_ExistingDevice_ShouldSucceed(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
_sut.isValid = true;
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.User.CreationDate = DateTime.UtcNow - TimeSpan.FromDays(1);
|
||||||
|
_globalSettings.DisableEmailNewDevice = false;
|
||||||
|
|
||||||
|
context.ValidatedTokenRequest.GrantType = "client_credentials"; // This || AuthCode will allow process to continue to get device
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceType"] = "Android"; // This needs to be an actual Type
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceName"] = "DeviceName";
|
||||||
|
context.ValidatedTokenRequest.Raw["DevicePushToken"] = "DevicePushToken";
|
||||||
|
|
||||||
|
_deviceRepository.GetByIdentifierAsync("DeviceIdentifier", Arg.Any<Guid>())
|
||||||
|
.Returns(new Device() { Identifier = "DeviceIdentifier" });
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await _eventService.LogUserEventAsync(
|
||||||
|
context.CustomValidatorRequestContext.User.Id, EventType.User_LoggedIn);
|
||||||
|
await _userRepository.Received(1).ReplaceAsync(Arg.Any<User>());
|
||||||
|
|
||||||
|
Assert.False(context.GrantResult.IsError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Logic path
|
||||||
|
ValidateAsync -> IsLegacyUser -> BuildErrorResultAsync
|
||||||
|
*/
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_InvalidAuthType_ShouldSetSsoResult(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
||||||
|
context.ValidatedTokenRequest.Raw["DevicePushToken"] = "DevicePushToken";
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceName"] = "DeviceName";
|
||||||
|
context.ValidatedTokenRequest.Raw["DeviceType"] = "Android"; // This needs to be an actual Type
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
_sut.isValid = true;
|
||||||
|
|
||||||
|
context.ValidatedTokenRequest.GrantType = "";
|
||||||
|
|
||||||
|
_policyService.AnyPoliciesApplicableToUserAsync(
|
||||||
|
Arg.Any<Guid>(), PolicyType.RequireSso, OrganizationUserStatusType.Confirmed)
|
||||||
|
.Returns(Task.FromResult(true));
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.GrantResult.IsError);
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
Assert.Equal("SSO authentication is required.", errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ValidateAsync_IsLegacyUser_FailAuthForLegacyUserAsync(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
var user = context.CustomValidatorRequestContext.User;
|
||||||
|
user.Key = null;
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
context.ValidatedTokenRequest.ClientId = "Not Web";
|
||||||
|
_sut.isValid = true;
|
||||||
|
_featureService.IsEnabled(FeatureFlagKeys.BlockLegacyUsers).Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(context.GrantResult.IsError);
|
||||||
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
Assert.Equal($"Encryption key migration is required. Please log in to the web vault at {_globalSettings.BaseServiceUri.VaultWithHash}"
|
||||||
|
, errorResponse.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RequiresTwoFactorAsync_ClientCredentialsGrantType_ShouldReturnFalse(
|
||||||
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
|
context.CustomValidatorRequestContext.CaptchaResponse.IsBot = false;
|
||||||
|
context.ValidatedTokenRequest.GrantType = "client_credentials";
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await _sut.TestRequiresTwoFactorAsync(
|
||||||
|
context.CustomValidatorRequestContext.User,
|
||||||
|
context.ValidatedTokenRequest);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result.Item1);
|
||||||
|
Assert.Null(result.Item2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BaseRequestValidationContextFake CreateContext(
|
||||||
|
ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
return new BaseRequestValidationContextFake(
|
||||||
|
tokenRequest,
|
||||||
|
requestContext,
|
||||||
|
grantResult
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserManager<User> SubstituteUserManager()
|
||||||
|
{
|
||||||
|
return new UserManager<User>(Substitute.For<IUserStore<User>>(),
|
||||||
|
Substitute.For<IOptions<IdentityOptions>>(),
|
||||||
|
Substitute.For<IPasswordHasher<User>>(),
|
||||||
|
Enumerable.Empty<IUserValidator<User>>(),
|
||||||
|
Enumerable.Empty<IPasswordValidator<User>>(),
|
||||||
|
Substitute.For<ILookupNormalizer>(),
|
||||||
|
Substitute.For<IdentityErrorDescriber>(),
|
||||||
|
Substitute.For<IServiceProvider>(),
|
||||||
|
Substitute.For<ILogger<UserManager<User>>>());
|
||||||
|
}
|
||||||
|
}
|
152
test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs
Normal file
152
test/Identity.Test/Wrappers/BaseRequestValidatorTestWrapper.cs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
|
using Bit.Core.Auth.Identity;
|
||||||
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.Repositories;
|
||||||
|
using Bit.Core.Context;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Core.Settings;
|
||||||
|
using Bit.Core.Tokens;
|
||||||
|
using Bit.Identity.IdentityServer;
|
||||||
|
using Duende.IdentityServer.Models;
|
||||||
|
using Duende.IdentityServer.Validation;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Bit.Identity.Test.Wrappers;
|
||||||
|
|
||||||
|
public class BaseRequestValidationContextFake
|
||||||
|
{
|
||||||
|
public ValidatedTokenRequest ValidatedTokenRequest;
|
||||||
|
public CustomValidatorRequestContext CustomValidatorRequestContext;
|
||||||
|
public GrantValidationResult GrantResult;
|
||||||
|
|
||||||
|
public BaseRequestValidationContextFake(
|
||||||
|
ValidatedTokenRequest tokenRequest,
|
||||||
|
CustomValidatorRequestContext customValidatorRequestContext,
|
||||||
|
GrantValidationResult grantResult)
|
||||||
|
{
|
||||||
|
ValidatedTokenRequest = tokenRequest;
|
||||||
|
CustomValidatorRequestContext = customValidatorRequestContext;
|
||||||
|
GrantResult = grantResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IBaseRequestValidatorTestWrapper
|
||||||
|
{
|
||||||
|
Task ValidateAsync(BaseRequestValidationContextFake context);
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BaseRequestValidatorTestWrapper : BaseRequestValidator<BaseRequestValidationContextFake>,
|
||||||
|
IBaseRequestValidatorTestWrapper
|
||||||
|
{
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Some of the logic trees call `ValidateContextAsync`. Since this is a test wrapper, we set the return value
|
||||||
|
* of ValidateContextAsync() to whatever we need for the specific test case.
|
||||||
|
*/
|
||||||
|
public bool isValid { get; set; }
|
||||||
|
public BaseRequestValidatorTestWrapper(
|
||||||
|
UserManager<User> userManager,
|
||||||
|
IDeviceRepository deviceRepository,
|
||||||
|
IDeviceService deviceService,
|
||||||
|
IUserService userService,
|
||||||
|
IEventService eventService,
|
||||||
|
IOrganizationDuoWebTokenProvider organizationDuoWebTokenProvider,
|
||||||
|
ITemporaryDuoWebV4SDKService duoWebV4SDKService,
|
||||||
|
IOrganizationRepository organizationRepository,
|
||||||
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
|
IApplicationCacheService applicationCacheService,
|
||||||
|
IMailService mailService,
|
||||||
|
ILogger logger,
|
||||||
|
ICurrentContext currentContext,
|
||||||
|
GlobalSettings globalSettings,
|
||||||
|
IUserRepository userRepository,
|
||||||
|
IPolicyService policyService,
|
||||||
|
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> tokenDataFactory,
|
||||||
|
IFeatureService featureService,
|
||||||
|
ISsoConfigRepository ssoConfigRepository,
|
||||||
|
IUserDecryptionOptionsBuilder userDecryptionOptionsBuilder) :
|
||||||
|
base(
|
||||||
|
userManager,
|
||||||
|
deviceRepository,
|
||||||
|
deviceService,
|
||||||
|
userService,
|
||||||
|
eventService,
|
||||||
|
organizationDuoWebTokenProvider,
|
||||||
|
duoWebV4SDKService,
|
||||||
|
organizationRepository,
|
||||||
|
organizationUserRepository,
|
||||||
|
applicationCacheService,
|
||||||
|
mailService,
|
||||||
|
logger,
|
||||||
|
currentContext,
|
||||||
|
globalSettings,
|
||||||
|
userRepository,
|
||||||
|
policyService,
|
||||||
|
tokenDataFactory,
|
||||||
|
featureService,
|
||||||
|
ssoConfigRepository,
|
||||||
|
userDecryptionOptionsBuilder)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ValidateAsync(
|
||||||
|
BaseRequestValidationContextFake context)
|
||||||
|
{
|
||||||
|
await ValidateAsync(context, context.ValidatedTokenRequest, context.CustomValidatorRequestContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Tuple<bool, Organization>> TestRequiresTwoFactorAsync(
|
||||||
|
User user,
|
||||||
|
ValidatedTokenRequest context)
|
||||||
|
{
|
||||||
|
return await RequiresTwoFactorAsync(user, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ClaimsPrincipal GetSubject(
|
||||||
|
BaseRequestValidationContextFake context)
|
||||||
|
{
|
||||||
|
return context.ValidatedTokenRequest.Subject ?? new ClaimsPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetErrorResult(
|
||||||
|
BaseRequestValidationContextFake context,
|
||||||
|
Dictionary<string, object> customResponse)
|
||||||
|
{
|
||||||
|
context.GrantResult = new GrantValidationResult(TokenRequestErrors.InvalidGrant, customResponse: customResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetSsoResult(
|
||||||
|
BaseRequestValidationContextFake context,
|
||||||
|
Dictionary<string, object> customResponse)
|
||||||
|
{
|
||||||
|
context.GrantResult = new GrantValidationResult(
|
||||||
|
TokenRequestErrors.InvalidGrant, "Sso authentication required.", customResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Task SetSuccessResult(
|
||||||
|
BaseRequestValidationContextFake context,
|
||||||
|
User user,
|
||||||
|
List<Claim> claims,
|
||||||
|
Dictionary<string, object> customResponse)
|
||||||
|
{
|
||||||
|
context.GrantResult = new GrantValidationResult(customResponse: customResponse);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void SetTwoFactorResult(
|
||||||
|
BaseRequestValidationContextFake context,
|
||||||
|
Dictionary<string, object> customResponse)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
protected override Task<bool> ValidateContextAsync(
|
||||||
|
BaseRequestValidationContextFake context,
|
||||||
|
CustomValidatorRequestContext validatorContext)
|
||||||
|
{
|
||||||
|
return Task.FromResult(isValid);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user