-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Expand file tree
/
Copy pathSsoRequestValidator.cs
More file actions
100 lines (89 loc) · 5.27 KB
/
SsoRequestValidator.cs
File metadata and controls
100 lines (89 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.Entities;
using Bit.Core.Models.Api;
using Bit.Core.Repositories;
using Bit.Identity.IdentityServer.RequestValidationConstants;
using Duende.IdentityModel;
using Duende.IdentityServer.Validation;
namespace Bit.Identity.IdentityServer.RequestValidators;
/// <summary>
/// Validates whether a user is required to authenticate via SSO based on organization policies.
/// </summary>
public class SsoRequestValidator(
IOrganizationRepository _organizationRepository,
IPolicyRequirementQuery _policyRequirementQuery) : ISsoRequestValidator
{
/// <summary>
/// Validates the SSO requirement for a user attempting to authenticate.
/// Sets context.SsoRequired to indicate whether SSO is required.
/// If SSO is required, sets the validation error result and custom response in the context.
/// </summary>
/// <param name="user">The user attempting to authenticate.</param>
/// <param name="request">The token request containing grant type and other authentication details.</param>
/// <param name="context">The validator context to be updated with SSO requirement status and error results if applicable.</param>
/// <returns>true if the user can proceed with authentication; false if SSO is required and the user must be redirected to SSO flow.</returns>
public async Task<bool> ValidateAsync(User user, ValidatedTokenRequest request, CustomValidatorRequestContext context)
{
// Check if the user is required to authenticate via SSO. If the user requires SSO, but they are
// logging in using an API Key (client_credentials) then they are allowed to bypass the SSO requirement.
// If the GrantType is authorization_code or client_credentials we know the user is trying to log in
// using the SSO flow so they are allowed to continue.
if (request.GrantType is OidcConstants.GrantTypes.AuthorizationCode or OidcConstants.GrantTypes.ClientCredentials)
{
// SSO is not required for users already using SSO to authenticate which uses the authorization_code grant type,
// or logging-in via API key which is the client_credentials grant type.
// Allow user to continue request validation
context.SsoRequired = false;
return true;
}
var requiredSsoRequirement = await _policyRequirementQuery.GetAsyncVNext<RequireSsoPolicyRequirement>(user.Id);
context.SsoRequired = requiredSsoRequirement.SsoRequired;
if (!context.SsoRequired)
{
return true;
}
// Users without SSO requirement requesting 2FA recovery will be fast-forwarded through login and are
// presented with their 2FA management area as a reminder to re-evaluate their 2FA posture after recovery and
// review their new recovery token if desired.
// SSO users cannot be assumed to be authenticated, and must prove authentication with their IdP after recovery.
// As described in validation order determination, if TwoFactorRequired, the 2FA validation scheme will have been
// 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
// to /login.
if (context is { TwoFactorRequired: true, TwoFactorRecoveryRequested: true })
{
await SetContextCustomResponseSsoErrorAsync(context, requiredSsoRequirement, SsoConstants.RequestErrors.SsoTwoFactorRecoveryDescription);
return false;
}
await SetContextCustomResponseSsoErrorAsync(context, requiredSsoRequirement, SsoConstants.RequestErrors.SsoRequiredDescription);
return false;
}
/// <summary>
/// Sets the customResponse in the context with the error result for the SSO validation failure.
/// </summary>
/// <param name="context">The validator context to update with error details.</param>
/// <param name="requireSsoPolicyRequirement">Require Sso policy requirement for user.</param>
/// <param name="errorMessage">The error message to return to the client.</param>
private async Task SetContextCustomResponseSsoErrorAsync(CustomValidatorRequestContext context, RequireSsoPolicyRequirement requireSsoPolicyRequirement, string errorMessage)
{
var organization = requireSsoPolicyRequirement.OrganizationIds.Count == 1
? await _organizationRepository.GetByIdAsync(requireSsoPolicyRequirement.OrganizationIds.First())
: null;
var ssoOrganizationIdentifier = organization?.Identifier;
context.ValidationErrorResult = new ValidationResult
{
IsError = true,
Error = OidcConstants.TokenErrors.InvalidGrant,
ErrorDescription = errorMessage
};
context.CustomResponse = new Dictionary<string, object>
{
{ CustomResponseConstants.ResponseKeys.ErrorModel, new ErrorResponseModel(errorMessage) }
};
// Include organization identifier in the response if available
if (!string.IsNullOrEmpty(ssoOrganizationIdentifier))
{
context.CustomResponse[CustomResponseConstants.ResponseKeys.SsoOrganizationIdentifier] = ssoOrganizationIdentifier;
}
}
}