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