You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
9.8 KiB
185 lines
9.8 KiB
/*
|
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
|
* the license and the contributors participating to this project.
|
|
*/
|
|
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using AspNet.Security.OpenIdConnect.Extensions;
|
|
using AspNet.Security.OpenIdConnect.Primitives;
|
|
using AspNet.Security.OpenIdConnect.Server;
|
|
using JetBrains.Annotations;
|
|
using Microsoft.AspNetCore.Authentication;
|
|
using OpenIddict.Abstractions;
|
|
|
|
namespace OpenIddict.Server
|
|
{
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public partial class OpenIddictServerProvider<TApplication, TAuthorization, TScope, TToken> : OpenIdConnectServerProvider
|
|
where TApplication : class where TAuthorization : class where TScope : class where TToken : class
|
|
{
|
|
public override Task ProcessChallengeResponse([NotNull] ProcessChallengeResponseContext context)
|
|
{
|
|
Debug.Assert(context.Request.IsAuthorizationRequest() ||
|
|
context.Request.IsTokenRequest(),
|
|
"The request should be an authorization or token request.");
|
|
|
|
// Add the custom properties that are marked as public
|
|
// as authorization or token response properties.
|
|
var parameters = GetParameters(context.HttpContext, context.Request, context.Properties);
|
|
foreach (var parameter in parameters)
|
|
{
|
|
context.Response.AddParameter(parameter.Item2, parameter.Item3);
|
|
}
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
|
|
public override async Task ProcessSigninResponse([NotNull] ProcessSigninResponseContext context)
|
|
{
|
|
var options = (OpenIddictServerOptions) context.Options;
|
|
|
|
Debug.Assert(context.Request.IsAuthorizationRequest() ||
|
|
context.Request.IsTokenRequest(),
|
|
"The request should be an authorization or token request.");
|
|
|
|
if (context.Request.IsTokenRequest() && (context.Request.IsAuthorizationCodeGrantType() ||
|
|
context.Request.IsRefreshTokenGrantType()))
|
|
{
|
|
// Note: when handling a grant_type=authorization_code or refresh_token request,
|
|
// the OpenID Connect server middleware allows creating authentication tickets
|
|
// that are completely disconnected from the original code or refresh token ticket.
|
|
// This scenario is deliberately not supported in OpenIddict and all the tickets
|
|
// must be linked. To ensure the properties are flowed from the authorization code
|
|
// or the refresh token to the new ticket, they are manually restored if necessary.
|
|
if (!context.Ticket.Properties.HasProperty(OpenIdConnectConstants.Properties.TokenId))
|
|
{
|
|
// Retrieve the original authentication ticket from the request properties.
|
|
var ticket = context.Request.GetProperty<AuthenticationTicket>(
|
|
OpenIddictConstants.Properties.AuthenticationTicket);
|
|
Debug.Assert(ticket != null, "The authentication ticket shouldn't be null.");
|
|
|
|
foreach (var property in ticket.Properties.Items)
|
|
{
|
|
// Don't override the properties that have been
|
|
// manually set on the new authentication ticket.
|
|
if (context.Ticket.HasProperty(property.Key))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
context.Ticket.AddProperty(property.Key, property.Value);
|
|
}
|
|
|
|
// Always include the "openid" scope when the developer doesn't explicitly call SetScopes.
|
|
// Note: the application is allowed to specify a different "scopes": in this case,
|
|
// don't replace the "scopes" property stored in the authentication ticket.
|
|
if (context.Request.HasScope(OpenIdConnectConstants.Scopes.OpenId) && !context.Ticket.HasScope())
|
|
{
|
|
context.Ticket.SetScopes(OpenIdConnectConstants.Scopes.OpenId);
|
|
}
|
|
|
|
context.IncludeIdentityToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OpenId);
|
|
}
|
|
|
|
context.IncludeRefreshToken = context.Ticket.HasScope(OpenIdConnectConstants.Scopes.OfflineAccess);
|
|
|
|
// Always include a refresh token for grant_type=refresh_token requests if
|
|
// rolling tokens are enabled and if the offline_access scope was specified.
|
|
if (context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
context.IncludeRefreshToken &= options.UseRollingTokens;
|
|
}
|
|
|
|
// If token revocation was explicitly disabled,
|
|
// none of the following security routines apply.
|
|
if (options.DisableTokenRevocation)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var token = context.Request.GetProperty<TToken>($"{OpenIddictConstants.Properties.Token}:{context.Ticket.GetTokenId()}");
|
|
Debug.Assert(token != null, "The token shouldn't be null.");
|
|
|
|
// If rolling tokens are enabled or if the request is a grant_type=authorization_code request,
|
|
// mark the authorization code or the refresh token as redeemed to prevent future reuses.
|
|
// If the operation fails, return an error indicating the code/token is no longer valid.
|
|
// See https://tools.ietf.org/html/rfc6749#section-6 for more information.
|
|
if (options.UseRollingTokens || context.Request.IsAuthorizationCodeGrantType())
|
|
{
|
|
if (!await TryRedeemTokenAsync(token, context.HttpContext))
|
|
{
|
|
context.Reject(
|
|
error: OpenIdConnectConstants.Errors.InvalidGrant,
|
|
description: context.Request.IsAuthorizationCodeGrantType() ?
|
|
"The specified authorization code is no longer valid." :
|
|
"The specified refresh token is no longer valid.");
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (context.Request.IsRefreshTokenGrantType())
|
|
{
|
|
// When rolling tokens are enabled, try to revoke all the previously issued tokens
|
|
// associated with the authorization if the request is a refresh_token request.
|
|
// If the operation fails, silently ignore the error and keep processing the request:
|
|
// this may indicate that one of the revoked tokens was modified by a concurrent request.
|
|
if (options.UseRollingTokens)
|
|
{
|
|
await TryRevokeTokensAsync(context.Ticket, context.HttpContext);
|
|
}
|
|
|
|
// When rolling tokens are disabled, try to extend the expiration date
|
|
// of the existing token instead of returning a new refresh token
|
|
// with a new expiration date if sliding expiration was not disabled.
|
|
// If the operation fails, silently ignore the error and keep processing
|
|
// the request: this may indicate that a concurrent refresh token request
|
|
// already updated the expiration date associated with the refresh token.
|
|
if (!options.UseRollingTokens && options.UseSlidingExpiration)
|
|
{
|
|
await TryExtendTokenAsync(token, context.Ticket, context.HttpContext, options);
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no authorization was explicitly attached to the authentication ticket,
|
|
// create an ad hoc authorization if an authorization code or a refresh token
|
|
// is going to be returned to the client application as part of the response.
|
|
if (!context.Ticket.HasProperty(OpenIddictConstants.Properties.AuthorizationId) &&
|
|
(context.IncludeAuthorizationCode || context.IncludeRefreshToken))
|
|
{
|
|
await CreateAuthorizationAsync(context.Ticket, options, context.HttpContext, context.Request);
|
|
}
|
|
|
|
// Add the custom properties that are marked as public as authorization or
|
|
// token response properties and remove them from the authentication ticket
|
|
// so they are not persisted in the authorization code/access/refresh token.
|
|
// Note: make sure the foreach statement iterates on a copy of the ticket
|
|
// as the property collection is modified when the property is removed.
|
|
var parameters = GetParameters(context.HttpContext, context.Request, context.Ticket.Properties);
|
|
foreach (var parameter in parameters.ToArray())
|
|
{
|
|
context.Response.AddParameter(parameter.Item2, parameter.Item3);
|
|
context.Ticket.RemoveProperty(parameter.Item1);
|
|
}
|
|
}
|
|
|
|
public override Task ProcessSignoutResponse([NotNull] ProcessSignoutResponseContext context)
|
|
{
|
|
Debug.Assert(context.Request.IsLogoutRequest(), "The request should be a logout request.");
|
|
|
|
// Add the custom properties that are marked as public as logout response properties.
|
|
var parameters = GetParameters(context.HttpContext, context.Request, context.Properties);
|
|
foreach (var parameter in parameters)
|
|
{
|
|
context.Response.AddParameter(parameter.Item2, parameter.Item3);
|
|
}
|
|
|
|
return Task.FromResult(0);
|
|
}
|
|
}
|
|
}
|