committed by
GitHub
13 changed files with 1151 additions and 22 deletions
@ -0,0 +1,144 @@ |
|||
/* |
|||
* 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; |
|||
using System.Collections.Immutable; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore; |
|||
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
using static OpenIddict.Server.OpenIddictServerHandlers.Userinfo; |
|||
|
|||
namespace OpenIddict.Server.AspNetCore |
|||
{ |
|||
public static partial class OpenIddictServerAspNetCoreHandlers |
|||
{ |
|||
public static class Userinfo |
|||
{ |
|||
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Userinfo request extraction: |
|||
*/ |
|||
ExtractGetRequest<ExtractUserinfoRequestContext>.Descriptor, |
|||
ExtractAccessToken<ExtractUserinfoRequestContext>.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo request handling: |
|||
*/ |
|||
EnablePassthroughMode.Descriptor, |
|||
InferIssuerFromHost.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo response processing: |
|||
*/ |
|||
ProcessJsonResponse<ApplyUserinfoResponseContext>.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of enabling the pass-through mode for the received request.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class EnablePassthroughMode : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.AddFilter<RequireUserinfoEndpointPassthroughEnabled>() |
|||
.UseSingletonHandler<EnablePassthroughMode>() |
|||
.SetOrder(AttachIssuer.Descriptor.Order - 1_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
|
|||
// this may indicate that the request was incorrectly processed by another server stack.
|
|||
var request = context.Transaction.GetHttpRequest(); |
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The ASP.NET Core HTTP request cannot be resolved."); |
|||
} |
|||
|
|||
context.SkipRequest(); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of infering the issuer URL from the HTTP request host.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class InferIssuerFromHost : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.AddFilter<RequireHttpRequest>() |
|||
.UseSingletonHandler<InferIssuerFromHost>() |
|||
.SetOrder(AttachIssuer.Descriptor.Order + 500) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
|
|||
// this may indicate that the request was incorrectly processed by another server stack.
|
|||
var request = context.Transaction.GetHttpRequest(); |
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The ASP.NET Core HTTP request cannot be resolved."); |
|||
} |
|||
|
|||
// If the issuer was not populated by another handler (e.g from the server options),
|
|||
// try to infer it from the request scheme/host/path base (which requires HTTP/1.1).
|
|||
if (context.Issuer == null) |
|||
{ |
|||
if (!request.Host.HasValue) |
|||
{ |
|||
throw new InvalidOperationException("No host was attached to the HTTP request."); |
|||
} |
|||
|
|||
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer)) |
|||
{ |
|||
throw new InvalidOperationException("The issuer address cannot be inferred from the current request."); |
|||
} |
|||
|
|||
context.Issuer = issuer.AbsoluteUri; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,145 @@ |
|||
/* |
|||
* 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; |
|||
using System.Collections.Immutable; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
using static OpenIddict.Server.OpenIddictServerHandlers.Userinfo; |
|||
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlers; |
|||
using Owin; |
|||
|
|||
namespace OpenIddict.Server.Owin |
|||
{ |
|||
public static partial class OpenIddictServerOwinHandlers |
|||
{ |
|||
public static class Userinfo |
|||
{ |
|||
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Userinfo request extraction: |
|||
*/ |
|||
ExtractGetRequest<ExtractUserinfoRequestContext>.Descriptor, |
|||
ExtractAccessToken<ExtractUserinfoRequestContext>.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo request handling: |
|||
*/ |
|||
EnablePassthroughMode.Descriptor, |
|||
InferIssuerFromHost.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo response processing: |
|||
*/ |
|||
ProcessJsonResponse<ApplyUserinfoResponseContext>.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of enabling the pass-through mode for the received request.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class EnablePassthroughMode : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.AddFilter<RequireUserinfoEndpointPassthroughEnabled>() |
|||
.UseSingletonHandler<EnablePassthroughMode>() |
|||
.SetOrder(AttachIssuer.Descriptor.Order - 1_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
|
|||
// this may indicate that the request was incorrectly processed by another server stack.
|
|||
var request = context.Transaction.GetOwinRequest(); |
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The OWIN request cannot be resolved."); |
|||
} |
|||
|
|||
context.SkipRequest(); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of infering the issuer URL from the HTTP request host.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class InferIssuerFromHost : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.AddFilter<RequireOwinRequest>() |
|||
.UseSingletonHandler<InferIssuerFromHost>() |
|||
.SetOrder(AttachIssuer.Descriptor.Order + 500) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
|
|||
// this may indicate that the request was incorrectly processed by another server stack.
|
|||
var request = context.Transaction.GetOwinRequest(); |
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The OWIN request cannot be resolved."); |
|||
} |
|||
|
|||
// If the issuer was not populated by another handler (e.g from the server options),
|
|||
// try to infer it from the request scheme/host/path base (which requires HTTP/1.1).
|
|||
if (context.Issuer == null) |
|||
{ |
|||
if (string.IsNullOrEmpty(request.Host.Value)) |
|||
{ |
|||
throw new InvalidOperationException("No host was attached to the HTTP request."); |
|||
} |
|||
|
|||
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer)) |
|||
{ |
|||
throw new InvalidOperationException("The issuer address cannot be inferred from the current request."); |
|||
} |
|||
|
|||
context.Issuer = issuer.AbsoluteUri; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,638 @@ |
|||
/* |
|||
* 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; |
|||
using System.Collections.Immutable; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Newtonsoft.Json.Linq; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Server.OpenIddictServerEvents; |
|||
|
|||
namespace OpenIddict.Server |
|||
{ |
|||
public static partial class OpenIddictServerHandlers |
|||
{ |
|||
public static class Userinfo |
|||
{ |
|||
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Userinfo request top-level processing: |
|||
*/ |
|||
ExtractUserinfoRequest.Descriptor, |
|||
ValidateUserinfoRequest.Descriptor, |
|||
HandleUserinfoRequest.Descriptor, |
|||
ApplyUserinfoResponse<ProcessChallengeResponseContext>.Descriptor, |
|||
ApplyUserinfoResponse<ProcessErrorResponseContext>.Descriptor, |
|||
ApplyUserinfoResponse<ProcessRequestContext>.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo request validation: |
|||
*/ |
|||
ValidateAccessTokenParameter.Descriptor, |
|||
ValidateAccessToken.Descriptor, |
|||
|
|||
/* |
|||
* Userinfo request handling: |
|||
*/ |
|||
AttachIssuer.Descriptor, |
|||
AttachPrincipal.Descriptor, |
|||
AttachAudiences.Descriptor, |
|||
AttachClaims.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of extracting userinfo requests and invoking the corresponding event handlers.
|
|||
/// </summary>
|
|||
public class ExtractUserinfoRequest : IOpenIddictServerHandler<ProcessRequestContext> |
|||
{ |
|||
private readonly IOpenIddictServerProvider _provider; |
|||
|
|||
public ExtractUserinfoRequest([NotNull] IOpenIddictServerProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.UseScopedHandler<ExtractUserinfoRequest>() |
|||
.SetOrder(int.MinValue + 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.EndpointType != OpenIddictServerEndpointType.Userinfo) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var notification = new ExtractUserinfoRequestContext(context.Transaction); |
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
context.SkipRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRejected) |
|||
{ |
|||
context.Reject( |
|||
error: notification.Error ?? Errors.InvalidRequest, |
|||
description: notification.ErrorDescription, |
|||
uri: notification.ErrorUri); |
|||
return; |
|||
} |
|||
|
|||
if (notification.Request == null) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The userinfo request was not correctly extracted. To extract userinfo requests, ") |
|||
.Append("create a class implementing 'IOpenIddictServerHandler<ExtractUserinfoRequestContext>' ") |
|||
.AppendLine("and register it using 'services.AddOpenIddict().AddServer().AddEventHandler()'.") |
|||
.ToString()); |
|||
} |
|||
|
|||
context.Logger.LogInformation("The userinfo request was successfully extracted: {Request}.", notification.Request); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of validating userinfo requests and invoking the corresponding event handlers.
|
|||
/// </summary>
|
|||
public class ValidateUserinfoRequest : IOpenIddictServerHandler<ProcessRequestContext> |
|||
{ |
|||
private readonly IOpenIddictServerProvider _provider; |
|||
|
|||
public ValidateUserinfoRequest([NotNull] IOpenIddictServerProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.UseScopedHandler<ValidateUserinfoRequest>() |
|||
.SetOrder(ExtractUserinfoRequest.Descriptor.Order + 1_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.EndpointType != OpenIddictServerEndpointType.Userinfo) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var notification = new ValidateUserinfoRequestContext(context.Transaction); |
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
context.SkipRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRejected) |
|||
{ |
|||
context.Reject( |
|||
error: notification.Error ?? Errors.InvalidRequest, |
|||
description: notification.ErrorDescription, |
|||
uri: notification.ErrorUri); |
|||
return; |
|||
} |
|||
|
|||
// Store the security principal extracted from the authorization code/refresh token as an environment property.
|
|||
context.Transaction.Properties[Properties.OriginalPrincipal] = notification.Principal; |
|||
|
|||
context.Logger.LogInformation("The userinfo request was successfully validated."); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of handling userinfo requests and invoking the corresponding event handlers.
|
|||
/// </summary>
|
|||
public class HandleUserinfoRequest : IOpenIddictServerHandler<ProcessRequestContext> |
|||
{ |
|||
private readonly IOpenIddictServerProvider _provider; |
|||
|
|||
public HandleUserinfoRequest([NotNull] IOpenIddictServerProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.UseScopedHandler<HandleUserinfoRequest>() |
|||
.SetOrder(ValidateUserinfoRequest.Descriptor.Order + 1_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public async ValueTask HandleAsync([NotNull] ProcessRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.EndpointType != OpenIddictServerEndpointType.Userinfo) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var notification = new HandleUserinfoRequestContext(context.Transaction); |
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
context.SkipRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRejected) |
|||
{ |
|||
context.Reject( |
|||
error: notification.Error ?? Errors.InvalidRequest, |
|||
description: notification.ErrorDescription, |
|||
uri: notification.ErrorUri); |
|||
return; |
|||
} |
|||
|
|||
var response = new OpenIddictResponse |
|||
{ |
|||
[Claims.Subject] = notification.Subject, |
|||
[Claims.Address] = notification.Address, |
|||
[Claims.Birthdate] = notification.BirthDate, |
|||
[Claims.Email] = notification.Email, |
|||
[Claims.EmailVerified] = notification.EmailVerified, |
|||
[Claims.FamilyName] = notification.FamilyName, |
|||
[Claims.GivenName] = notification.GivenName, |
|||
[Claims.Issuer] = notification.Issuer, |
|||
[Claims.PhoneNumber] = notification.PhoneNumber, |
|||
[Claims.PhoneNumberVerified] = notification.PhoneNumberVerified, |
|||
[Claims.PreferredUsername] = notification.PreferredUsername, |
|||
[Claims.Profile] = notification.Profile, |
|||
[Claims.Website] = notification.Website |
|||
}; |
|||
|
|||
switch (notification.Audiences.Count) |
|||
{ |
|||
case 0: break; |
|||
|
|||
case 1: |
|||
response[Claims.Audience] = notification.Audiences.ElementAt(0); |
|||
break; |
|||
|
|||
default: |
|||
response[Claims.Audience] = new JArray(notification.Audiences); |
|||
break; |
|||
} |
|||
|
|||
foreach (var claim in notification.Claims) |
|||
{ |
|||
response.SetParameter(claim.Key, claim.Value); |
|||
} |
|||
|
|||
context.Response = response; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of processing userinfo responses and invoking the corresponding event handlers.
|
|||
/// </summary>
|
|||
public class ApplyUserinfoResponse<TContext> : IOpenIddictServerHandler<TContext> where TContext : BaseRequestContext |
|||
{ |
|||
private readonly IOpenIddictServerProvider _provider; |
|||
|
|||
public ApplyUserinfoResponse([NotNull] IOpenIddictServerProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<TContext>() |
|||
.UseScopedHandler<ApplyUserinfoResponse<TContext>>() |
|||
.SetOrder(int.MaxValue - 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public async ValueTask HandleAsync([NotNull] TContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.EndpointType != OpenIddictServerEndpointType.Userinfo) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var notification = new ApplyUserinfoResponseContext(context.Transaction); |
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
context.HandleRequest(); |
|||
return; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
context.SkipRequest(); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting userinfo requests that don't specify an access token.
|
|||
/// </summary>
|
|||
public class ValidateAccessTokenParameter : IOpenIddictServerHandler<ValidateUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateUserinfoRequestContext>() |
|||
.UseSingletonHandler<ValidateAccessTokenParameter>() |
|||
.SetOrder(int.MinValue + 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] ValidateUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(context.Request.AccessToken)) |
|||
{ |
|||
context.Logger.LogError("The userinfo request was rejected because the access token was missing."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidRequest, |
|||
description: "The mandatory 'access_token' parameter is missing."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting userinfo requests that specify an invalid access token.
|
|||
/// </summary>
|
|||
public class ValidateAccessToken : IOpenIddictServerHandler<ValidateUserinfoRequestContext> |
|||
{ |
|||
private readonly IOpenIddictServerProvider _provider; |
|||
|
|||
public ValidateAccessToken([NotNull] IOpenIddictServerProvider provider) |
|||
=> _provider = provider; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<ValidateUserinfoRequestContext>() |
|||
.UseScopedHandler<ValidateAccessToken>() |
|||
.SetOrder(100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public async ValueTask HandleAsync([NotNull] ValidateUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
var notification = new DeserializeAccessTokenContext(context.Transaction) |
|||
{ |
|||
Token = context.Request.AccessToken |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (!notification.IsHandled) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The access token was not correctly processed. This may indicate ") |
|||
.Append("that the event handler responsible of validating access tokens ") |
|||
.Append("was not registered or was explicitly removed from the handlers list.") |
|||
.ToString()); |
|||
} |
|||
|
|||
if (notification.Principal == null) |
|||
{ |
|||
context.Logger.LogError("The userinfo request was rejected because the access token was invalid."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified access token is invalid."); |
|||
|
|||
return; |
|||
} |
|||
|
|||
var date = notification.Principal.GetExpirationDate(); |
|||
if (date.HasValue && date.Value < DateTimeOffset.UtcNow) |
|||
{ |
|||
context.Logger.LogError("The userinfo request was rejected because the access token was expired."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified access token is no longer valid."); |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Attach the principal extracted from the authorization code to the parent event context.
|
|||
context.Principal = notification.Principal; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of attaching the principal
|
|||
/// extracted from the access token to the event context.
|
|||
/// </summary>
|
|||
public class AttachPrincipal : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.UseSingletonHandler<AttachPrincipal>() |
|||
.SetOrder(int.MinValue + 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.Transaction.Properties.TryGetValue(Properties.OriginalPrincipal, out var principal)) |
|||
{ |
|||
context.Principal ??= (ClaimsPrincipal) principal; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of attaching the issuer URL to the userinfo response.
|
|||
/// </summary>
|
|||
public class AttachIssuer : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.UseSingletonHandler<AttachIssuer>() |
|||
.SetOrder(AttachPrincipal.Descriptor.Order + 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.Options.Issuer != null) |
|||
{ |
|||
context.Issuer = context.Options.Issuer.AbsoluteUri; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of attaching the audiences to the userinfo response.
|
|||
/// </summary>
|
|||
public class AttachAudiences : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.UseSingletonHandler<AttachAudiences>() |
|||
.SetOrder(AttachIssuer.Descriptor.Order + 100_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// Note: when receiving an access token, its audiences list cannot be used for the "aud" claim
|
|||
// as the client application is not the intented audience but only an authorized presenter.
|
|||
// See http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
|
|||
context.Audiences.UnionWith(context.Principal.GetPresenters()); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of attaching well known claims to the userinfo response.
|
|||
/// </summary>
|
|||
public class AttachClaims : IOpenIddictServerHandler<HandleUserinfoRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictServerHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleUserinfoRequestContext>() |
|||
.UseSingletonHandler<AttachClaims>() |
|||
.SetOrder(AttachAudiences.Descriptor.Order + 1_000) |
|||
.Build(); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The context associated with the event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] HandleUserinfoRequestContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
context.Subject = context.Principal.GetClaim(Claims.Subject); |
|||
|
|||
// The following claims are all optional and should be excluded when
|
|||
// no corresponding value has been found in the authentication principal:
|
|||
|
|||
if (context.Principal.HasScope(Scopes.Profile)) |
|||
{ |
|||
context.FamilyName = context.Principal.GetClaim(Claims.FamilyName); |
|||
context.GivenName = context.Principal.GetClaim(Claims.GivenName); |
|||
context.BirthDate = context.Principal.GetClaim(Claims.Birthdate); |
|||
} |
|||
|
|||
if (context.Principal.HasScope(Scopes.Email)) |
|||
{ |
|||
context.Email = context.Principal.GetClaim(Claims.Email); |
|||
} |
|||
|
|||
if (context.Principal.HasScope(Scopes.Phone)) |
|||
{ |
|||
context.PhoneNumber = context.Principal.GetClaim(Claims.PhoneNumber); |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue