private async Task <TokenRequestValidationResult> ValidateCibaRequestRequestAsync(NameValueCollection parameters) { _logger.LogDebug("Start validation of CIBA request"); ///////////////////////////////////////////// // check if client is authorized for grant type ///////////////////////////////////////////// if (!_validatedRequest.Client.AllowedGrantTypes.ToList().Contains(OidcConstants.GrantTypes.Ciba)) { LogError("Client not authorized for CIBA flow"); return(Invalid(OidcConstants.TokenErrors.UnauthorizedClient)); } LicenseValidator.ValidateCiba(); ///////////////////////////////////////////// // validate authentication request id parameter ///////////////////////////////////////////// var authRequestId = parameters.Get(OidcConstants.TokenRequest.AuthenticationRequestId); if (authRequestId.IsMissing()) { LogError("Authentication request id is missing"); return(Invalid(OidcConstants.TokenErrors.InvalidRequest)); } if (authRequestId.Length > _options.InputLengthRestrictions.AuthenticationRequestId) { LogError("Authentication request id too long"); return(Invalid(OidcConstants.TokenErrors.InvalidGrant)); } ///////////////////////////////////////////// // validate authentication request id ///////////////////////////////////////////// var validationContext = new BackchannelAuthenticationRequestIdValidationContext { AuthenticationRequestId = authRequestId, Request = _validatedRequest }; await _backchannelAuthenticationRequestIdValidator.ValidateAsync(validationContext); if (validationContext.Result.IsError) { return(validationContext.Result); } ////////////////////////////////////////////////////////// // resource indicator ////////////////////////////////////////////////////////// if (_validatedRequest.RequestedResourceIndicator != null && _validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators?.Any() == true && !_validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators.Contains(_validatedRequest.RequestedResourceIndicator)) { return(Invalid(OidcConstants.AuthorizeErrors.InvalidTarget, "Resource indicator does not match any resource indicator in the original backchannel authentication request.")); } ////////////////////////////////////////////////////////// // resource and scope validation ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.BackChannelAuthenticationRequest.AuthorizedScopes, ResourceIndicators = _validatedRequest.BackChannelAuthenticationRequest.RequestedResourceIndicators, // if we are issuing a refresh token, then we need to allow the non-isolated resource IncludeNonIsolatedApiResources = _validatedRequest.BackChannelAuthenticationRequest.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); _logger.LogDebug("Validation of CIBA token request success"); return(Valid()); }
public async Task <BackchannelAuthenticationRequestValidationResult> ValidateRequestAsync(NameValueCollection parameters, ClientSecretValidationResult clientValidationResult) { using var activity = Tracing.BasicActivitySource.StartActivity("BackchannelAuthenticationRequestValidator.ValidateRequest"); if (clientValidationResult == null) { throw new ArgumentNullException(nameof(clientValidationResult)); } _logger.LogDebug("Start backchannel authentication request validation"); _validatedRequest = new ValidatedBackchannelAuthenticationRequest { Raw = parameters ?? throw new ArgumentNullException(nameof(parameters)), Options = _options }; _validatedRequest.SetClient(clientValidationResult.Client, clientValidationResult.Secret, clientValidationResult.Confirmation); ////////////////////////////////////////////////////////// // Client must be configured for CIBA ////////////////////////////////////////////////////////// if (!clientValidationResult.Client.AllowedGrantTypes.Contains(OidcConstants.GrantTypes.Ciba)) { LogError("Client {clientId} not configured with the CIBA grant type.", clientValidationResult.Client.ClientId); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnauthorizedClient, "Unauthorized client")); } LicenseValidator.ValidateCiba(); ////////////////////////////////////////////////////////// // load request object ////////////////////////////////////////////////////////// var jwtRequest = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.Request); // check length restrictions if (jwtRequest.IsPresent()) { if (jwtRequest.Length >= _options.InputLengthRestrictions.Jwt) { LogError("request value is too long"); return(Invalid(OidcConstants.AuthorizeErrors.InvalidRequestObject, "Invalid request value")); } } _validatedRequest.RequestObject = jwtRequest; ////////////////////////////////////////////////////////// // validate request object ////////////////////////////////////////////////////////// var roValidationResult = await TryValidateRequestObjectAsync(); if (!roValidationResult.Success) { return(roValidationResult.ErrorResult); } if (_validatedRequest.Client.RequireRequestObject && !_validatedRequest.RequestObjectValues.Any()) { LogError("Client is configured for RequireRequestObject but none present"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest)); } ////////////////////////////////////////////////////////// // scope must be present ////////////////////////////////////////////////////////// var scope = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.Scope); if (scope.IsMissing()) { LogError("Missing scope"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing scope")); } if (scope.Length > _options.InputLengthRestrictions.Scope) { LogError("scopes too long."); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid scope")); } _validatedRequest.RequestedScopes = scope.FromSpaceSeparatedString().Distinct().ToList(); ////////////////////////////////////////////////////////// // openid scope required ////////////////////////////////////////////////////////// if (!_validatedRequest.RequestedScopes.Contains(IdentityServerConstants.StandardScopes.OpenId)) { LogError("openid scope missing."); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing the openid scope")); } ////////////////////////////////////////////////////////// // check for resource indicators and valid format ////////////////////////////////////////////////////////// var resourceIndicators = _validatedRequest.Raw.GetValues("resource") ?? Enumerable.Empty <string>(); if (resourceIndicators?.Any(x => x.Length > _options.InputLengthRestrictions.ResourceIndicatorMaxLength) == true) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Resource indicator maximum length exceeded")); } if (!resourceIndicators.AreValidResourceIndicatorFormat(_logger)) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Invalid resource indicator format")); } _validatedRequest.RequestedResourceIndiators = resourceIndicators?.ToList(); ////////////////////////////////////////////////////////// // check if scopes are valid/supported and check for resource scopes ////////////////////////////////////////////////////////// var validatedResources = await _resourceValidator.ValidateRequestedResourcesAsync(new ResourceValidationRequest { Client = _validatedRequest.Client, Scopes = _validatedRequest.RequestedScopes, ResourceIndicators = resourceIndicators, IncludeNonIsolatedApiResources = _validatedRequest.RequestedScopes.Contains(OidcConstants.StandardScopes.OfflineAccess), }); if (!validatedResources.Succeeded) { if (validatedResources.InvalidResourceIndicators.Any()) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidTarget, "Invalid resource indicator")); } if (validatedResources.InvalidScopes.Any()) { return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidScope, "Invalid scope")); } } LicenseValidator.ValidateResourceIndicators(resourceIndicators); _validatedRequest.ValidatedResources = validatedResources; ////////////////////////////////////////////////////////// // check requested_expiry ////////////////////////////////////////////////////////// var requestLifetime = _validatedRequest.Client.CibaLifetime ?? _options.Ciba.DefaultLifetime; var requestedExpiry = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.RequestedExpiry); if (requestedExpiry.IsPresent()) { // Int32.MaxValue == 2147483647, which is 10 characters in length // so using 9 so we don't overflow below on the Int32.Parse if (requestedExpiry.Length > 9) { LogError("requested_expiry too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid requested_expiry")); } if (Int32.TryParse(requestedExpiry, out var expiryValue) && expiryValue > 0 && expiryValue <= requestLifetime) { _validatedRequest.Expiry = expiryValue; } else { LogError("requested_expiry value out of valid range"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid requested_expiry")); } } else { _validatedRequest.Expiry = requestLifetime; } ////////////////////////////////////////////////////////// // check acr_values ////////////////////////////////////////////////////////// var acrValues = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.AcrValues); if (acrValues.IsPresent()) { if (acrValues.Length > _options.InputLengthRestrictions.AcrValues) { LogError("Acr values too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid acr_values")); } _validatedRequest.AuthenticationContextReferenceClasses = acrValues.FromSpaceSeparatedString().Distinct().ToList(); ////////////////////////////////////////////////////////// // check custom acr_values: idp and tenant ////////////////////////////////////////////////////////// var tenant = _validatedRequest.AuthenticationContextReferenceClasses.FirstOrDefault(x => x.StartsWith(KnownAcrValues.Tenant)); if (tenant != null) { _validatedRequest.AuthenticationContextReferenceClasses.Remove(tenant); tenant = tenant.Substring(KnownAcrValues.Tenant.Length); _validatedRequest.Tenant = tenant; } var idp = _validatedRequest.AuthenticationContextReferenceClasses.FirstOrDefault(x => x.StartsWith(KnownAcrValues.HomeRealm)); if (idp != null) { _validatedRequest.AuthenticationContextReferenceClasses.Remove(idp); idp = idp.Substring(KnownAcrValues.HomeRealm.Length); // check if idp is present but client does not allow it, and then ignore it if (_validatedRequest.Client.IdentityProviderRestrictions != null && _validatedRequest.Client.IdentityProviderRestrictions.Any()) { if (!_validatedRequest.Client.IdentityProviderRestrictions.Contains(idp)) { _logger.LogWarning("idp requested ({idp}) is not in client restriction list.", idp); idp = null; } } _validatedRequest.IdP = idp; } } ////////////////////////////////////////////////////////// // login hints ////////////////////////////////////////////////////////// var loginHint = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.LoginHint); var loginHintToken = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.LoginHintToken); var idTokenHint = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.IdTokenHint); var loginHintCount = 0; if (loginHint.IsPresent()) { loginHintCount++; } if (loginHintToken.IsPresent()) { loginHintCount++; } if (idTokenHint.IsPresent()) { loginHintCount++; } if (loginHintCount == 0) { LogError("Missing login_hint_token, id_token_hint, or login_hint"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Missing login_hint_token, id_token_hint, or login_hint")); } else if (loginHintCount > 1) { LogError("Too many of login_hint_token, id_token_hint, or login_hint"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Too many of login_hint_token, id_token_hint, or login_hint")); } ////////////////////////////////////////////////////////// // check login_hint ////////////////////////////////////////////////////////// if (loginHint.IsPresent()) { if (loginHint.Length > _options.InputLengthRestrictions.LoginHint) { LogError("Login hint too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid login_hint")); } _validatedRequest.LoginHint = loginHint; } ////////////////////////////////////////////////////////// // check login_hint_token ////////////////////////////////////////////////////////// if (loginHintToken.IsPresent()) { if (loginHintToken.Length > _options.InputLengthRestrictions.LoginHintToken) { LogError("Login hint token too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid login_hint_token")); } _validatedRequest.LoginHintToken = loginHintToken; } ////////////////////////////////////////////////////////// // check id_token_hint ////////////////////////////////////////////////////////// if (idTokenHint.IsPresent()) { if (idTokenHint.Length > _options.InputLengthRestrictions.IdTokenHint) { LogError("id token hint too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid id_token_hint")); } var idTokenHintValidationResult = await _tokenValidator.ValidateIdentityTokenAsync(idTokenHint, _validatedRequest.ClientId, false); if (idTokenHintValidationResult.IsError) { LogError("id token hint failed to validate: " + idTokenHintValidationResult.Error, idTokenHintValidationResult.ErrorDescription); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid id_token_hint")); } _validatedRequest.IdTokenHint = idTokenHint; _validatedRequest.IdTokenHintClaims = idTokenHintValidationResult.Claims; } ////////////////////////////////////////////////////////// // check user_code ////////////////////////////////////////////////////////// var userCode = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.UserCode); if (userCode.IsPresent()) { if (userCode.Length > _options.InputLengthRestrictions.UserCode) { LogError("user_code too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidRequest, "Invalid user_code")); } _validatedRequest.UserCode = userCode; } ////////////////////////////////////////////////////////// // check binding_message ////////////////////////////////////////////////////////// var bindingMessage = _validatedRequest.Raw.Get(OidcConstants.BackchannelAuthenticationRequest.BindingMessage); if (bindingMessage.IsPresent()) { if (bindingMessage.Length > _options.InputLengthRestrictions.BindingMessage) { LogError("binding_message too long"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage, "Invalid binding_message")); } _validatedRequest.BindingMessage = bindingMessage; } ////////////////////////////////////////////////////////// // validate the login hint w/ custom validator ////////////////////////////////////////////////////////// var userResult = await _backchannelAuthenticationUserValidator.ValidateRequestAsync(new BackchannelAuthenticationUserValidatorContext { Client = _validatedRequest.Client, IdTokenHint = _validatedRequest.IdTokenHint, LoginHint = _validatedRequest.LoginHint, LoginHintToken = _validatedRequest.LoginHintToken, IdTokenHintClaims = _validatedRequest.IdTokenHintClaims, UserCode = _validatedRequest.UserCode, BindingMessage = _validatedRequest.BindingMessage }); if (userResult.IsError) { if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.AccessDenied) { LogError("Request was denied access for that user"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.AccessDenied, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.ExpiredLoginHintToken) { LogError("Expired login_hint_token"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.ExpiredLoginHintToken, userResult.ErrorDescription ?? "Expired login_hint_token")); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId) { LogError("Unknown user id"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.MissingUserCode) { LogError("Missing user_code"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.MissingUserCode, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.InvalidUserCode) { LogError("Invalid user_code"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidUserCode, userResult.ErrorDescription)); } if (userResult.Error == OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage) { LogError("Invalid binding_message"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.InvalidBindingMessage, userResult.ErrorDescription)); } LogError("Unexpected error from IBackchannelAuthenticationUserValidator: {error}", userResult.Error); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId)); } if (userResult.Subject == null || !userResult.Subject.HasClaim(x => x.Type == JwtClaimTypes.Subject)) { LogError("No subject or subject id returned from IBackchannelAuthenticationUserValidator"); return(Invalid(OidcConstants.BackchannelAuthenticationRequestErrors.UnknownUserId)); } _validatedRequest.Subject = userResult.Subject; LogSuccess(); return(new BackchannelAuthenticationRequestValidationResult(_validatedRequest)); }