Exemple #1
0
        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> 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> 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> 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());
        }