public async Task <IActionResult> Exchange([ModelBinder(BinderType = typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request)
        {
            if (request.IsClientCredentialsGrantType())
            {
                // Note: the client credentials are automatically validated by OpenIddict:
                // if client_id or client_secret are invalid, this action won't be invoked.

                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = CreateTicket(application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
예제 #2
0
        public async Task <IActionResult> Exchange(OpenIdConnectRequest request)
        {
            Debug.Assert(request.IsTokenRequest(),
                         "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                         "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

            if (request.IsClientCredentialsGrantType())
            {
                // Note: the client credentials are automatically validated by OpenIddict:
                // if client_id or client_secret are invalid, this action won't be invoked.

                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = CreateTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
예제 #3
0
        public async Task <IActionResult> Token(OpenIdConnectRequest request)
        {
            // Warning: this action is decorated with IgnoreAntiforgeryTokenAttribute to override
            // the global antiforgery token validation policy applied by the MVC modules stack,
            // which is required for this stateless OAuth2/OIDC token endpoint to work correctly.
            // To prevent effective CSRF/session fixation attacks, this action MUST NOT return
            // an authentication cookie or try to establish an ASP.NET Core user session.

            if (request.IsPasswordGrantType())
            {
                return(await ExchangePasswordGrantType(request));
            }

            if (request.IsClientCredentialsGrantType())
            {
                return(await ExchangeClientCredentialsGrantType(request));
            }

            if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                return(await ExchangeAuthorizationCodeOrRefreshTokenGrantType(request));
            }

            throw new NotSupportedException("The specified grant type is not supported.");
        }
예제 #4
0
        public async Task <IActionResult> Exchange([ModelBinder(typeof(OpenIddictMvcBinder))] OpenIdConnectRequest request)
        {
            if (!request.IsClientCredentialsGrantType())
            {
                return(BadRequest(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The specified grant type is not supported."
                }));
            }

            var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

            if (application == null)
            {
                return(BadRequest(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidClient,
                    ErrorDescription = "The client application was not found in the database."
                }));
            }

            var ticket = CreateTicket(application);

            return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
        }
예제 #5
0
        public async Task <IActionResult> Exchange(OpenIdConnectRequest request)
        {
            try
            {
                _logger.LogInformation("About to authorize user");
                if (request.IsClientCredentialsGrantType())
                {
                    var authenticationTicket = await CreateTicket(request);

                    //this occur when the client credentials are wrong
                    if (authenticationTicket == null)
                    {
                        var properties = new AuthenticationProperties(new Dictionary <string, string>
                        {
                            [Properties.Error]            = Errors.InvalidGrant,
                            [Properties.ErrorDescription] = "The client id/secret couple is invalid."
                        });

                        return(Forbid(properties, OpenIddictServerDefaults.AuthenticationScheme));
                    }
                    _logger.LogInformation("Client Credentials authentication successfully.");
                    return(SignIn(authenticationTicket.Principal, authenticationTicket.Properties, authenticationTicket.AuthenticationScheme));
                }
                return(BadRequest("Invalid grant type"));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
                return(BadRequest(ex.Message));
            }
        }
예제 #6
0
        public async Task <IActionResult> Exchange([ModelBinder(typeof(OpenIddictMvcBinder))]
                                                   OpenIdConnectRequest request)
        {
            try
            {
                if (request.IsClientCredentialsGrantType())
                {
                    // Note: the client credentials are automatically validated by OpenIddict:
                    // if client_id or client_secret are invalid, this action will not be invoked
                    var ticket = await _mediator.Send(new ClientCredentialsRequest { ClientId = request.ClientId }, HttpContext.RequestAborted);

                    return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
                }
            }
            catch (IdentityException ex)
            {
                return(BadRequest(ex.Response));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
예제 #7
0
        public async Task <IActionResult> Login(OpenIdConnectRequest request, Profile profile)
        {
            if (request.IsClientCredentialsGrantType())
            {
                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "Id Клиента не найден в базе данных."
                    }));
                }

                if (!await _repository.GetProfileAsync(profile.Login, profile.Password))
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                        ErrorDescription = "Login or PAssword не совпадают."
                    }));
                }
                var idprofile = await _repository.GetProfileIdAsync(profile.Login, profile.Password);

                var i = idprofile.ToString();
                request.Username = i.ToString();
                var ticket = CreateTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            else if (request.IsRefreshTokenGrantType())
            {
                var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);


                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "Id Клиента не найден в базе данных."
                    }));
                }
                var ticket = CreateTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
        public void IsClientCredentialsGrantType_ReturnsExpectedResult(string type, bool result)
        {
            // Arrange
            var request = new OpenIdConnectRequest();

            request.GrantType = type;

            // Act and assert
            Assert.Equal(result, request.IsClientCredentialsGrantType());
        }
        private async Task <IActionResult> HandleAuthorizationFlow(OpenIdConnectRequest request)
        {
            if (request.IsClientCredentialsGrantType())
            {
                return(await HandleClientCredentialsGrantTypeFlow(request));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
        public async Task <ActionResult> Exchange(OpenIdConnectRequest request)
        {
            Debug.Assert(request.IsTokenRequest(),
                         "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                         "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

            if (request.IsPasswordGrantType())
            {
                var user = await _userManager.FindByNameAsync(request.Username);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The username/password couple is invalid."
                    }));
                }

                // Validate the username/password parameters and ensure the account is not locked out.
                var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true);

                if (!result.Succeeded)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The username/password couple is invalid."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = await CreateTicketAsync(request, user);

                var claimsPrincipal = await _userClaimsPrincipalFactory.CreateAsync(user);

                var limitedPermissions = _authorizationOptions.LimitedCookiePermissions?.Split(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, StringSplitOptions.RemoveEmptyEntries) ?? new string[0];

                if (!user.IsAdministrator)
                {
                    limitedPermissions = claimsPrincipal
                                         .Claims
                                         .Where(c => c.Type == PlatformConstants.Security.Claims.PermissionClaimType)
                                         .Select(c => c.Value)
                                         .Intersect(limitedPermissions, StringComparer.OrdinalIgnoreCase)
                                         .ToArray();
                }

                if (limitedPermissions.Any())
                {
                    // Set limited permissions and authenticate user with combined mode Cookies + Bearer.
                    //
                    // LimitedPermissions claims that will be granted to the user by cookies when bearer token authentication is enabled.
                    // This can help to authorize the user for direct(non - AJAX) GET requests to the VC platform API and / or to use some 3rd - party web applications for the VC platform(like Hangfire dashboard).
                    //
                    // If the user identity has claim named "limited_permissions", this attribute should authorize only permissions listed in that claim. Any permissions that are required by this attribute but
                    // not listed in the claim should cause this method to return false. However, if permission limits of user identity are not defined ("limited_permissions" claim is missing),
                    // then no limitations should be applied to the permissions.
                    ((ClaimsIdentity)claimsPrincipal.Identity).AddClaim(new Claim(PlatformConstants.Security.Claims.LimitedPermissionsClaimType, string.Join(PlatformConstants.Security.Claims.PermissionClaimTypeDelimiter, limitedPermissions)));
                    await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, claimsPrincipal);
                }

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the authorization code/refresh token.
                var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);

                // Retrieve the user profile corresponding to the authorization code/refresh token.
                // Note: if you want to automatically invalidate the authorization code/refresh token
                // when the user password/roles change, use the following line instead:
                // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
                var user = await _userManager.GetUserAsync(info.Principal);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The token is no longer valid."
                    }));
                }

                // Ensure the user is still allowed to sign in.
                if (!await _signInManager.CanSignInAsync(user))
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The user is no longer allowed to sign in."
                    }));
                }

                // Create a new authentication ticket, but reuse the properties stored in the
                // authorization code/refresh token, including the scopes originally granted.
                var ticket = await CreateTicketAsync(request, user, info.Properties);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.IsClientCredentialsGrantType())
            {
                // Note: the client credentials are automatically validated by OpenIddict:
                // if client_id or client_secret are invalid, this action won't be invoked.
                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = CreateTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
예제 #11
0
        private async Task <bool> InvokeTokenEndpointAsync()
        {
            if (!string.Equals(Request.Method, "POST", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The token request was rejected because an invalid " +
                                        "HTTP method was received: {Method}.", Request.Method);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed token request has been received: make sure to use POST."
                }));
            }

            // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
            if (string.IsNullOrEmpty(Request.ContentType))
            {
                Options.Logger.LogError("The token request was rejected because the " +
                                        "mandatory 'Content-Type' header was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed token request has been received: " +
                                       "the mandatory 'Content-Type' header was missing from the POST request."
                }));
            }

            // May have media/type; charset=utf-8, allow partial match.
            if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
            {
                Options.Logger.LogError("The token request was rejected because an invalid 'Content-Type' " +
                                        "header was received: {ContentType}.", Request.ContentType);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "A malformed token request has been received: " +
                                       "the 'Content-Type' header contained an unexcepted value. " +
                                       "Make sure to use 'application/x-www-form-urlencoded'."
                }));
            }

            var request = new OpenIdConnectRequest(await Request.ReadFormAsync());

            // Note: set the message type before invoking the ExtractTokenRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.Token);

            // Store the token request in the OWIN context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractTokenRequestContext(Context, Options, request);
            await Options.Provider.ExtractTokenRequest(@event);

            if (@event.HandledResponse)
            {
                return(true);
            }

            else if (@event.Skipped)
            {
                return(false);
            }

            else if (@event.IsRejected)
            {
                Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ @event.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            // Reject token requests missing the mandatory grant_type parameter.
            if (string.IsNullOrEmpty(request.GrantType))
            {
                Options.Logger.LogError("The token request was rejected because the grant type was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'grant_type' parameter was missing.",
                }));
            }

            // Reject grant_type=authorization_code requests if the authorization endpoint is disabled.
            else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue)
            {
                Options.Logger.LogError("The token request was rejected because the authorization code grant was disabled.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The authorization code grant is not allowed by this authorization server."
                }));
            }

            // Reject grant_type=authorization_code requests missing the authorization code.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code))
            {
                Options.Logger.LogError("The token request was rejected because the authorization code was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'code' parameter was missing."
                }));
            }

            // Reject grant_type=refresh_token requests missing the refresh token.
            // See https://tools.ietf.org/html/rfc6749#section-6
            else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken))
            {
                Options.Logger.LogError("The token request was rejected because the refresh token was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'refresh_token' parameter was missing."
                }));
            }

            // Reject grant_type=password requests missing username or password.
            // See https://tools.ietf.org/html/rfc6749#section-4.3.2
            else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) ||
                                                       string.IsNullOrEmpty(request.Password)))
            {
                Options.Logger.LogError("The token request was rejected because the resource owner credentials were missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'username' and/or 'password' parameters " +
                                       "was/were missing from the request message."
                }));
            }

            // When client_id and client_secret are both null, try to extract them from the Authorization header.
            // See http://tools.ietf.org/html/rfc6749#section-2.3.1 and
            // http://openid.net/specs/openid-connect-core-1_0.html#ClientAuthentication
            if (string.IsNullOrEmpty(request.ClientId) && string.IsNullOrEmpty(request.ClientSecret))
            {
                var header = Request.Headers.Get("Authorization");
                if (!string.IsNullOrEmpty(header) && header.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
                {
                    try {
                        var value = header.Substring("Basic ".Length).Trim();
                        var data  = Encoding.UTF8.GetString(Convert.FromBase64String(value));

                        var index = data.IndexOf(':');
                        if (index >= 0)
                        {
                            request.ClientId     = data.Substring(0, index);
                            request.ClientSecret = data.Substring(index + 1);
                        }
                    }

                    catch (FormatException) { }
                    catch (ArgumentException) { }
                }
            }

            var context = new ValidateTokenRequestContext(Context, Options, request);
            await Options.Provider.ValidateTokenRequest(context);

            // If the validation context was set as fully validated,
            // mark the OpenID Connect request as confidential.
            if (context.IsValidated)
            {
                request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel,
                                    OpenIdConnectConstants.ConfidentialityLevels.Private);
            }

            if (context.HandledResponse)
            {
                return(true);
            }

            else if (context.Skipped)
            {
                return(false);
            }

            else if (context.IsRejected)
            {
                Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                        /* Description: */ context.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            // Reject grant_type=client_credentials requests if validation was skipped.
            else if (context.IsSkipped && request.IsClientCredentialsGrantType())
            {
                Options.Logger.LogError("The token request must be fully validated to use the client_credentials grant type.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Client authentication is required when using client_credentials."
                }));
            }

            // Ensure that the client_id has been set from the ValidateTokenRequest event.
            else if (context.IsValidated && string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The token request was validated but the client_id was not set.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.ServerError,
                    ErrorDescription = "An internal server error occurred."
                }));
            }

            // At this stage, client_id cannot be null for grant_type=authorization_code requests,
            // as it must either be set in the ValidateTokenRequest notification
            // by the developer or manually flowed by non-confidential client applications.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.ClientId))
            {
                Options.Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "client_id was missing from the token request"
                }));
            }

            AuthenticationTicket ticket = null;

            // See http://tools.ietf.org/html/rfc6749#section-4.1
            // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant).
            // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant).
            if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                ticket = request.IsAuthorizationCodeGrantType() ?
                         await DeserializeAuthorizationCodeAsync(request.Code, request) :
                         await DeserializeRefreshTokenAsync(request.RefreshToken, request);

                if (ticket == null)
                {
                    Options.Logger.LogError("The token request was rejected because the " +
                                            "authorization code or the refresh token was invalid.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Invalid ticket"
                    }));
                }

                // If the client was fully authenticated when retrieving its refresh token,
                // the current request must be rejected if client authentication was not enforced.
                if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential())
                {
                    Options.Logger.LogError("The token request was rejected because client authentication " +
                                            "was required to use the confidential refresh token.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Client authentication is required to use this ticket"
                    }));
                }

                if (ticket.Properties.ExpiresUtc.HasValue &&
                    ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
                {
                    Options.Logger.LogError("The token request was rejected because the " +
                                            "authorization code or the refresh token was expired.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Expired ticket"
                    }));
                }

                // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token
                // was issued to a public client but cannot be null for an authorization code grant request.
                var presenters = ticket.GetPresenters();
                if (request.IsAuthorizationCodeGrantType() && !presenters.Any())
                {
                    Options.Logger.LogError("The token request was rejected because the authorization " +
                                            "code didn't contain any valid presenter.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.ServerError,
                        ErrorDescription = "An internal server error occurred."
                    }));
                }

                // Ensure the authorization code/refresh token was issued to the client application making the token request.
                // Note: when using the refresh token grant, client_id is optional but must validated if present.
                // As a consequence, this check doesn't depend on the actual status of client authentication.
                // See https://tools.ietf.org/html/rfc6749#section-6
                // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken
                if (!string.IsNullOrEmpty(request.ClientId) && presenters.Any() &&
                    !presenters.Contains(request.ClientId, StringComparer.Ordinal))
                {
                    Options.Logger.LogError("The token request was rejected because the authorization " +
                                            "code was issued to a different client application.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Ticket does not contain matching client_id"
                    }));
                }

                // Validate the redirect_uri flowed by the client application during this token request.
                // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request
                // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri
                // but the specifications allow proceeding the token request without returning an error
                // if the authorization request didn't contain an explicit redirect_uri.
                // See https://tools.ietf.org/html/rfc6749#section-4.1.3
                // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation
                var address = ticket.GetProperty(OpenIdConnectConstants.Properties.RedirectUri);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address))
                {
                    ticket.SetProperty(OpenIdConnectConstants.Properties.RedirectUri, null);

                    if (string.IsNullOrEmpty(request.RedirectUri))
                    {
                        Options.Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " +
                                                "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "redirect_uri was missing from the token request"
                        }));
                    }

                    else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'redirect_uri' " +
                                                "parameter didn't correspond to the expected value.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Authorization code does not contain matching redirect_uri"
                        }));
                    }
                }

                // If a code challenge was initially sent in the authorization request and associated with the
                // code, validate the code verifier to ensure the token request is sent by a legit caller.
                var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge))
                {
                    ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallenge, null);

                    // Get the code verifier from the token request.
                    // If it cannot be found, return an invalid_grant error.
                    var verifier = request.CodeVerifier;
                    if (string.IsNullOrEmpty(verifier))
                    {
                        Options.Logger.LogError("The token request was rejected because the required 'code_verifier' " +
                                                "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The required 'code_verifier' was missing from the token request."
                        }));
                    }

                    // Note: the code challenge method is always validated when receiving the authorization request.
                    var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod);
                    ticket.SetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod, null);

                    Debug.Assert(string.IsNullOrEmpty(method) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal),
                                 "The specified code challenge method should be supported.");

                    // If the S256 challenge method was used, compute the hash corresponding to the code verifier.
                    if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal))
                    {
                        using (var algorithm = SHA256.Create()) {
                            // Compute the SHA-256 hash of the code verifier and encode it using base64-url.
                            // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information.
                            var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier));

                            verifier = Base64UrlEncoder.Encode(hash);
                        }
                    }

                    // Compare the verifier and the code challenge: if the two don't match, return an error.
                    // Note: to prevent timing attacks, a time-constant comparer is always used.
                    if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'code_verifier' was invalid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The specified 'code_verifier' was invalid."
                        }));
                    }
                }

                if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Resource))
                {
                    // When an explicit resource parameter has been included in the token request
                    // but was missing from the initial request, the request MUST be rejected.
                    var resources = ticket.GetResources();
                    if (!resources.Any())
                    {
                        Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not allowed.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request cannot contain a resource parameter " +
                                               "if the authorization request didn't contain one"
                        }));
                    }

                    // When an explicit resource parameter has been included in the token request,
                    // the authorization server MUST ensure that it doesn't contain resources
                    // that were not allowed during the initial authorization/token request.
                    else if (!new HashSet <string>(resources).IsSupersetOf(request.GetResources()))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'resource' parameter was not valid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request doesn't contain a valid resource parameter"
                        }));
                    }
                }

                if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope))
                {
                    // When an explicit scope parameter has been included in the token request
                    // but was missing from the initial request, the request MUST be rejected.
                    // See http://tools.ietf.org/html/rfc6749#section-6
                    var scopes = ticket.GetScopes();
                    if (!scopes.Any())
                    {
                        Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request cannot contain a scope parameter " +
                                               "if the authorization request didn't contain one"
                        }));
                    }

                    // When an explicit scope parameter has been included in the token request,
                    // the authorization server MUST ensure that it doesn't contain scopes
                    // that were not allowed during the initial authorization/token request.
                    // See https://tools.ietf.org/html/rfc6749#section-6
                    else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes()))
                    {
                        Options.Logger.LogError("The token request was rejected because the 'scope' parameter was not valid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "Token request doesn't contain a valid scope parameter"
                        }));
                    }
                }
            }

            var notification = new HandleTokenRequestContext(Context, Options, request, ticket);
            await Options.Provider.HandleTokenRequest(notification);

            if (notification.HandledResponse)
            {
                return(true);
            }

            else if (notification.Skipped)
            {
                return(false);
            }

            else if (notification.IsRejected)
            {
                Options.Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                        /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant,
                                        /* Description: */ notification.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            // Flow the changes made to the ticket.
            ticket = notification.Ticket;

            // Ensure an authentication ticket has been provided or return
            // an error code indicating that the grant type is not supported.
            if (ticket == null)
            {
                Options.Logger.LogError("The token request was rejected because no authentication " +
                                        "ticket was returned by application code.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The specified grant_type parameter is not supported."
                }));
            }

            return(await HandleSignInAsync(ticket));
        }
        public async Task <IActionResult> Exchange(OpenIdConnectRequest request)
        {
            Debug.Assert(request.IsTokenRequest(),
                         "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                         "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

            if (request.IsPasswordGrantType())
            {
                var user = await _userManager.FindByNameAsync(request.Username);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The username/password couple is invalid."
                    }));
                }

                // Validate the username/password parameters and ensure the account is not locked out.
                var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true);

                if (!result.Succeeded)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The username/password couple is invalid."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = await CreateTicketAsync(request, user);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the authorization code/refresh token.
                var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);

                // Retrieve the user profile corresponding to the authorization code/refresh token.
                // Note: if you want to automatically invalidate the authorization code/refresh token
                // when the user password/roles change, use the following line instead:
                // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
                var user = await _userManager.GetUserAsync(info.Principal);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The token is no longer valid."
                    }));
                }

                // Ensure the user is still allowed to sign in.
                if (!await _signInManager.CanSignInAsync(user))
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The user is no longer allowed to sign in."
                    }));
                }

                // Create a new authentication ticket, but reuse the properties stored in the
                // authorization code/refresh token, including the scopes originally granted.
                var ticket = await CreateTicketAsync(request, user, info.Properties);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.IsClientCredentialsGrantType())
            {
                // Note: the client credentials are automatically validated by OpenIddict:
                // if client_id or client_secret are invalid, this action won't be invoked.
                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = CreateTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }
예제 #13
0
        private async Task <bool> InvokeTokenEndpointAsync()
        {
            if (!HttpMethods.IsPost(Request.Method))
            {
                Logger.LogError("The token request was rejected because an invalid " +
                                "HTTP method was specified: {Method}.", Request.Method);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified HTTP method is not valid."
                }));
            }

            // See http://openid.net/specs/openid-connect-core-1_0.html#FormSerialization
            if (string.IsNullOrEmpty(Request.ContentType))
            {
                Logger.LogError("The token request was rejected because the " +
                                "mandatory 'Content-Type' header was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'Content-Type' header must be specified."
                }));
            }

            // May have media/type; charset=utf-8, allow partial match.
            if (!Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase))
            {
                Logger.LogError("The token request was rejected because an invalid 'Content-Type' " +
                                "header was specified: {ContentType}.", Request.ContentType);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The specified 'Content-Type' header is not valid."
                }));
            }

            var request = new OpenIdConnectRequest(await Request.ReadFormAsync(Context.RequestAborted));

            // Note: set the message type before invoking the ExtractTokenRequest event.
            request.SetProperty(OpenIdConnectConstants.Properties.MessageType,
                                OpenIdConnectConstants.MessageTypes.TokenRequest);

            // Store the token request in the ASP.NET context.
            Context.SetOpenIdConnectRequest(request);

            var @event = new ExtractTokenRequestContext(Context, Scheme, Options, request);
            await Provider.ExtractTokenRequest(@event);

            if (@event.Result != null)
            {
                if (@event.Result.Handled)
                {
                    Logger.LogDebug("The token request was handled in user code.");

                    return(true);
                }

                else if (@event.Result.Skipped)
                {
                    Logger.LogDebug("The default token request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (@event.IsRejected)
            {
                Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ @event.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = @event.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = @event.ErrorDescription,
                    ErrorUri = @event.ErrorUri
                }));
            }

            Logger.LogInformation("The token request was successfully extracted " +
                                  "from the HTTP request: {Request}.", request);

            // Reject token requests missing the mandatory grant_type parameter.
            if (string.IsNullOrEmpty(request.GrantType))
            {
                Logger.LogError("The token request was rejected because the grant type was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'grant_type' parameter is missing.",
                }));
            }

            // Reject grant_type=authorization_code requests if the authorization endpoint is disabled.
            else if (request.IsAuthorizationCodeGrantType() && !Options.AuthorizationEndpointPath.HasValue)
            {
                Logger.LogError("The token request was rejected because the authorization code grant was disabled.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                    ErrorDescription = "The authorization code grant is not allowed by this authorization server."
                }));
            }

            // Reject grant_type=authorization_code requests missing the authorization code.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            else if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(request.Code))
            {
                Logger.LogError("The token request was rejected because the authorization code was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'code' parameter is missing."
                }));
            }

            // Reject grant_type=refresh_token requests missing the refresh token.
            // See https://tools.ietf.org/html/rfc6749#section-6
            else if (request.IsRefreshTokenGrantType() && string.IsNullOrEmpty(request.RefreshToken))
            {
                Logger.LogError("The token request was rejected because the refresh token was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'refresh_token' parameter is missing."
                }));
            }

            // Reject grant_type=password requests missing username or password.
            // See https://tools.ietf.org/html/rfc6749#section-4.3.2
            else if (request.IsPasswordGrantType() && (string.IsNullOrEmpty(request.Username) ||
                                                       string.IsNullOrEmpty(request.Password)))
            {
                Logger.LogError("The token request was rejected because the resource owner credentials were missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'username' and/or 'password' parameters are missing."
                }));
            }

            // Try to resolve the client credentials specified in the 'Authorization' header.
            // If they cannot be extracted, fallback to the client_id/client_secret parameters.
            var credentials = Request.Headers.GetClientCredentials();

            if (credentials != null)
            {
                // Reject requests that use multiple client authentication methods.
                // See https://tools.ietf.org/html/rfc6749#section-2.3 for more information.
                if (!string.IsNullOrEmpty(request.ClientSecret))
                {
                    Logger.LogError("The token request was rejected because multiple client credentials were specified.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidRequest,
                        ErrorDescription = "Multiple client credentials cannot be specified."
                    }));
                }

                request.ClientId     = credentials?.Key;
                request.ClientSecret = credentials?.Value;
            }

            var context = new ValidateTokenRequestContext(Context, Scheme, Options, request);
            await Provider.ValidateTokenRequest(context);

            // If the validation context was set as fully validated,
            // mark the OpenID Connect request as confidential.
            if (context.IsValidated)
            {
                request.SetProperty(OpenIdConnectConstants.Properties.ConfidentialityLevel,
                                    OpenIdConnectConstants.ConfidentialityLevels.Private);
            }

            if (context.Result != null)
            {
                if (context.Result.Handled)
                {
                    Logger.LogDebug("The token request was handled in user code.");

                    return(true);
                }

                else if (context.Result.Skipped)
                {
                    Logger.LogDebug("The default token request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (context.IsRejected)
            {
                Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                                /* Description: */ context.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = context.Error ?? OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = context.ErrorDescription,
                    ErrorUri = context.ErrorUri
                }));
            }

            // Reject grant_type=client_credentials requests if validation was skipped.
            else if (context.IsSkipped && request.IsClientCredentialsGrantType())
            {
                Logger.LogError("The token request must be fully validated to use the client_credentials grant type.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = "Client authentication is required when using the client credentials grant."
                }));
            }

            // At this stage, client_id cannot be null for grant_type=authorization_code requests,
            // as it must either be set in the ValidateTokenRequest notification
            // by the developer or manually flowed by non-confidential client applications.
            // See https://tools.ietf.org/html/rfc6749#section-4.1.3
            if (request.IsAuthorizationCodeGrantType() && string.IsNullOrEmpty(context.ClientId))
            {
                Logger.LogError("The token request was rejected because the mandatory 'client_id' was missing.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The mandatory 'client_id' parameter is missing."
                }));
            }

            // Store the validated client_id as a request property.
            request.SetProperty(OpenIdConnectConstants.Properties.ValidatedClientId, context.ClientId);

            Logger.LogInformation("The token request was successfully validated.");

            AuthenticationTicket ticket = null;

            // See http://tools.ietf.org/html/rfc6749#section-4.1
            // and http://tools.ietf.org/html/rfc6749#section-4.1.3 (authorization code grant).
            // See http://tools.ietf.org/html/rfc6749#section-6 (refresh token grant).
            if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                ticket = request.IsAuthorizationCodeGrantType() ?
                         await DeserializeAuthorizationCodeAsync(request.Code, request) :
                         await DeserializeRefreshTokenAsync(request.RefreshToken, request);

                if (ticket == null)
                {
                    Logger.LogError("The token request was rejected because the " +
                                    "authorization code or the refresh token was invalid.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = request.IsAuthorizationCodeGrantType() ?
                                           "The specified authorization code is invalid." :
                                           "The specified refresh token is invalid."
                    }));
                }

                // If the client was fully authenticated when retrieving its refresh token,
                // the current request must be rejected if client authentication was not enforced.
                if (request.IsRefreshTokenGrantType() && !context.IsValidated && ticket.IsConfidential())
                {
                    Logger.LogError("The token request was rejected because client authentication " +
                                    "was required to use the confidential refresh token.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Client authentication is required to use the specified refresh token."
                    }));
                }

                if (ticket.Properties.ExpiresUtc.HasValue &&
                    ticket.Properties.ExpiresUtc < Options.SystemClock.UtcNow)
                {
                    Logger.LogError("The token request was rejected because the " +
                                    "authorization code or the refresh token was expired.");

                    return(await SendTokenResponseAsync(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = request.IsAuthorizationCodeGrantType() ?
                                           "The specified authorization code is no longer valid." :
                                           "The specified refresh token is no longer valid."
                    }));
                }

                // Note: presenters may be empty during a grant_type=refresh_token request if the refresh token
                // was issued to a public client but cannot be null for an authorization code grant request.
                var presenters = ticket.GetPresenters();
                if (request.IsAuthorizationCodeGrantType() && !presenters.Any())
                {
                    throw new InvalidOperationException("The presenters list cannot be extracted from the authorization code.");
                }

                // Ensure the authorization code/refresh token was issued to the client application making the token request.
                // Note: when using the refresh token grant, client_id is optional but must validated if present.
                // As a consequence, this check doesn't depend on the actual status of client authentication.
                // See https://tools.ietf.org/html/rfc6749#section-6
                // and http://openid.net/specs/openid-connect-core-1_0.html#RefreshingAccessToken
                //if (!string.IsNullOrEmpty(context.ClientId) && presenters.Any() &&
                //    !presenters.Contains(context.ClientId, StringComparer.Ordinal))
                //{
                //    Logger.LogError("The token request was rejected because the authorization " +
                //                    "code was issued to a different client application.");

                //    return await SendTokenResponseAsync(new OpenIdConnectResponse
                //    {
                //        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                //        ErrorDescription = request.IsAuthorizationCodeGrantType() ?
                //            "The specified authorization code cannot be used by this client application." :
                //            "The specified refresh token cannot be used by this client application."
                //    });
                //}

                // Validate the redirect_uri flowed by the client application during this token request.
                // Note: for pure OAuth2 requests, redirect_uri is only mandatory if the authorization request
                // contained an explicit redirect_uri. OpenID Connect requests MUST include a redirect_uri
                // but the specifications allow proceeding the token request without returning an error
                // if the authorization request didn't contain an explicit redirect_uri.
                // See https://tools.ietf.org/html/rfc6749#section-4.1.3
                // and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation
                var address = ticket.GetProperty(OpenIdConnectConstants.Properties.OriginalRedirectUri);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(address))
                {
                    if (string.IsNullOrEmpty(request.RedirectUri))
                    {
                        Logger.LogError("The token request was rejected because the mandatory 'redirect_uri' " +
                                        "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The mandatory 'redirect_uri' parameter is missing."
                        }));
                    }

                    else if (!string.Equals(address, request.RedirectUri, StringComparison.Ordinal))
                    {
                        Logger.LogError("The token request was rejected because the 'redirect_uri' " +
                                        "parameter didn't correspond to the expected value.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The specified 'redirect_uri' parameter doesn't match the client " +
                                               "redirection endpoint the authorization code was initially sent to."
                        }));
                    }
                }

                // If a code challenge was initially sent in the authorization request and associated with the
                // code, validate the code verifier to ensure the token request is sent by a legit caller.
                var challenge = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallenge);
                if (request.IsAuthorizationCodeGrantType() && !string.IsNullOrEmpty(challenge))
                {
                    // Get the code verifier from the token request.
                    // If it cannot be found, return an invalid_grant error.
                    var verifier = request.CodeVerifier;
                    if (string.IsNullOrEmpty(verifier))
                    {
                        Logger.LogError("The token request was rejected because the required 'code_verifier' " +
                                        "parameter was missing from the grant_type=authorization_code request.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidRequest,
                            ErrorDescription = "The mandatory 'code_verifier' parameter is missing."
                        }));
                    }

                    // Note: the code challenge method is always validated when receiving the authorization request.
                    var method = ticket.GetProperty(OpenIdConnectConstants.Properties.CodeChallengeMethod);

                    Debug.Assert(string.IsNullOrEmpty(method) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Plain, StringComparison.Ordinal) ||
                                 string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal),
                                 "The specified code challenge method should be supported.");

                    // If the S256 challenge method was used, compute the hash corresponding to the code verifier.
                    if (string.Equals(method, OpenIdConnectConstants.CodeChallengeMethods.Sha256, StringComparison.Ordinal))
                    {
                        using (var algorithm = SHA256.Create())
                        {
                            // Compute the SHA-256 hash of the code verifier and encode it using base64-url.
                            // See https://tools.ietf.org/html/rfc7636#section-4.6 for more information.
                            var hash = algorithm.ComputeHash(Encoding.ASCII.GetBytes(request.CodeVerifier));

                            verifier = Base64UrlEncoder.Encode(hash);
                        }
                    }

                    // Compare the verifier and the code challenge: if the two don't match, return an error.
                    // Note: to prevent timing attacks, a time-constant comparer is always used.
                    if (!OpenIdConnectServerHelpers.AreEqual(verifier, challenge))
                    {
                        Logger.LogError("The token request was rejected because the 'code_verifier' was invalid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The specified 'code_verifier' parameter is invalid."
                        }));
                    }
                }

                if (request.IsRefreshTokenGrantType() && !string.IsNullOrEmpty(request.Scope))
                {
                    // When an explicit scope parameter has been included in the token request
                    // but was missing from the initial request, the request MUST be rejected.
                    // See http://tools.ietf.org/html/rfc6749#section-6
                    var scopes = ticket.GetScopes();
                    if (!scopes.Any())
                    {
                        Logger.LogError("The token request was rejected because the 'scope' parameter was not allowed.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The 'scope' parameter is not valid in this context."
                        }));
                    }

                    // When an explicit scope parameter has been included in the token request,
                    // the authorization server MUST ensure that it doesn't contain scopes
                    // that were not allowed during the initial authorization/token request.
                    // See https://tools.ietf.org/html/rfc6749#section-6
                    else if (!new HashSet <string>(scopes).IsSupersetOf(request.GetScopes()))
                    {
                        Logger.LogError("The token request was rejected because the 'scope' parameter was not valid.");

                        return(await SendTokenResponseAsync(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "The specified 'scope' parameter is invalid."
                        }));
                    }
                }
            }

            var notification = new HandleTokenRequestContext(Context, Scheme, Options, request, ticket);
            await Provider.HandleTokenRequest(notification);

            if (notification.Result != null)
            {
                if (notification.Result.Handled)
                {
                    Logger.LogDebug("The token request was handled in user code.");

                    return(true);
                }

                else if (notification.Result.Skipped)
                {
                    Logger.LogDebug("The default token request handling was skipped from user code.");

                    return(false);
                }
            }

            else if (notification.IsRejected)
            {
                Logger.LogError("The token request was rejected with the following error: {Error} ; {Description}",
                                /* Error: */ notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant,
                                /* Description: */ notification.ErrorDescription);

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = notification.Error ?? OpenIdConnectConstants.Errors.InvalidGrant,
                    ErrorDescription = notification.ErrorDescription,
                    ErrorUri = notification.ErrorUri
                }));
            }

            // Flow the changes made to the ticket.
            ticket = notification.Ticket;

            // Ensure an authentication ticket has been provided or return
            // an error code indicating that the request was rejected.
            if (ticket == null)
            {
                Logger.LogError("The token request was rejected because it was not handled by the user code.");

                return(await SendTokenResponseAsync(new OpenIdConnectResponse
                {
                    Error = OpenIdConnectConstants.Errors.InvalidRequest,
                    ErrorDescription = "The token request was rejected by the authorization server."
                }));
            }

            return(await SignInAsync(ticket));
        }
예제 #14
0
        public async Task <IActionResult> Exchange(OpenIdConnectRequest request)
        {
            Debug.Assert(request.IsTokenRequest(),
                         "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                         "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

            if (request.IsPasswordGrantType())
            {
                var user = await _userManager.FindByEmailAsync(request.Username) ??
                           await _userManager.FindByNameAsync(request.Username);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Please check that your email and password is correct"
                    }));
                }

                // Ensure the user is enabled.
                if (!user.IsEnabled)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The specified user account is disabled"
                    }));
                }

                // Validate the username/password parameters and ensure the account is not locked out.
                var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, true);

                // Ensure the user is not already locked out.
                if (result.IsLockedOut)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The specified user account has been suspended"
                    }));
                }

                // Reject the token request if two-factor authentication has been enabled by the user.
                if (result.RequiresTwoFactor)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Invalid login procedure"
                    }));
                }

                // Ensure the user is allowed to sign in.
                if (result.IsNotAllowed)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The specified user is not allowed to sign in"
                    }));
                }

                if (!result.Succeeded)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "Please check that your email and password is correct"
                    }));
                }

                // Create a new authentication ticket.
                var ticket = await CreateTicketAsync(request, user);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the refresh token.
                var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);

                // Retrieve the user profile corresponding to the refresh token.
                // Note: if you want to automatically invalidate the refresh token
                // when the user password/roles change, use the following line instead:
                // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
                var user = await _userManager.GetUserAsync(info.Principal);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The refresh token is no longer valid"
                    }));
                }

                // Ensure the user is still allowed to sign in.
                if (!await _signInManager.CanSignInAsync(user))
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The user is no longer allowed to sign in"
                    }));
                }

                // Create a new authentication ticket, but reuse the properties stored
                // in the refresh token, including the scopes originally granted.
                var ticket = await CreateTicketAsync(request, user);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.IsClientCredentialsGrantType())
            {
                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                var ticket = CreateWorkplaceSignInTicket(request, application);
                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported"
            }));
        }
        public async Task <IActionResult> Exchange(OpenIdConnectRequest request)
        {
            Debug.Assert(request.IsTokenRequest(),
                         "The OpenIddict binder for ASP.NET Core MVC is not registered. " +
                         "Make sure services.AddOpenIddict().AddMvcBinders() is correctly called.");

            if (request.IsPasswordGrantType())
            {
                var user = await _userManager.FindByNameAsync(request.Username);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "用户名或密码错误"
                    }));
                }
                if (user.IsDeleted)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "用户名或密码错误"
                    }));
                }

                // Validate the username/password parameters and ensure the account is not locked out.
                var result = await _signInManager.CheckPasswordSignInAsync(user, request.Password, lockoutOnFailure : true);

                if (!result.Succeeded)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "用户名或密码错误"
                    }));
                }

                // Create a new authentication ticket.
                var ticket = await CreateTicketAsync(request, user);

                await _userLoginLogManager.CreateAsync(new UserLoginLog
                {
                    LoginTime        = DateTime.Now,
                    TrueName         = user.TrueName,
                    UserId           = user.Id,
                    UserName         = user.UserName,
                    OrganizationId   = user.OrganizationId,
                    LoginApplication = request.ClientId,
                    LoginIp          = HttpContext.Connection.RemoteIpAddress.ToString(),
                }, CancellationToken.None);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }

            else if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType())
            {
                // Retrieve the claims principal stored in the authorization code/refresh token.
                var info = await HttpContext.AuthenticateAsync(OpenIdConnectServerDefaults.AuthenticationScheme);

                // Retrieve the user profile corresponding to the authorization code/refresh token.
                // Note: if you want to automatically invalidate the authorization code/refresh token
                // when the user password/roles change, use the following line instead:
                // var user = _signInManager.ValidateSecurityStampAsync(info.Principal);
                var user = await _userManager.GetUserAsync(info.Principal);

                if (user == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The token is no longer valid."
                    }));
                }
                if (user.IsDeleted)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The token is no longer valid."
                    }));
                }

                // Ensure the user is still allowed to sign in.
                if (!await _signInManager.CanSignInAsync(user))
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidGrant,
                        ErrorDescription = "The user is no longer allowed to sign in."
                    }));
                }

                // Create a new authentication ticket, but reuse the properties stored in the
                // authorization code/refresh token, including the scopes originally granted.
                var ticket = await CreateTicketAsync(request, user, info.Properties);

                await _userLoginLogManager.CreateAsync(new UserLoginLog
                {
                    LoginTime        = DateTime.Now,
                    TrueName         = user.TrueName,
                    UserId           = user.Id,
                    UserName         = user.UserName,
                    OrganizationId   = user.OrganizationId,
                    LoginApplication = request.ClientId,
                    LoginIp          = HttpContext.Connection.RemoteIpAddress.ToString(),
                }, CancellationToken.None);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.GrantType == "openid")
            {
                var oid = request["openid"];
                if (oid.HasValue)
                {
                    string openId    = oid.Value.Value.ToString();
                    string hasOpenid = _cache.GetString(openId);
                    if (hasOpenid == "1")
                    {
                        _cache.Remove(oid.Value.Value.ToString());
                    }
                    else
                    {
                        return(BadRequest(new OpenIdConnectResponse
                        {
                            Error = "illegal_request",
                            ErrorDescription = "非法请求"
                        }));
                    }

                    var user = _dbContext.Users.Where(x => x.WXOpenId == openId).FirstOrDefault();
                    if (user == null)
                    {
                        return(BadRequest(new OpenIdConnectResponse
                        {
                            Error = "login_error",
                            ErrorDescription = "用户不存在"
                        }));
                    }
                    if (user.IsDeleted)
                    {
                        return(BadRequest(new OpenIdConnectResponse
                        {
                            Error = "login_error",
                            ErrorDescription = "用户不存在"
                        }));
                    }

                    var ticket = await CreateTicketAsync(request, user);

                    await _userLoginLogManager.CreateAsync(new UserLoginLog
                    {
                        LoginTime        = DateTime.Now,
                        TrueName         = user.TrueName,
                        UserId           = user.Id,
                        UserName         = user.UserName,
                        OrganizationId   = user.OrganizationId,
                        LoginApplication = request.ClientId,
                        LoginIp          = HttpContext.Connection.RemoteIpAddress.ToString(),
                    }, CancellationToken.None);

                    return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
                }
            }
            else if (request.IsClientCredentialsGrantType())
            {
                var application = await _applicationManager.FindByClientIdAsync(request.ClientId, HttpContext.RequestAborted);

                if (application == null)
                {
                    return(BadRequest(new OpenIdConnectResponse
                    {
                        Error = OpenIdConnectConstants.Errors.InvalidClient,
                        ErrorDescription = "The client application was not found in the database."
                    }));
                }

                // Create a new authentication ticket.
                var ticket = CreateApplicationTicket(request, application);

                return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
            }
            else if (request.GrantType == "face")
            {
                var faceImage = request["image"];
                var username  = request["username"];
                if (faceImage.HasValue && username.HasValue)
                {
                    string image = faceImage.Value.Value.ToString();
                    string uid   = username.Value.Value.ToString();

                    BDFaceVerifyRequest faceRequest = new BDFaceVerifyRequest();
                    faceRequest.uid    = uid;
                    faceRequest.topNum = 1;
                    faceRequest.image  = image;

                    var r = await restClient.Post <ResponseMessage <BDFaceVerifyResponse> >("/baidu/face/verify", faceRequest);

                    if (r.IsSuccess() && r.Extension != null && r.Extension.result != null && r.Extension.result[0] >= 80)
                    {
                        var user = _dbContext.Users.Where(x => x.UserName.ToLower() == uid.ToLower()).FirstOrDefault();
                        if (user == null)
                        {
                            return(BadRequest(new OpenIdConnectResponse
                            {
                                Error = "login_error",
                                ErrorDescription = "用户不存在"
                            }));
                        }
                        if (user.IsDeleted)
                        {
                            return(BadRequest(new OpenIdConnectResponse
                            {
                                Error = "login_error",
                                ErrorDescription = "用户不存在"
                            }));
                        }
                        var ticket = await CreateTicketAsync(request, user);

                        await _userLoginLogManager.CreateAsync(new UserLoginLog
                        {
                            LoginTime        = DateTime.Now,
                            TrueName         = user.TrueName,
                            UserId           = user.Id,
                            UserName         = user.UserName,
                            OrganizationId   = user.OrganizationId,
                            LoginApplication = request.ClientId,
                            LoginIp          = HttpContext.Connection.RemoteIpAddress.ToString()
                        }, CancellationToken.None);

                        return(SignIn(ticket.Principal, ticket.Properties, ticket.AuthenticationScheme));
                    }
                    else
                    {
                        return(BadRequest(new OpenIdConnectResponse
                        {
                            Error = OpenIdConnectConstants.Errors.InvalidGrant,
                            ErrorDescription = "人脸认证失败"
                        }));
                    }
                }
            }

            return(BadRequest(new OpenIdConnectResponse
            {
                Error = OpenIdConnectConstants.Errors.UnsupportedGrantType,
                ErrorDescription = "The specified grant type is not supported."
            }));
        }