private async Task <TokenRequestValidationResult> RunValidationAsync(Func <NameValueCollection, Task <TokenRequestValidationResult> > validationFunc, NameValueCollection parameters) { // run standard validation var result = await validationFunc(parameters); if (result.IsError) { return(result); } // run custom validation _logger.LogTrace("Calling into custom request validator: {type}", _customRequestValidator.GetType().FullName); var customValidationContext = new CustomTokenRequestValidationContext { Result = result }; await _customRequestValidator.ValidateAsync(customValidationContext); if (customValidationContext.Result.IsError) { if (customValidationContext.Result.Error.IsPresent()) { LogError("Custom token request validator", new { error = customValidationContext.Result.Error }); } else { LogError("Custom token request validator error"); } return(customValidationContext.Result); } LogSuccess(); LicenseValidator.ValidateClient(customValidationContext.Result.ValidatedRequest.ClientId); return(customValidationContext.Result); }
public async Task <AuthorizeRequestValidationResult> ValidateAsync(NameValueCollection parameters, ClaimsPrincipal subject = null) { _logger.LogDebug("Start authorize request protocol validation"); var request = new ValidatedAuthorizeRequest { Options = _options, IssuerName = await _issuerNameService.GetCurrentAsync(), Subject = subject ?? Principal.Anonymous, Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)) }; // load client_id // client_id must always be present on the request var loadClientResult = await LoadClientAsync(request); if (loadClientResult.IsError) { return(loadClientResult); } // load request object var roLoadResult = await LoadRequestObjectAsync(request); if (roLoadResult.IsError) { return(roLoadResult); } // validate request object var roValidationResult = await ValidateRequestObjectAsync(request); if (roValidationResult.IsError) { return(roValidationResult); } // validate client_id and redirect_uri var clientResult = await ValidateClientAsync(request); if (clientResult.IsError) { return(clientResult); } // state, response_type, response_mode var mandatoryResult = ValidateCoreParameters(request); if (mandatoryResult.IsError) { return(mandatoryResult); } // scope, scope restrictions and plausability, and resource indicators var scopeResult = await ValidateScopeAndResourceAsync(request); if (scopeResult.IsError) { return(scopeResult); } // nonce, prompt, acr_values, login_hint etc. var optionalResult = await ValidateOptionalParametersAsync(request); if (optionalResult.IsError) { return(optionalResult); } // custom validator _logger.LogDebug("Calling into custom validator: {type}", _customValidator.GetType().FullName); var context = new CustomAuthorizeRequestValidationContext { Result = new AuthorizeRequestValidationResult(request) }; await _customValidator.ValidateAsync(context); var customResult = context.Result; if (customResult.IsError) { LogError("Error in custom validation", customResult.Error, request); return(Invalid(request, customResult.Error, customResult.ErrorDescription)); } _logger.LogTrace("Authorize request protocol validation successful"); LicenseValidator.ValidateClient(request.ClientId); return(Valid(request)); }
private async Task <AuthorizeRequestValidationResult> ValidateScopeAndResourceAsync(ValidatedAuthorizeRequest request) { ////////////////////////////////////////////////////////// // scope must be present ////////////////////////////////////////////////////////// var scope = request.Raw.Get(OidcConstants.AuthorizeRequest.Scope); if (scope.IsMissing()) { LogError("scope is missing", request); return(Invalid(request, description: "Invalid scope")); } if (scope.Length > _options.InputLengthRestrictions.Scope) { LogError("scopes too long.", request); return(Invalid(request, description: "Invalid scope")); } request.RequestedScopes = scope.FromSpaceSeparatedString().Distinct().ToList(); if (request.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId)) { request.IsOpenIdRequest = true; } ////////////////////////////////////////////////////////// // check scope vs response_type plausability ////////////////////////////////////////////////////////// var requirement = Constants.ResponseTypeToScopeRequirement[request.ResponseType]; if (requirement == Constants.ScopeRequirement.Identity || requirement == Constants.ScopeRequirement.IdentityOnly) { if (request.IsOpenIdRequest == false) { LogError("response_type requires the openid scope", request); return(Invalid(request, description: "Missing openid scope")); } } ////////////////////////////////////////////////////////// // check for resource indicators and valid format ////////////////////////////////////////////////////////// var resourceIndicators = request.Raw.GetValues(OidcConstants.TokenRequest.Resource) ?? Enumerable.Empty <string>(); if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator maximum length exceeded")); } if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger)) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator format")); } // we don't want to allow resource indicators when "token" is requested to authorize endpoint if (request.GrantType == GrantType.Implicit && resourceIndicators.Any()) { // todo: correct error? return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicators not allowed for response_type 'token'.")); } request.RequestedResourceIndiators = resourceIndicators; ////////////////////////////////////////////////////////// // check if scopes are valid/supported and check for resource scopes ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = request.Client, Scopes = request.RequestedScopes, ResourceIndicators = resourceIndicators, IncludeNonIsolatedApiResources = request.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess), }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope")); } } LicenseValidator.ValidateResourceIndicators(resourceIndicators); if (validatedResources.Resources.IdentityResources.Any() && !request.IsOpenIdRequest) { LogError("Identity related scope requests, but no openid scope", request); return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Identity scopes requested, but openid scope is missing")); } if (validatedResources.Resources.ApiScopes.Any()) { request.IsApiResourceRequest = true; } ////////////////////////////////////////////////////////// // check id vs resource scopes and response types plausability ////////////////////////////////////////////////////////// var responseTypeValidationCheck = true; switch (requirement) { case Constants.ScopeRequirement.Identity: if (!validatedResources.Resources.IdentityResources.Any()) { _logger.LogError("Requests for id_token response type must include identity scopes"); responseTypeValidationCheck = false; } break; case Constants.ScopeRequirement.IdentityOnly: if (!validatedResources.Resources.IdentityResources.Any() || validatedResources.Resources.ApiScopes.Any()) { _logger.LogError("Requests for id_token response type only must not include resource scopes"); responseTypeValidationCheck = false; } break; case Constants.ScopeRequirement.ResourceOnly: if (validatedResources.Resources.IdentityResources.Any() || !validatedResources.Resources.ApiScopes.Any()) { _logger.LogError("Requests for token response type only must include resource scopes, but no identity scopes."); responseTypeValidationCheck = false; } break; } if (!responseTypeValidationCheck) { return(Invalid(request, OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope for response type")); } request.ValidatedResources = validatedResources; return(Valid(request)); }
// todo: do we want to rework the semantics of these ignore params? // also seems like other workflows other than CC clients can omit scopes? private async Task <string> ValidateRequestedScopesAndResourcesAsync(NameValueCollection parameters, bool ignoreImplicitIdentityScopes = false, bool ignoreImplicitOfflineAccess = false) { var scopes = parameters.Get(OidcConstants.TokenRequest.Scope); if (scopes.IsMissing()) { _logger.LogTrace("Client provided no scopes - checking allowed scopes list"); if (!_validatedRequest.Client.AllowedScopes.IsNullOrEmpty()) { // this finds all the scopes the client is allowed to access var clientAllowedScopes = new List <string>(); if (!ignoreImplicitIdentityScopes) { var resources = await _resourceStore.FindResourcesByScopeAsync(_validatedRequest.Client.AllowedScopes); clientAllowedScopes.AddRange(resources.ToScopeNames().Where(x => _validatedRequest.Client.AllowedScopes.Contains(x))); } else { var apiScopes = await _resourceStore.FindApiScopesByNameAsync(_validatedRequest.Client.AllowedScopes); clientAllowedScopes.AddRange(apiScopes.Select(x => x.Name)); } if (!ignoreImplicitOfflineAccess) { if (_validatedRequest.Client.AllowOfflineAccess) { clientAllowedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess); } } scopes = clientAllowedScopes.Distinct().ToSpaceSeparatedString(); _logger.LogTrace("Defaulting to: {scopes}", scopes); } else { LogError("No allowed scopes configured for client", new { clientId = _validatedRequest.Client.ClientId }); return(OidcConstants.TokenErrors.InvalidScope); } } if (scopes.Length > _options.InputLengthRestrictions.Scope) { LogError("Scope parameter exceeds max allowed length"); return(OidcConstants.TokenErrors.InvalidScope); } var requestedScopes = scopes.ParseScopesString(); if (requestedScopes == null) { LogError("No scopes found in request"); return(OidcConstants.TokenErrors.InvalidScope); } var resourceIndicators = _validatedRequest.RequestedResourceIndicator == null? Enumerable.Empty <string>() : new[] { _validatedRequest.RequestedResourceIndicator }; var resourceValidationResult = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = requestedScopes, ResourceIndicators = resourceIndicators, // if the client is passing explicit scopes, we want to exclude the non-isolated resource scenaio from validation IncludeNonIsolatedApiResources = parameters.Get(OidcConstants.TokenRequest.Scope).IsMissing() }); if (!resourceValidationResult.Succeeded) { if (resourceValidationResult.InvalidResourceIndicators.Any()) { LogError("Invalid resource indicator"); return(OidcConstants.TokenErrors.InvalidTarget); } if (resourceValidationResult.InvalidScopes.Any()) { LogError("Invalid scopes requested"); } else { LogError("Invalid scopes for client requested"); } return(OidcConstants.TokenErrors.InvalidScope); } _validatedRequest.RequestedScopes = requestedScopes; LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = resourceValidationResult.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); return(null); }
private async Task <TokenRequestValidationResult> ValidateDeviceCodeRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of device code request"); ///////////////////////////////////////////// // resource indicator not supported for device flow ///////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null) { LogError("Resource indicators not supported for device flow"); return(Invalid(OidcConstants.TokenErrors.InvalidTarget)); } ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.DeviceFlow)) { LogError("Client not authorized for device flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } ///////////////////////////////////////////// // validate device code parameter ///////////////////////////////////////////// var deviceCode = parameters.Get(OidcConstants.TokenRequest.DeviceCode); if (deviceCode.IsMissing()) { LogError("Device code is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (deviceCode.Length > _options.InputLengthRestrictions.DeviceCode) { LogError("Device code too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate device code ///////////////////////////////////////////// var deviceCodeContext = new DeviceCodeValidationContext { DeviceCode = deviceCode, Request = _validatedRequest }; await _deviceCodeValidator.ValidateAsync(deviceCodeContext); if (deviceCodeContext.Result.IsError) { return(deviceCodeContext.Result); } ////////////////////////////////////////////////////////// // scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.DeviceCode.AuthorizedScopes, ResourceIndicators = null // not supported for device grant }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources; _logger.LogDebug("Validation of device code token request success"); return(Valid()); }
private async Task <TokenRequestValidationResult> ValidateRefreshTokenRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of refresh token request"); var refreshTokenHandle = parameters.Get(OidcConstants.TokenRequest.RefreshToken); if (refreshTokenHandle.IsMissing()) { LogError("Refresh token is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (refreshTokenHandle.Length > _options.InputLengthRestrictions.RefreshToken) { LogError("Refresh token too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } var result = await _refreshTokenService.ValidateRefreshTokenAsync(refreshTokenHandle, _validatedRequest.Client); if (result.IsError) { LogWarning("Refresh token validation failed. aborting"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.RefreshToken = result.RefreshToken; _validatedRequest.RefreshTokenHandle = refreshTokenHandle; _validatedRequest.Subject = result.RefreshToken.Subject; ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// var resourceIndicators = _validatedRequest.RefreshToken.AuthorizedResourceIndicators; if (_validatedRequest.RefreshToken.AuthorizedResourceIndicators != null) { // we had an authorization request so check current requested resource against original list if (_validatedRequest.RequestedResourceIndicator != null && !_validatedRequest.RefreshToken.AuthorizedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original authorize request.")); } } else if (!String.IsNullOrWhiteSpace(_validatedRequest.RequestedResourceIndicator)) { resourceIndicators = new[] { _validatedRequest.RequestedResourceIndicator }; } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.RefreshToken.AuthorizedScopes, ResourceIndicators = resourceIndicators, // we're issuing refresh token, so we need to allow for non-isolated resource IncludeNonIsolatedApiResources = true, }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); _logger.LogDebug("Validation of refresh token request success"); // todo: more logging - similar to TokenValidator before return(Valid()); }
private async Task <TokenRequestValidationResult> ValidateAuthorizationCodeRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of authorization code token request"); ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.AuthorizationCode) && !_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(GrantType.Hybrid)) { LogError("Client not authorized for code flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } ///////////////////////////////////////////// // validate authorization code ///////////////////////////////////////////// var code = parameters.Get(OidcConstants.TokenRequest.Code); if (code.IsMissing()) { LogError("Authorization code is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } if (code.Length > _options.InputLengthRestrictions.AuthorizationCode) { LogError("Authorization code is too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.AuthorizationCodeHandle = code; var authZcode = await _authorizationCodeStore.GetAuthorizationCodeAsync(code); if (authZcode == null) { LogError("Invalid authorization code", new { code }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate client binding ///////////////////////////////////////////// if (authZcode.ClientId != _validatedRequest.Client.ClientId) { LogError("Client is trying to use a code from a different client", new { clientId = _validatedRequest.Client.ClientId, codeClient = authZcode.ClientId }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } // remove code from store // todo: set to consumed in the future? await _authorizationCodeStore.RemoveAuthorizationCodeAsync(code); if (authZcode.CreationTime.HasExceeded(authZcode.Lifetime, _clock.UtcNow.UtcDateTime)) { LogError("Authorization code expired", new { code }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // populate session id ///////////////////////////////////////////// if (authZcode.SessionId.IsPresent()) { _validatedRequest.SessionId = authZcode.SessionId; } ///////////////////////////////////////////// // validate code expiration ///////////////////////////////////////////// if (authZcode.CreationTime.HasExceeded(_validatedRequest.Client.AuthorizationCodeLifetime, _clock.UtcNow.UtcDateTime)) { LogError("Authorization code is expired"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _validatedRequest.AuthorizationCode = authZcode; _validatedRequest.Subject = authZcode.Subject; ///////////////////////////////////////////// // validate redirect_uri ///////////////////////////////////////////// var redirectUri = parameters.Get(OidcConstants.TokenRequest.RedirectUri); if (redirectUri.IsMissing()) { LogError("Redirect URI is missing"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } if (redirectUri.Equals(_validatedRequest.AuthorizationCode.RedirectUri, StringComparison.Ordinal) == false) { LogError("Invalid redirect_uri", new { redirectUri, expectedRedirectUri = _validatedRequest.AuthorizationCode.RedirectUri }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate scopes are present ///////////////////////////////////////////// if (_validatedRequest.AuthorizationCode.RequestedScopes == null || !_validatedRequest.AuthorizationCode.RequestedScopes.Any()) { LogError("Authorization code has no associated scopes"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null && _validatedRequest.AuthorizationCode.RequestedResourceIndicators?.Any() == true && !_validatedRequest.AuthorizationCode.RequestedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original authorize request.")); } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.AuthorizationCode.RequestedScopes, ResourceIndicators = _validatedRequest.AuthorizationCode.RequestedResourceIndicators, // if we are issuing a refresh token, then we need to allow the non-isolated resource IncludeNonIsolatedApiResources = _validatedRequest.AuthorizationCode.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess) }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Invalid resource indicator.")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidScope, "Invalid scope.")); } } LicenseValidator.ValidateResourceIndicators(_validatedRequest.RequestedResourceIndicator); _validatedRequest.ValidatedResources = validatedResources.FilterByResourceIndicator(_validatedRequest.RequestedResourceIndicator); ///////////////////////////////////////////// // validate PKCE parameters ///////////////////////////////////////////// var codeVerifier = parameters.Get(OidcConstants.TokenRequest.CodeVerifier); if (_validatedRequest.Client.RequirePkce || _validatedRequest.AuthorizationCode.CodeChallenge.IsPresent()) { _logger.LogDebug("Client required a proof key for code exchange. Starting PKCE validation"); var proofKeyResult = ValidateAuthorizationCodeWithProofKeyParameters(codeVerifier, _validatedRequest.AuthorizationCode); if (proofKeyResult.IsError) { return(proofKeyResult); } _validatedRequest.CodeVerifier = codeVerifier; } else { if (codeVerifier.IsPresent()) { LogError("Unexpected code_verifier: {codeVerifier}. This happens when the client is trying to use PKCE, but it is not enabled. Set RequirePkce to true.", codeVerifier); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } } ///////////////////////////////////////////// // make sure user is enabled ///////////////////////////////////////////// var isActiveCtx = new IsActiveContext(_validatedRequest.AuthorizationCode.Subject, _validatedRequest.Client, IdentityServerConstants.ProfileIsActiveCallers.AuthorizationCodeValidation); await _profile.IsActiveAsync(isActiveCtx); if (isActiveCtx.IsActive == false) { LogError("User has been disabled", new { subjectId = _validatedRequest.AuthorizationCode.Subject.GetSubjectId() }); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } _logger.LogDebug("Validation of authorization code token request success"); return(Valid()); }