mirror of
https://github.com/bitwarden/server
synced 2025-12-21 10:43:44 +00:00
refactor(base-request-validator) [PM-28621] Unwind pm-21153 Feature Flag (#6730)
* refactor(base-request-validator) [PM-28621]: Remove feature flagged logic and constant. * refactor(base-request-validator) [PM-28621]: Update tests to reflect unwound feature flag.
This commit is contained in:
@@ -160,7 +160,6 @@ public static class FeatureFlagKeys
|
|||||||
public const string DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods";
|
public const string DisableAlternateLoginMethods = "pm-22110-disable-alternate-login-methods";
|
||||||
public const string PM23174ManageAccountRecoveryPermissionDrivesTheNeedToSetMasterPassword =
|
public const string PM23174ManageAccountRecoveryPermissionDrivesTheNeedToSetMasterPassword =
|
||||||
"pm-23174-manage-account-recovery-permission-drives-the-need-to-set-master-password";
|
"pm-23174-manage-account-recovery-permission-drives-the-need-to-set-master-password";
|
||||||
public const string RecoveryCodeSupportForSsoRequiredUsers = "pm-21153-recovery-code-support-for-sso-required";
|
|
||||||
public const string MJMLBasedEmailTemplates = "mjml-based-email-templates";
|
public const string MJMLBasedEmailTemplates = "mjml-based-email-templates";
|
||||||
public const string MjmlWelcomeEmailTemplates = "pm-21741-mjml-welcome-email";
|
public const string MjmlWelcomeEmailTemplates = "pm-21741-mjml-welcome-email";
|
||||||
public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow";
|
public const string MarketingInitiatedPremiumFlow = "pm-26140-marketing-initiated-premium-flow";
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ 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)
|
||||||
{
|
|
||||||
if (_featureService.IsEnabled(FeatureFlagKeys.RecoveryCodeSupportForSsoRequiredUsers))
|
|
||||||
{
|
{
|
||||||
var validators = DetermineValidationOrder(context, request, validatorContext);
|
var validators = DetermineValidationOrder(context, request, validatorContext);
|
||||||
var allValidationSchemesSuccessful = await ProcessValidatorsAsync(validators);
|
var allValidationSchemesSuccessful = await ProcessValidatorsAsync(validators);
|
||||||
@@ -106,147 +104,10 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
// Each validation task is responsible for setting its own non-success status, if applicable.
|
// Each validation task is responsible for setting its own non-success status, if applicable.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.Device,
|
await BuildSuccessResultAsync(validatorContext.User, context, validatorContext.Device,
|
||||||
validatorContext.RememberMeRequested);
|
validatorContext.RememberMeRequested);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// 1. We need to check if the user's master password hash is correct.
|
|
||||||
var valid = await ValidateContextAsync(context, validatorContext);
|
|
||||||
var user = validatorContext.User;
|
|
||||||
if (!valid)
|
|
||||||
{
|
|
||||||
await UpdateFailedAuthDetailsAsync(user);
|
|
||||||
|
|
||||||
await BuildErrorResultAsync("Username or password is incorrect. Try again.", false, context, user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Decide if this user belongs to an organization that requires SSO.
|
|
||||||
// TODO: Clean up Feature Flag: Remove this if block: PM-28281
|
|
||||||
if (!_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired))
|
|
||||||
{
|
|
||||||
validatorContext.SsoRequired = await RequireSsoLoginAsync(user, request.GrantType);
|
|
||||||
if (validatorContext.SsoRequired)
|
|
||||||
{
|
|
||||||
SetSsoResult(context,
|
|
||||||
new Dictionary<string, object>
|
|
||||||
{
|
|
||||||
{ "ErrorModel", new ErrorResponseModel("SSO authentication is required.") }
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var ssoValid = await _ssoRequestValidator.ValidateAsync(user, request, validatorContext);
|
|
||||||
if (!ssoValid)
|
|
||||||
{
|
|
||||||
// SSO is required
|
|
||||||
SetValidationErrorResult(context, validatorContext);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Check if 2FA is required.
|
|
||||||
(validatorContext.TwoFactorRequired, var twoFactorOrganization) =
|
|
||||||
await _twoFactorAuthenticationValidator.RequiresTwoFactorAsync(user, request);
|
|
||||||
|
|
||||||
// This flag is used to determine if the user wants a rememberMe token sent when
|
|
||||||
// authentication is successful.
|
|
||||||
var returnRememberMeToken = false;
|
|
||||||
|
|
||||||
if (validatorContext.TwoFactorRequired)
|
|
||||||
{
|
|
||||||
var twoFactorToken = request.Raw["TwoFactorToken"];
|
|
||||||
var twoFactorProvider = request.Raw["TwoFactorProvider"];
|
|
||||||
var validTwoFactorRequest = !string.IsNullOrWhiteSpace(twoFactorToken) &&
|
|
||||||
!string.IsNullOrWhiteSpace(twoFactorProvider);
|
|
||||||
|
|
||||||
// 3a. Response for 2FA required and not provided state.
|
|
||||||
if (!validTwoFactorRequest ||
|
|
||||||
!Enum.TryParse(twoFactorProvider, out TwoFactorProviderType twoFactorProviderType))
|
|
||||||
{
|
|
||||||
var resultDict = await _twoFactorAuthenticationValidator
|
|
||||||
.BuildTwoFactorResultAsync(user, twoFactorOrganization);
|
|
||||||
if (resultDict == null)
|
|
||||||
{
|
|
||||||
await BuildErrorResultAsync("No two-step providers enabled.", false, context, user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include Master Password Policy in 2FA response.
|
|
||||||
resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user));
|
|
||||||
SetTwoFactorResult(context, resultDict);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var twoFactorTokenValid =
|
|
||||||
await _twoFactorAuthenticationValidator
|
|
||||||
.VerifyTwoFactorAsync(user, twoFactorOrganization, twoFactorProviderType, twoFactorToken);
|
|
||||||
|
|
||||||
// 3b. Response for 2FA required but request is not valid or remember token expired state.
|
|
||||||
if (!twoFactorTokenValid)
|
|
||||||
{
|
|
||||||
// The remember me token has expired.
|
|
||||||
if (twoFactorProviderType == TwoFactorProviderType.Remember)
|
|
||||||
{
|
|
||||||
var resultDict = await _twoFactorAuthenticationValidator
|
|
||||||
.BuildTwoFactorResultAsync(user, twoFactorOrganization);
|
|
||||||
|
|
||||||
// Include Master Password Policy in 2FA response
|
|
||||||
resultDict.Add("MasterPasswordPolicy", await GetMasterPasswordPolicyAsync(user));
|
|
||||||
SetTwoFactorResult(context, resultDict);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
await SendFailedTwoFactorEmail(user, twoFactorProviderType);
|
|
||||||
await UpdateFailedAuthDetailsAsync(user);
|
|
||||||
await BuildErrorResultAsync("Two-step token is invalid. Try again.", true, context, user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3c. When the 2FA authentication is successful, we can check if the user wants a
|
|
||||||
// rememberMe token.
|
|
||||||
var twoFactorRemember = request.Raw["TwoFactorRemember"] == "1";
|
|
||||||
// Check if the user wants a rememberMe token.
|
|
||||||
if (twoFactorRemember
|
|
||||||
// if the 2FA auth was rememberMe do not send another token.
|
|
||||||
&& twoFactorProviderType != TwoFactorProviderType.Remember)
|
|
||||||
{
|
|
||||||
returnRememberMeToken = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Check if the user is logging in from a new device.
|
|
||||||
var deviceValid = await _deviceValidator.ValidateRequestDeviceAsync(request, validatorContext);
|
|
||||||
if (!deviceValid)
|
|
||||||
{
|
|
||||||
SetValidationErrorResult(context, validatorContext);
|
|
||||||
await LogFailedLoginEvent(validatorContext.User, EventType.User_FailedLogIn);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Force legacy users to the web for migration.
|
|
||||||
if (UserService.IsLegacyUser(user) && request.ClientId != "web")
|
|
||||||
{
|
|
||||||
await FailAuthForLegacyUserAsync(user, context);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task FailAuthForLegacyUserAsync(User user, T context)
|
protected async Task FailAuthForLegacyUserAsync(User user, T context)
|
||||||
{
|
{
|
||||||
@@ -392,9 +253,14 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
if (validatorContext.TwoFactorRequired &&
|
if (validatorContext.TwoFactorRequired &&
|
||||||
validatorContext.TwoFactorRecoveryRequested)
|
validatorContext.TwoFactorRecoveryRequested)
|
||||||
{
|
{
|
||||||
SetSsoResult(context, new Dictionary<string, object>
|
SetSsoResult(context,
|
||||||
|
new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ "ErrorModel", new ErrorResponseModel("Two-factor recovery has been performed. SSO authentication is required.") }
|
{
|
||||||
|
"ErrorModel",
|
||||||
|
new ErrorResponseModel(
|
||||||
|
"Two-factor recovery has been performed. SSO authentication is required.")
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -683,7 +549,8 @@ public abstract class BaseRequestValidator<T> where T : class
|
|||||||
/// <param name="user">user trying to login</param>
|
/// <param name="user">user trying to login</param>
|
||||||
/// <param name="grantType">magic string identifying the grant type requested</param>
|
/// <param name="grantType">magic string identifying the grant type requested</param>
|
||||||
/// <returns>true if sso required; false if not required or already in process</returns>
|
/// <returns>true if sso required; false if not required or already in process</returns>
|
||||||
[Obsolete("This method is deprecated and will be removed in future versions, PM-28281. Please use the SsoRequestValidator scheme instead.")]
|
[Obsolete(
|
||||||
|
"This method is deprecated and will be removed in future versions, PM-28281. Please use the SsoRequestValidator scheme instead.")]
|
||||||
private async Task<bool> RequireSsoLoginAsync(User user, string grantType)
|
private async Task<bool> RequireSsoLoginAsync(User user, string grantType)
|
||||||
{
|
{
|
||||||
if (grantType == "authorization_code" || grantType == "client_credentials")
|
if (grantType == "authorization_code" || grantType == "client_credentials")
|
||||||
|
|||||||
@@ -48,8 +48,6 @@ public class SsoRequestValidator(
|
|||||||
// evaluated, and recovery will have been performed if requested.
|
// evaluated, and recovery will have been performed if requested.
|
||||||
// We will send a descriptive message in these cases so clients can give the appropriate feedback and redirect
|
// We will send a descriptive message in these cases so clients can give the appropriate feedback and redirect
|
||||||
// to /login.
|
// to /login.
|
||||||
// If the feature flag RecoveryCodeSupportForSsoRequiredUsers is set to false then this code is unreachable since
|
|
||||||
// Two Factor validation occurs after SSO validation in that scenario.
|
|
||||||
if (context.TwoFactorRequired && context.TwoFactorRecoveryRequested)
|
if (context.TwoFactorRequired && context.TwoFactorRecoveryRequested)
|
||||||
{
|
{
|
||||||
await SetContextCustomResponseSsoErrorAsync(context, SsoConstants.RequestErrors.SsoTwoFactorRecoveryDescription);
|
await SetContextCustomResponseSsoErrorAsync(context, SsoConstants.RequestErrors.SsoTwoFactorRecoveryDescription);
|
||||||
|
|||||||
@@ -104,13 +104,6 @@ public class BaseRequestValidatorTests
|
|||||||
_userAccountKeysQuery);
|
_userAccountKeysQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(bool recoveryCodeSupportEnabled)
|
|
||||||
{
|
|
||||||
_featureService
|
|
||||||
.IsEnabled(FeatureFlagKeys.RecoveryCodeSupportForSsoRequiredUsers)
|
|
||||||
.Returns(recoveryCodeSupportEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logic path
|
/* Logic path
|
||||||
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
* ValidateAsync -> UpdateFailedAuthDetailsAsync -> _mailService.SendFailedLoginAttemptsEmailAsync
|
||||||
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
* |-> BuildErrorResultAsync -> _eventService.LogUserEventAsync
|
||||||
@@ -118,16 +111,14 @@ public class BaseRequestValidatorTests
|
|||||||
* |-> SetErrorResult
|
* |-> SetErrorResult
|
||||||
*/
|
*/
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_ContextNotValid_SelfHosted_ShouldBuildErrorResult_ShouldLogWarning(
|
public async Task ValidateAsync_ContextNotValid_SelfHosted_ShouldBuildErrorResult_ShouldLogWarning(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_globalSettings.SelfHosted = true;
|
_globalSettings.SelfHosted = true;
|
||||||
_sut.isValid = false;
|
_sut.isValid = false;
|
||||||
@@ -144,16 +135,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_DeviceNotValidated_ShouldLogError(
|
public async Task ValidateAsync_DeviceNotValidated_ShouldLogError(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
@@ -186,16 +175,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_DeviceValidated_ShouldSucceed(
|
public async Task ValidateAsync_DeviceValidated_ShouldSucceed(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
@@ -232,16 +219,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_ValidatedAuthRequest_ConsumedOnSuccess(
|
public async Task ValidateAsync_ValidatedAuthRequest_ConsumedOnSuccess(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
@@ -297,16 +282,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_ValidatedAuthRequest_NotConsumed_When2faRequired(
|
public async Task ValidateAsync_ValidatedAuthRequest_NotConsumed_When2faRequired(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
|
|
||||||
// 1 -> to pass
|
// 1 -> to pass
|
||||||
@@ -329,7 +312,8 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
// 2 -> will result to false with no extra configuration
|
// 2 -> will result to false with no extra configuration
|
||||||
// 3 -> set two factor to be required
|
// 3 -> set two factor to be required
|
||||||
requestContext.User.TwoFactorProviders = "{\"1\":{\"Enabled\":true,\"MetaData\":{\"Email\":\"user@test.dev\"}}}";
|
requestContext.User.TwoFactorProviders =
|
||||||
|
"{\"1\":{\"Enabled\":true,\"MetaData\":{\"Email\":\"user@test.dev\"}}}";
|
||||||
_twoFactorAuthenticationValidator
|
_twoFactorAuthenticationValidator
|
||||||
.RequiresTwoFactorAsync(requestContext.User, tokenRequest)
|
.RequiresTwoFactorAsync(requestContext.User, tokenRequest)
|
||||||
.Returns(Task.FromResult(new Tuple<bool, Organization>(true, null)));
|
.Returns(Task.FromResult(new Tuple<bool, Organization>(true, null)));
|
||||||
@@ -356,16 +340,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_TwoFactorTokenInvalid_ShouldSendFailedTwoFactorEmail(
|
public async Task ValidateAsync_TwoFactorTokenInvalid_ShouldSendFailedTwoFactorEmail(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = requestContext.User;
|
var user = requestContext.User;
|
||||||
|
|
||||||
@@ -400,16 +382,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_TwoFactorRememberTokenExpired_ShouldNotSendFailedTwoFactorEmail(
|
public async Task ValidateAsync_TwoFactorRememberTokenExpired_ShouldNotSendFailedTwoFactorEmail(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = requestContext.User;
|
var user = requestContext.User;
|
||||||
|
|
||||||
@@ -455,21 +435,17 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
// 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", true)]
|
[BitAutoData("password")]
|
||||||
[BitAutoData("password", false)]
|
[BitAutoData("webauthn")]
|
||||||
[BitAutoData("webauthn", true)]
|
[BitAutoData("refresh_token")]
|
||||||
[BitAutoData("webauthn", false)]
|
|
||||||
[BitAutoData("refresh_token", true)]
|
|
||||||
[BitAutoData("refresh_token", false)]
|
|
||||||
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredTrue_ShouldSetSsoResult(
|
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredTrue_ShouldSetSsoResult(
|
||||||
string grantType,
|
string grantType,
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
@@ -489,21 +465,17 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
// Test grantTypes with RequireSsoPolicyRequirement when feature flag is enabled
|
// Test grantTypes with RequireSsoPolicyRequirement when feature flag is enabled
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData("password", true)]
|
[BitAutoData("password")]
|
||||||
[BitAutoData("password", false)]
|
[BitAutoData("webauthn")]
|
||||||
[BitAutoData("webauthn", true)]
|
[BitAutoData("refresh_token")]
|
||||||
[BitAutoData("webauthn", false)]
|
|
||||||
[BitAutoData("refresh_token", true)]
|
|
||||||
[BitAutoData("refresh_token", false)]
|
|
||||||
public async Task ValidateAsync_GrantTypes_WithPolicyRequirementsEnabled_OrgSsoRequiredTrue_ShouldSetSsoResult(
|
public async Task ValidateAsync_GrantTypes_WithPolicyRequirementsEnabled_OrgSsoRequiredTrue_ShouldSetSsoResult(
|
||||||
string grantType,
|
string grantType,
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
@@ -525,21 +497,17 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData("password", true)]
|
[BitAutoData("password")]
|
||||||
[BitAutoData("password", false)]
|
[BitAutoData("webauthn")]
|
||||||
[BitAutoData("webauthn", true)]
|
[BitAutoData("refresh_token")]
|
||||||
[BitAutoData("webauthn", false)]
|
|
||||||
[BitAutoData("refresh_token", true)]
|
|
||||||
[BitAutoData("refresh_token", false)]
|
|
||||||
public async Task ValidateAsync_GrantTypes_WithPolicyRequirementsEnabled_OrgSsoRequiredFalse_ShouldSucceed(
|
public async Task ValidateAsync_GrantTypes_WithPolicyRequirementsEnabled_OrgSsoRequiredFalse_ShouldSucceed(
|
||||||
string grantType,
|
string grantType,
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
@@ -574,21 +542,17 @@ public class BaseRequestValidatorTests
|
|||||||
// Test grantTypes where SSO would be required but the user is not in an
|
// Test grantTypes where SSO would be required but the user is not in an
|
||||||
// organization that requires it
|
// organization that requires it
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData("password", true)]
|
[BitAutoData("password")]
|
||||||
[BitAutoData("password", false)]
|
[BitAutoData("webauthn")]
|
||||||
[BitAutoData("webauthn", true)]
|
[BitAutoData("refresh_token")]
|
||||||
[BitAutoData("webauthn", false)]
|
|
||||||
[BitAutoData("refresh_token", true)]
|
|
||||||
[BitAutoData("refresh_token", false)]
|
|
||||||
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredFalse_ShouldSucceed(
|
public async Task ValidateAsync_GrantTypes_OrgSsoRequiredFalse_ShouldSucceed(
|
||||||
string grantType,
|
string grantType,
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
@@ -623,19 +587,17 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
// Test the grantTypes where SSO is in progress or not relevant
|
// Test the grantTypes where SSO is in progress or not relevant
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData("authorization_code", true)]
|
[BitAutoData("authorization_code")]
|
||||||
[BitAutoData("authorization_code", false)]
|
[BitAutoData("client_credentials")]
|
||||||
[BitAutoData("client_credentials", true)]
|
[BitAutoData("client_credentials")]
|
||||||
[BitAutoData("client_credentials", false)]
|
|
||||||
public async Task ValidateAsync_GrantTypes_SsoRequiredFalse_ShouldSucceed(
|
public async Task ValidateAsync_GrantTypes_SsoRequiredFalse_ShouldSucceed(
|
||||||
string grantType,
|
string grantType,
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
@@ -671,16 +633,14 @@ public class BaseRequestValidatorTests
|
|||||||
* ValidateAsync -> UserService.IsLegacyUser -> FailAuthForLegacyUserAsync
|
* ValidateAsync -> UserService.IsLegacyUser -> FailAuthForLegacyUserAsync
|
||||||
*/
|
*/
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_IsLegacyUser_FailAuthForLegacyUserAsync(
|
public async Task ValidateAsync_IsLegacyUser_FailAuthForLegacyUserAsync(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = context.CustomValidatorRequestContext.User;
|
var user = context.CustomValidatorRequestContext.User;
|
||||||
user.Key = null;
|
user.Key = null;
|
||||||
@@ -705,16 +665,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_CustomResponse_NoMasterPassword_ShouldSetUserDecryptionOptions(
|
public async Task ValidateAsync_CustomResponse_NoMasterPassword_ShouldSetUserDecryptionOptions(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
@@ -755,19 +713,16 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true, KdfType.PBKDF2_SHA256, 654_321, null, null)]
|
[BitAutoData(KdfType.PBKDF2_SHA256, 654_321, null, null)]
|
||||||
[BitAutoData(false, KdfType.PBKDF2_SHA256, 654_321, null, null)]
|
[BitAutoData(KdfType.Argon2id, 11, 128, 5)]
|
||||||
[BitAutoData(true, KdfType.Argon2id, 11, 128, 5)]
|
|
||||||
[BitAutoData(false, KdfType.Argon2id, 11, 128, 5)]
|
|
||||||
public async Task ValidateAsync_CustomResponse_MasterPassword_ShouldSetUserDecryptionOptions(
|
public async Task ValidateAsync_CustomResponse_MasterPassword_ShouldSetUserDecryptionOptions(
|
||||||
bool featureFlagValue,
|
|
||||||
KdfType kdfType, int kdfIterations, int? kdfMemory, int? kdfParallelism,
|
KdfType kdfType, int kdfIterations, int? kdfMemory, int? kdfParallelism,
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.ForUser(Arg.Any<User>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.WithDevice(Arg.Any<Device>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
|
_userDecryptionOptionsBuilder.WithSso(Arg.Any<SsoConfig>()).Returns(_userDecryptionOptionsBuilder);
|
||||||
@@ -826,16 +781,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_CustomResponse_ShouldIncludeAccountKeys(
|
public async Task ValidateAsync_CustomResponse_ShouldIncludeAccountKeys(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var mockAccountKeys = new UserAccountKeysData
|
var mockAccountKeys = new UserAccountKeysData
|
||||||
{
|
{
|
||||||
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData(
|
PublicKeyEncryptionKeyPairData = new PublicKeyEncryptionKeyPairData(
|
||||||
@@ -908,16 +861,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_CustomResponse_AccountKeysQuery_SkippedWhenPrivateKeyIsNull(
|
public async Task ValidateAsync_CustomResponse_AccountKeysQuery_SkippedWhenPrivateKeyIsNull(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
requestContext.User.PrivateKey = null;
|
requestContext.User.PrivateKey = null;
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -938,16 +889,14 @@ public class BaseRequestValidatorTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_CustomResponse_AccountKeysQuery_CalledWithCorrectUser(
|
public async Task ValidateAsync_CustomResponse_AccountKeysQuery_CalledWithCorrectUser(
|
||||||
bool featureFlagValue,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagValue);
|
|
||||||
var expectedUser = requestContext.User;
|
var expectedUser = requestContext.User;
|
||||||
|
|
||||||
_userAccountKeysQuery.Run(Arg.Any<User>()).Returns(new UserAccountKeysData
|
_userAccountKeysQuery.Run(Arg.Any<User>()).Returns(new UserAccountKeysData
|
||||||
@@ -987,22 +936,20 @@ public class BaseRequestValidatorTests
|
|||||||
/// Tests the core PM-21153 feature: SSO-required users can use recovery codes to disable 2FA,
|
/// Tests the core PM-21153 feature: SSO-required users can use recovery codes to disable 2FA,
|
||||||
/// but must then authenticate via SSO with a descriptive message about the recovery.
|
/// but must then authenticate via SSO with a descriptive message about the recovery.
|
||||||
/// This test validates:
|
/// This test validates:
|
||||||
/// 1. Validation order is changed (2FA before SSO) when recovery code is provided
|
/// 1. Validation order prioritizes 2FA before SSO when recovery code is provided
|
||||||
/// 2. Recovery code successfully validates and sets TwoFactorRecoveryRequested flag
|
/// 2. Recovery code successfully validates and sets TwoFactorRecoveryRequested flag
|
||||||
/// 3. SSO validation then fails with recovery-specific message
|
/// 3. SSO validation then fails with recovery-specific message
|
||||||
/// 4. User is NOT logged in (must authenticate via IdP)
|
/// 4. User is NOT logged in (must authenticate via IdP)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)] // Feature flag ON - new behavior
|
[BitAutoData]
|
||||||
[BitAutoData(false)] // Feature flag OFF - should fail at SSO before 2FA recovery
|
|
||||||
public async Task ValidateAsync_RecoveryCodeForSsoRequiredUser_BlocksWithDescriptiveMessage(
|
public async Task ValidateAsync_RecoveryCodeForSsoRequiredUser_BlocksWithDescriptiveMessage(
|
||||||
bool featureFlagEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagEnabled);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = requestContext.User;
|
var user = requestContext.User;
|
||||||
|
|
||||||
@@ -1040,9 +987,7 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
|
||||||
if (featureFlagEnabled)
|
// Recovery succeeds, then SSO blocks with descriptive message
|
||||||
{
|
|
||||||
// NEW BEHAVIOR: Recovery succeeds, then SSO blocks with descriptive message
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
"Two-factor recovery has been performed. SSO authentication is required.",
|
"Two-factor recovery has been performed. SSO authentication is required.",
|
||||||
errorResponse.Message);
|
errorResponse.Message);
|
||||||
@@ -1050,20 +995,8 @@ public class BaseRequestValidatorTests
|
|||||||
// Verify recovery was marked
|
// Verify recovery was marked
|
||||||
Assert.True(requestContext.TwoFactorRecoveryRequested,
|
Assert.True(requestContext.TwoFactorRecoveryRequested,
|
||||||
"TwoFactorRecoveryRequested flag should be set");
|
"TwoFactorRecoveryRequested flag should be set");
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// LEGACY BEHAVIOR: SSO blocks BEFORE recovery can happen
|
|
||||||
Assert.Equal(
|
|
||||||
"SSO authentication is required.",
|
|
||||||
errorResponse.Message);
|
|
||||||
|
|
||||||
// Recovery never happened because SSO checked first
|
// User is NOT logged in
|
||||||
Assert.False(requestContext.TwoFactorRecoveryRequested,
|
|
||||||
"TwoFactorRecoveryRequested should be false (SSO blocked first)");
|
|
||||||
}
|
|
||||||
|
|
||||||
// In both cases: User is NOT logged in
|
|
||||||
await _eventService.DidNotReceive().LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
await _eventService.DidNotReceive().LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1078,16 +1011,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// 4. NOT be logged in
|
/// 4. NOT be logged in
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_InvalidRecoveryCodeForSsoRequiredUser_FailsAt2FA(
|
public async Task ValidateAsync_InvalidRecoveryCodeForSsoRequiredUser_FailsAt2FA(
|
||||||
bool featureFlagEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagEnabled);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = requestContext.User;
|
var user = requestContext.User;
|
||||||
|
|
||||||
@@ -1121,9 +1052,7 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
var errorResponse = (ErrorResponseModel)context.GrantResult.CustomResponse["ErrorModel"];
|
||||||
|
|
||||||
if (featureFlagEnabled)
|
// 2FA is checked first (due to recovery code request), fails with 2FA error
|
||||||
{
|
|
||||||
// NEW BEHAVIOR: 2FA is checked first (due to recovery code request), fails with 2FA error
|
|
||||||
Assert.Equal(
|
Assert.Equal(
|
||||||
"Two-step token is invalid. Try again.",
|
"Two-step token is invalid. Try again.",
|
||||||
errorResponse.Message);
|
errorResponse.Message);
|
||||||
@@ -1141,32 +1070,15 @@ public class BaseRequestValidatorTests
|
|||||||
|
|
||||||
// Verify failed login event was logged
|
// Verify failed login event was logged
|
||||||
await _eventService.Received(1).LogUserEventAsync(user.Id, EventType.User_FailedLogIn2fa);
|
await _eventService.Received(1).LogUserEventAsync(user.Id, EventType.User_FailedLogIn2fa);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// LEGACY BEHAVIOR: SSO is checked first, blocks before 2FA
|
|
||||||
Assert.Equal(
|
|
||||||
"SSO authentication is required.",
|
|
||||||
errorResponse.Message);
|
|
||||||
|
|
||||||
// 2FA validation never happened
|
|
||||||
await _mailService.DidNotReceive().SendFailedTwoFactorAttemptEmailAsync(
|
|
||||||
Arg.Any<string>(),
|
|
||||||
Arg.Any<TwoFactorProviderType>(),
|
|
||||||
Arg.Any<DateTime>(),
|
|
||||||
Arg.Any<string>());
|
|
||||||
}
|
|
||||||
|
|
||||||
// In both cases: User is NOT logged in
|
// User is NOT logged in
|
||||||
await _eventService.DidNotReceive().LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
await _eventService.DidNotReceive().LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
||||||
|
|
||||||
// Verify user failed login count was updated (in new behavior path)
|
// Verify user failed login count was updated (in new behavior path)
|
||||||
if (featureFlagEnabled)
|
|
||||||
{
|
|
||||||
await _userRepository.Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
await _userRepository.Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
||||||
u.Id == user.Id && u.FailedLoginCount > 0));
|
u.Id == user.Id && u.FailedLoginCount > 0));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests that non-SSO users can successfully use recovery codes to disable 2FA and log in.
|
/// Tests that non-SSO users can successfully use recovery codes to disable 2FA and log in.
|
||||||
@@ -1179,16 +1091,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// This is the "happy path" for recovery code usage.
|
/// This is the "happy path" for recovery code usage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_RecoveryCodeForNonSsoUser_SuccessfulLogin(
|
public async Task ValidateAsync_RecoveryCodeForNonSsoUser_SuccessfulLogin(
|
||||||
bool featureFlagEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(featureFlagEnabled);
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
var user = requestContext.User;
|
var user = requestContext.User;
|
||||||
|
|
||||||
@@ -1235,7 +1145,8 @@ public class BaseRequestValidatorTests
|
|||||||
await _sut.ValidateAsync(context);
|
await _sut.ValidateAsync(context);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(context.GrantResult.IsError, "Authentication should succeed for non-SSO user with valid recovery code");
|
Assert.False(context.GrantResult.IsError,
|
||||||
|
"Authentication should succeed for non-SSO user with valid recovery code");
|
||||||
|
|
||||||
// Verify user successfully logged in
|
// Verify user successfully logged in
|
||||||
await _eventService.Received(1).LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
await _eventService.Received(1).LogUserEventAsync(user.Id, EventType.User_LoggedIn);
|
||||||
@@ -1244,20 +1155,10 @@ public class BaseRequestValidatorTests
|
|||||||
await _userRepository.Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
await _userRepository.Received(1).ReplaceAsync(Arg.Is<User>(u =>
|
||||||
u.Id == user.Id && u.FailedLoginCount == 0));
|
u.Id == user.Id && u.FailedLoginCount == 0));
|
||||||
|
|
||||||
if (featureFlagEnabled)
|
// Recovery flag should be set for audit purposes
|
||||||
{
|
|
||||||
// NEW BEHAVIOR: Recovery flag should be set for audit purposes
|
|
||||||
Assert.True(requestContext.TwoFactorRecoveryRequested,
|
Assert.True(requestContext.TwoFactorRecoveryRequested,
|
||||||
"TwoFactorRecoveryRequested flag should be set for audit/logging");
|
"TwoFactorRecoveryRequested flag should be set for audit/logging");
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
// LEGACY BEHAVIOR: Recovery flag doesn't exist, but login still succeeds
|
|
||||||
// (SSO check happens before 2FA in legacy, but user is not SSO-required so both pass)
|
|
||||||
Assert.False(requestContext.TwoFactorRecoveryRequested,
|
|
||||||
"TwoFactorRecoveryRequested should be false in legacy mode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tests that when RedirectOnSsoRequired is DISABLED, the legacy SSO validation path is used.
|
/// Tests that when RedirectOnSsoRequired is DISABLED, the legacy SSO validation path is used.
|
||||||
@@ -1265,16 +1166,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// is checked using the old PolicyService.AnyPoliciesApplicableToUserAsync approach.
|
/// is checked using the old PolicyService.AnyPoliciesApplicableToUserAsync approach.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Disabled_UsesLegacySsoValidation(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Disabled_UsesLegacySsoValidation(
|
||||||
bool recoveryCodeFeatureEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(recoveryCodeFeatureEnabled);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(false);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(false);
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -1309,16 +1208,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// instead of the legacy RequireSsoLoginAsync method.
|
/// instead of the legacy RequireSsoLoginAsync method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_UsesNewSsoRequestValidator(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_UsesNewSsoRequestValidator(
|
||||||
bool recoveryCodeFeatureEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(recoveryCodeFeatureEnabled);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -1367,16 +1264,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// authentication continues successfully through the new validation path.
|
/// authentication continues successfully through the new validation path.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_SsoNotRequired_SuccessfulLogin(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_SsoNotRequired_SuccessfulLogin(
|
||||||
bool recoveryCodeFeatureEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(recoveryCodeFeatureEnabled);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -1430,16 +1325,14 @@ public class BaseRequestValidatorTests
|
|||||||
/// (e.g., with organization identifier), that custom response is properly propagated to the result.
|
/// (e.g., with organization identifier), that custom response is properly propagated to the result.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData]
|
||||||
[BitAutoData(false)]
|
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_PropagatesCustomResponse(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_PropagatesCustomResponse(
|
||||||
bool recoveryCodeFeatureEnabled,
|
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(recoveryCodeFeatureEnabled);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
||||||
_sut.isValid = true;
|
_sut.isValid = true;
|
||||||
|
|
||||||
@@ -1473,7 +1366,8 @@ public class BaseRequestValidatorTests
|
|||||||
Assert.True(context.GrantResult.IsError);
|
Assert.True(context.GrantResult.IsError);
|
||||||
Assert.NotNull(context.GrantResult.CustomResponse);
|
Assert.NotNull(context.GrantResult.CustomResponse);
|
||||||
Assert.Contains("SsoOrganizationIdentifier", context.CustomValidatorRequestContext.CustomResponse);
|
Assert.Contains("SsoOrganizationIdentifier", context.CustomValidatorRequestContext.CustomResponse);
|
||||||
Assert.Equal("test-org-identifier", context.CustomValidatorRequestContext.CustomResponse["SsoOrganizationIdentifier"]);
|
Assert.Equal("test-org-identifier",
|
||||||
|
context.CustomValidatorRequestContext.CustomResponse["SsoOrganizationIdentifier"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1484,11 +1378,11 @@ public class BaseRequestValidatorTests
|
|||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Disabled_RecoveryWithSso_LegacyMessage(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Disabled_RecoveryWithSso_LegacyMessage(
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(true);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(false);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(false);
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -1535,11 +1429,11 @@ public class BaseRequestValidatorTests
|
|||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_RecoveryWithSso_NewValidatorMessage(
|
public async Task ValidateAsync_RedirectOnSsoRequired_Enabled_RecoveryWithSso_NewValidatorMessage(
|
||||||
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest tokenRequest,
|
||||||
[AuthFixtures.CustomValidatorRequestContext] CustomValidatorRequestContext requestContext,
|
[AuthFixtures.CustomValidatorRequestContext]
|
||||||
|
CustomValidatorRequestContext requestContext,
|
||||||
GrantValidationResult grantResult)
|
GrantValidationResult grantResult)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
SetupRecoveryCodeSupportForSsoRequiredUsersFeatureFlag(true);
|
|
||||||
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
_featureService.IsEnabled(FeatureFlagKeys.RedirectOnSsoRequired).Returns(true);
|
||||||
|
|
||||||
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
var context = CreateContext(tokenRequest, requestContext, grantResult);
|
||||||
@@ -1568,7 +1462,10 @@ public class BaseRequestValidatorTests
|
|||||||
};
|
};
|
||||||
requestContext.CustomResponse = new Dictionary<string, object>
|
requestContext.CustomResponse = new Dictionary<string, object>
|
||||||
{
|
{
|
||||||
{ "ErrorModel", new ErrorResponseModel("Two-factor recovery has been performed. SSO authentication is required.") }
|
{
|
||||||
|
"ErrorModel",
|
||||||
|
new ErrorResponseModel("Two-factor recovery has been performed. SSO authentication is required.")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
_ssoRequestValidator.ValidateAsync(
|
_ssoRequestValidator.ValidateAsync(
|
||||||
|
|||||||
Reference in New Issue
Block a user