internal static (string value, OpenIdConnectMessage error) ValidateParameterIsUnique(
            IDictionary <string, string[]> requestParameters,
            string parameterName,
            ProtocolErrorProvider provider)
        {
            if (requestParameters.TryGetValue(parameterName, out var currentParameter))
            {
                if (currentParameter.Length > 1)
                {
                    return(null, provider.TooManyParameters(parameterName));
                }

                var parameterValue = currentParameter.SingleOrDefault();
                if (string.IsNullOrEmpty(parameterValue))
                {
                    return(null, provider.MissingRequiredParameter(parameterName));
                }

                return(parameterValue, null);
            }
            else
            {
                return(null, provider.MissingRequiredParameter(parameterName));
            }
        }
Exemple #2
0
        public async Task FailsToCreateAuthorizationRequest_IfClientId_IsMissing()
        {
            // Arrange
            var parameters = new Dictionary <string, string[]>
            {
                [OpenIdConnectParameterNames.State] = new[] { "state" }
            };
            var expectedError = new AuthorizationRequestError(ProtocolErrorProvider.MissingRequiredParameter(OpenIdConnectParameterNames.ClientId), null, null);

            expectedError.Message.State = "state";

            var factory = CreateAuthorizationRequestFactory();

            // Act
            var result = await factory.CreateAuthorizationRequestAsync(parameters);

            // Assert
            Assert.False(result.IsValid);
            Assert.Equal(expectedError, result.Error, IdentityServiceErrorComparer.Instance);
            Assert.Null(result.Error.RedirectUri);
            Assert.Null(result.Error.ResponseMode);
        }
        private async Task <OpenIdConnectMessage> ValidateAuthorizationCode(
            IDictionary <string, string[]> requestParameters,
            string clientId,
            AuthorizationGrant consentGrant)
        {
            if (!(consentGrant.Token is AuthorizationCode code))
            {
                throw new InvalidOperationException("Granted token must be an authorization code.");
            }

            var(redirectUri, redirectUriError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.RedirectUri, _errorProvider);
            if (redirectUriError != null)
            {
                return(redirectUriError);
            }

            var tokenRedirectUri = code.RedirectUri;

            if (redirectUri == null && tokenRedirectUri != null)
            {
                return(_errorProvider.MissingRequiredParameter(OpenIdConnectParameterNames.RedirectUri));
            }

            if (!string.Equals(redirectUri, tokenRedirectUri, StringComparison.Ordinal))
            {
                return(_errorProvider.MismatchedRedirectUrl(redirectUri));
            }

            var resolution = await _redirectUriValidator.ResolveRedirectUriAsync(clientId, redirectUri);

            if (!resolution.IsValid)
            {
                return(_errorProvider.InvalidRedirectUri(redirectUri));
            }

            if (code.CodeChallenge != null)
            {
                if (!ProofOfKeyForCodeExchangeChallengeMethods.SHA256.Equals(code.CodeChallengeMethod, StringComparison.Ordinal))
                {
                    throw new InvalidOperationException("Unsupported code challenge method.");
                }

                var(verifier, verifierError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeVerifier, _errorProvider);
                if (verifierError != null)
                {
                    return(verifierError);
                }

                // code-verifier = [a-zA-Z0-9\-._~]{43,128}
                if (verifier.Length < 43 || verifier.Length > 128)
                {
                    return(_errorProvider.InvalidCodeVerifier());
                }

                for (var i = 0; i < verifier.Length; i++)
                {
                    if (verifier[i] > 127 || !ValidCodeVerifierCharacters[verifier[i]])
                    {
                        return(_errorProvider.InvalidCodeVerifier());
                    }
                }

                if (!string.Equals(code.CodeChallenge, GetComputedChallenge(verifier), StringComparison.Ordinal))
                {
                    return(_errorProvider.InvalidCodeVerifier());
                }
            }

            return(null);
        }
        public async Task <AuthorizationRequest> CreateAuthorizationRequestAsync(IDictionary <string, string[]> requestParameters)
        {
            // Parameters sent without a value MUST be treated as if they were
            // omitted from the request.The authorization server MUST ignore
            // unrecognized request parameters.Request and response parameters
            // MUST NOT be included more than once.

            // Validate that we only got send one state property as it needs to be included in all responses (including error ones)
            var(state, stateError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.State, _errorProvider);


            // Start by validating the client_id and redirect_uri as any of them being invalid indicates that we need to
            // return a 400 response instead of a 302 response with the error. This is signaled by the result not containing
            // a url to redirect to.
            var(clientId, redirectUri, clientError) = await ValidateClientIdAndRedirectUri(requestParameters, state);

            if (clientError != null)
            {
                // Send first the state error if there was one.
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        stateError ?? clientError,
                                                        redirectUri: null,
                                                        responseMode: null)));
            }

            // We need to determine what response mode to use to send the errors in case there are any.
            // In case the response type and response modes are valid, we should use those values when
            // notifying clients of the errors.
            // In case there is an issue with the response type or the response mode we need to determine
            // how to notify the relying party of the errors.
            // We can divide this in two situations:
            // The response mode is invalid:
            //  * We ignore the response mode and base our response based on the response type specified.
            //      If a token was requested we send the error response on the fragment of the redirect uri.
            //      If no token was requested we send the error response on the query of the redirect uri.
            // The response type is invalid:
            //  * We try to determine if this is a hybrid or implicit flow:
            //      If the invalid response type contained a request for an id_token or an access_token, or
            //      contained more than one space separated value, we send the response on the fragment,
            //      unless the response mode is specified and form_post.
            //      If the invalid response type only contained one value and we can not determine is an
            //      implicit request flow, we return the error on the query string unless the response mode
            //      is specified and form_post or fragment.

            var(responseType, parsedResponseType, tokenRequested, responseTypeError) = ValidateResponseType(requestParameters);
            var(responseMode, responseModeError) = ValidateResponseMode(requestParameters);

            var invalidCombinationError = ValidateResponseModeTypeCombination(responseType, tokenRequested, responseMode);

            if (responseModeError != null || responseMode == null)
            {
                responseMode = GetResponseMode(parsedResponseType, tokenRequested);
            }

            if (responseTypeError != null)
            {
                responseTypeError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? responseTypeError, redirectUri, responseMode)));
            }

            if (responseModeError != null)
            {
                responseModeError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? responseModeError, redirectUri, responseMode)));
            }

            if (invalidCombinationError != null)
            {
                invalidCombinationError.State = state;
                return(AuthorizationRequest.Invalid(
                           new AuthorizationRequestError(stateError ?? invalidCombinationError, redirectUri, responseMode)));
            }

            var(nonce, nonceError) = tokenRequested ?
                                     RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Nonce, _errorProvider) :
                                     RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Nonce, _errorProvider);

            if (nonceError != null)
            {
                nonceError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(nonceError, redirectUri, responseMode)));
            }

            var(scope, scopeError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Scope, _errorProvider);
            if (scopeError != null)
            {
                scopeError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(scopeError, redirectUri, responseMode)));
            }

            var parsedScope   = scope.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            var allWhiteSpace = true;

            for (int i = 0; i < parsedScope.Length; i++)
            {
                allWhiteSpace = string.IsNullOrWhiteSpace(parsedScope[i]);
                if (!allWhiteSpace)
                {
                    break;
                }
            }

            if (allWhiteSpace)
            {
                scopeError       = _errorProvider.MissingRequiredParameter(OpenIdConnectParameterNames.Scope);
                scopeError.State = state;

                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        scopeError,
                                                        redirectUri,
                                                        responseMode)));
            }

            if (parsedResponseType.Contains(OpenIdConnectResponseType.IdToken) && !parsedScope.Contains(OpenIdConnectScope.OpenId))
            {
                scopeError       = _errorProvider.MissingOpenIdScope();
                scopeError.State = state;

                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                        scopeError,
                                                        redirectUri,
                                                        responseMode)));
            }

            var resolvedScopes = await _scopeValidator.ResolveScopesAsync(clientId, parsedScope);

            if (!resolvedScopes.IsValid)
            {
                resolvedScopes.Error.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(resolvedScopes.Error, redirectUri, responseMode)));
            }

            var(prompt, promptError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, OpenIdConnectParameterNames.Prompt, _errorProvider);
            if (promptError != null)
            {
                promptError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(promptError, redirectUri, responseMode)));
            }

            if (prompt != null)
            {
                var parsedPrompt = prompt.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                promptError = ValidatePrompt(parsedPrompt);
                if (promptError != null)
                {
                    promptError.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(promptError, redirectUri, responseMode)));
                }
            }

            var(codeChallenge, codeChallengeError) = RequestParametersHelper.ValidateOptionalParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeChallenge, _errorProvider);
            if (codeChallengeError != null)
            {
                codeChallengeError.State = state;
                return(AuthorizationRequest.Invalid(new AuthorizationRequestError(codeChallengeError, redirectUri, responseMode)));
            }

            if (codeChallenge != null)
            {
                // The code challenge needs to be 43 characters long as its the result of Base64URLEncode(SHA256(code_verifier)).
                // We do this check here because the code challenge might get saved in the serialized authorization code and we
                // want to prevent it from getting unnecessarily big.
                if (codeChallenge.Length != 43)
                {
                    var invalidCodeChallenge = _errorProvider.InvalidCodeChallenge();
                    invalidCodeChallenge.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(
                                                            invalidCodeChallenge,
                                                            redirectUri,
                                                            responseMode)));
                }

                var(codeChallengeMethod, codeChallengeMethodError) = RequestParametersHelper.ValidateParameterIsUnique(requestParameters, ProofOfKeyForCodeExchangeParameterNames.CodeChallengeMethod, _errorProvider);
                if (codeChallengeMethodError != null)
                {
                    codeChallengeMethodError.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(codeChallengeMethodError, redirectUri, responseMode)));
                }

                if (!codeChallengeMethod.Equals(ProofOfKeyForCodeExchangeChallengeMethods.SHA256, StringComparison.Ordinal))
                {
                    var invalidChallengeMethod = _errorProvider.InvalidCodeChallengeMethod(codeChallengeMethod);
                    invalidChallengeMethod.State = state;
                    return(AuthorizationRequest.Invalid(new AuthorizationRequestError(invalidChallengeMethod, redirectUri, responseMode)));
                }
            }

            var result = new OpenIdConnectMessage(requestParameters);

            result.RequestType = OpenIdConnectRequestType.Authentication;

            var requestGrants = new RequestGrants
            {
                Tokens       = GetRequestedTokens(parsedResponseType, resolvedScopes.Scopes),
                Scopes       = resolvedScopes.Scopes.ToList(),
                ResponseMode = responseMode,
                RedirectUri  = redirectUri
            };

            return(await ValidateRequestAsync(AuthorizationRequest.Valid(result, requestGrants)));
        }