committed by
GitHub
89 changed files with 6410 additions and 88 deletions
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netcoreapp3.0</TargetFramework> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>ASP.NET Core integration package for the OpenIddict validation services.</Description> |
|||
<PackageTags>$(PackageTags);validation;aspnetcore</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,73 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using JetBrains.Annotations; |
|||
using OpenIddict.Validation.AspNetCore; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure
|
|||
/// the OpenIddict validation ASP.NET Core integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationAspNetCoreBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationAspNetCoreBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationAspNetCoreBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation ASP.NET Core configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationAspNetCoreBuilder"/>.</returns>
|
|||
public OpenIddictValidationAspNetCoreBuilder Configure([NotNull] Action<OpenIddictValidationAspNetCoreOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,98 @@ |
|||
/* |
|||
* 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.Diagnostics; |
|||
using System.Text; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict validation configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationAspNetCoreConfiguration : IConfigureOptions<AuthenticationOptions>, |
|||
IConfigureNamedOptions<OpenIddictValidationOptions>, |
|||
IPostConfigureOptions<AuthenticationOptions> |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation handler in the global authentication options.
|
|||
/// </summary>
|
|||
/// <param name="options">The options instance to initialize.</param>
|
|||
public void Configure([NotNull] AuthenticationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// If a handler was already registered and the type doesn't correspond to the OpenIddict handler, throw an exception.
|
|||
if (options.SchemeMap.TryGetValue(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, out var builder) && |
|||
builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler)) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The OpenIddict ASP.NET Core validation handler cannot be registered as an authentication scheme.") |
|||
.Append("This may indicate that an instance of another handler was registered with the same scheme.") |
|||
.ToString()); |
|||
} |
|||
|
|||
options.AddScheme<OpenIddictValidationAspNetCoreHandler>( |
|||
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme, displayName: null); |
|||
} |
|||
|
|||
public void Configure([NotNull] OpenIddictValidationOptions options) |
|||
=> Debug.Fail("This infrastructure method shouldn't be called"); |
|||
|
|||
public void Configure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components.
|
|||
foreach (var handler in OpenIddictValidationAspNetCoreHandlers.DefaultHandlers) |
|||
{ |
|||
options.DefaultHandlers.Add(handler); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Ensures that the authentication configuration is in a consistent and valid state.
|
|||
/// </summary>
|
|||
/// <param name="name">The authentication scheme associated with the handler instance.</param>
|
|||
/// <param name="options">The options instance to initialize.</param>
|
|||
public void PostConfigure([CanBeNull] string name, [NotNull] AuthenticationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
bool TryValidate(string scheme) |
|||
{ |
|||
// If the scheme was not set or if it cannot be found in the map, return true.
|
|||
if (string.IsNullOrEmpty(scheme) || !options.SchemeMap.TryGetValue(scheme, out var builder)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return builder.HandlerType != typeof(OpenIddictValidationAspNetCoreHandler); |
|||
} |
|||
|
|||
if (!TryValidate(options.DefaultSignInScheme) || !TryValidate(options.DefaultSignOutScheme)) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The OpenIddict ASP.NET Core validation cannot be used as the default sign-in/sign-out handler.") |
|||
.Append("Make sure that neither DefaultSignInScheme nor DefaultSignOutScheme ") |
|||
.Append("point to an instance of the OpenIddict ASP.NET Core validation handler.") |
|||
.ToString()); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes common constants used by the OpenIddict ASP.NET Core host.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationAspNetCoreConstants |
|||
{ |
|||
public static class Cache |
|||
{ |
|||
public const string AuthorizationRequest = "openiddict-authorization-request:"; |
|||
public const string LogoutRequest = "openiddict-logout-request:"; |
|||
} |
|||
|
|||
public static class Properties |
|||
{ |
|||
public const string Error = ".error"; |
|||
public const string ErrorDescription = ".error_description"; |
|||
public const string ErrorUri = ".error_uri"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/* |
|||
* 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 Microsoft.AspNetCore.Authentication; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the default values used by the OpenIddict validation handler.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationAspNetCoreDefaults |
|||
{ |
|||
/// <summary>
|
|||
/// Default value for <see cref="AuthenticationScheme.Name"/>.
|
|||
/// </summary>
|
|||
public const string AuthenticationScheme = "OpenIddict.Validation.AspNetCore"; |
|||
} |
|||
} |
|||
@ -0,0 +1,88 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.AspNetCore; |
|||
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlerFilters; |
|||
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlers; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationAspNetCoreExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation services for ASP.NET Core in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationAspNetCoreBuilder"/>.</returns>
|
|||
public static OpenIddictValidationAspNetCoreBuilder UseAspNetCore([NotNull] this OpenIddictValidationBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
builder.Services.AddAuthentication(); |
|||
|
|||
builder.Services.TryAddScoped<OpenIddictValidationAspNetCoreHandler>(); |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict ASP.NET Core validation components.
|
|||
// Note: the order used here is not important, as the actual order is set in the options.
|
|||
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
|||
|
|||
// Register the built-in filters used by the default OpenIddict ASP.NET Core validation event handlers.
|
|||
builder.Services.TryAddSingleton<RequireHttpRequest>(); |
|||
|
|||
// Register the option initializer used by the OpenIddict ASP.NET Core validation integration services.
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializers are only registered once.
|
|||
builder.Services.TryAddEnumerable(new[] |
|||
{ |
|||
ServiceDescriptor.Singleton<IConfigureOptions<AuthenticationOptions>, OpenIddictValidationAspNetCoreConfiguration>(), |
|||
ServiceDescriptor.Singleton<IPostConfigureOptions<AuthenticationOptions>, OpenIddictValidationAspNetCoreConfiguration>(), |
|||
|
|||
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationAspNetCoreConfiguration>() |
|||
}); |
|||
|
|||
return new OpenIddictValidationAspNetCoreBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation services for ASP.NET Core in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder UseAspNetCore( |
|||
[NotNull] this OpenIddictValidationBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationAspNetCoreBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.UseAspNetCore()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the current validation transaction to the ASP.NET Core host.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationAspNetCoreFeature |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the validation transaction that encapsulates all specific
|
|||
/// information about an individual OpenID Connect validation request.
|
|||
/// </summary>
|
|||
public OpenIddictValidationTransaction Transaction { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,225 @@ |
|||
/* |
|||
* 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.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using Properties = OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreConstants.Properties; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the logic necessary to extract, validate and handle OpenID Connect requests.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationAspNetCoreHandler : AuthenticationHandler<OpenIddictValidationAspNetCoreOptions>, |
|||
IAuthenticationRequestHandler |
|||
{ |
|||
private readonly IOpenIddictValidationProvider _provider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationAspNetCoreHandler"/> class.
|
|||
/// </summary>
|
|||
public OpenIddictValidationAspNetCoreHandler( |
|||
[NotNull] IOpenIddictValidationProvider provider, |
|||
[NotNull] IOptionsMonitor<OpenIddictValidationAspNetCoreOptions> options, |
|||
[NotNull] ILoggerFactory logger, |
|||
[NotNull] UrlEncoder encoder, |
|||
[NotNull] ISystemClock clock) |
|||
: base(options, logger, encoder, clock) |
|||
=> _provider = provider; |
|||
|
|||
public async Task<bool> HandleRequestAsync() |
|||
{ |
|||
// Note: the transaction may be already attached when replaying an ASP.NET Core request
|
|||
// (e.g when using the built-in status code pages middleware with the re-execute mode).
|
|||
var transaction = Context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction; |
|||
if (transaction == null) |
|||
{ |
|||
// Create a new transaction and attach the HTTP request to make it available to the ASP.NET Core handlers.
|
|||
transaction = await _provider.CreateTransactionAsync(); |
|||
transaction.Properties[typeof(HttpRequest).FullName] = new WeakReference<HttpRequest>(Request); |
|||
|
|||
// Attach the OpenIddict validation transaction to the ASP.NET Core features
|
|||
// so that it can retrieved while performing challenge/forbid operations.
|
|||
Context.Features.Set(new OpenIddictValidationAspNetCoreFeature { Transaction = transaction }); |
|||
} |
|||
|
|||
var context = new ProcessRequestContext(transaction); |
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.IsRequestHandled) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
else if (context.IsRequestSkipped) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
var notification = new ProcessErrorContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = context.Error ?? Errors.InvalidRequest, |
|||
ErrorDescription = context.ErrorDescription, |
|||
ErrorUri = context.ErrorUri |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The OpenID Connect response was not correctly processed. This may indicate ") |
|||
.Append("that the event handler responsible of processing OpenID Connect responses ") |
|||
.Append("was not registered or was explicitly removed from the handlers list.") |
|||
.ToString()); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
protected override async Task<AuthenticateResult> HandleAuthenticateAsync() |
|||
{ |
|||
var transaction = Context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction; |
|||
if (transaction == null) |
|||
{ |
|||
throw new InvalidOperationException("An identity cannot be extracted from this request."); |
|||
} |
|||
|
|||
var context = new ProcessAuthenticationContext(transaction); |
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.Principal == null || context.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return AuthenticateResult.NoResult(); |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
var builder = new StringBuilder(); |
|||
|
|||
if (!string.IsNullOrEmpty(context.Error)) |
|||
{ |
|||
builder.AppendLine("An error occurred while authenticating the current request:"); |
|||
builder.AppendFormat("Error code: ", context.Error); |
|||
|
|||
if (!string.IsNullOrEmpty(context.ErrorDescription)) |
|||
{ |
|||
builder.AppendLine(); |
|||
builder.AppendFormat("Error description: ", context.ErrorDescription); |
|||
} |
|||
|
|||
if (!string.IsNullOrEmpty(context.ErrorUri)) |
|||
{ |
|||
builder.AppendLine(); |
|||
builder.AppendFormat("Error URI: ", context.ErrorUri); |
|||
} |
|||
} |
|||
|
|||
else |
|||
{ |
|||
builder.Append("An unknown error occurred while authenticating the current request."); |
|||
} |
|||
|
|||
return AuthenticateResult.Fail(new Exception(builder.ToString()) |
|||
{ |
|||
// Note: the error details are stored as additional exception properties,
|
|||
// which is similar to what other ASP.NET Core security handlers do.
|
|||
Data = |
|||
{ |
|||
[Parameters.Error] = context.Error, |
|||
[Parameters.ErrorDescription] = context.ErrorDescription, |
|||
[Parameters.ErrorUri] = context.ErrorUri |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return AuthenticateResult.Success(new AuthenticationTicket( |
|||
context.Principal, |
|||
OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)); |
|||
} |
|||
|
|||
protected override async Task HandleChallengeAsync([CanBeNull] AuthenticationProperties properties) |
|||
{ |
|||
var transaction = Context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction; |
|||
if (transaction == null) |
|||
{ |
|||
throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint."); |
|||
} |
|||
|
|||
var context = new ProcessChallengeContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = GetProperty(properties, Properties.Error), |
|||
ErrorDescription = GetProperty(properties, Properties.ErrorDescription), |
|||
ErrorUri = GetProperty(properties, Properties.ErrorUri) |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
var notification = new ProcessErrorContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = context.Error ?? Errors.InvalidRequest, |
|||
ErrorDescription = context.ErrorDescription, |
|||
ErrorUri = context.ErrorUri |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The OpenID Connect response was not correctly processed. This may indicate ") |
|||
.Append("that the event handler responsible of processing OpenID Connect responses ") |
|||
.Append("was not registered or was explicitly removed from the handlers list.") |
|||
.ToString()); |
|||
} |
|||
|
|||
static string GetProperty(AuthenticationProperties properties, string name) |
|||
=> properties != null && properties.Items.TryGetValue(name, out string value) ? value : null; |
|||
} |
|||
|
|||
protected override Task HandleForbiddenAsync([CanBeNull] AuthenticationProperties properties) |
|||
=> HandleChallengeAsync(properties); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Contains a collection of event handler filters commonly used by the ASP.NET Core handlers.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public static class OpenIddictValidationAspNetCoreHandlerFilters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no ASP.NET Core request can be found.
|
|||
/// </summary>
|
|||
public class RequireHttpRequest : IOpenIddictValidationHandlerFilter<BaseContext> |
|||
{ |
|||
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new ValueTask<bool>(context.Transaction.GetHttpRequest() != null); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,303 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Net.Http.Headers; |
|||
using Newtonsoft.Json; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.AspNetCore.OpenIddictValidationAspNetCoreHandlerFilters; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static partial class OpenIddictValidationAspNetCoreHandlers |
|||
{ |
|||
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Request top-level processing: |
|||
*/ |
|||
InferIssuerFromHost.Descriptor, |
|||
ExtractGetOrPostRequest.Descriptor, |
|||
ExtractAccessToken.Descriptor, |
|||
|
|||
/* |
|||
* Response processing: |
|||
*/ |
|||
ProcessJsonResponse<ProcessChallengeContext>.Descriptor, |
|||
ProcessJsonResponse<ProcessErrorContext>.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class InferIssuerFromHost : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireHttpRequest>() |
|||
.UseSingletonHandler<InferIssuerFromHost>() |
|||
.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] ProcessRequestContext 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."); |
|||
} |
|||
|
|||
// Only use the current host as the issuer if the
|
|||
// issuer was not explicitly set in the options.
|
|||
if (context.Issuer != null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (!request.Host.HasValue) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidRequest, |
|||
description: "The mandatory 'Host' header is missing."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer) || |
|||
!issuer.IsWellFormedOriginalString()) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidRequest, |
|||
description: "The specified 'Host' header is invalid."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
context.Issuer = issuer; |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class ExtractGetOrPostRequest : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireHttpRequest>() |
|||
.UseSingletonHandler<ExtractGetOrPostRequest>() |
|||
.SetOrder(InferIssuerFromHost.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)); |
|||
} |
|||
|
|||
// 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 (HttpMethods.IsGet(request.Method)) |
|||
{ |
|||
context.Request = new OpenIddictRequest(request.Query); |
|||
} |
|||
|
|||
else if (HttpMethods.IsPost(request.Method) && !string.IsNullOrEmpty(request.ContentType) && |
|||
request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
context.Request = new OpenIddictRequest(await request.ReadFormAsync(request.HttpContext.RequestAborted)); |
|||
} |
|||
|
|||
else |
|||
{ |
|||
context.Request = new OpenIddictRequest(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class ExtractAccessToken : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireHttpRequest>() |
|||
.UseSingletonHandler<ExtractAccessToken>() |
|||
.SetOrder(ExtractGetOrPostRequest.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] ProcessRequestContext 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."); |
|||
} |
|||
|
|||
string header = request.Headers[HeaderNames.Authorization]; |
|||
if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Attach the access token to the request message.
|
|||
context.Request.AccessToken = header.Substring("Bearer ".Length); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
|
|||
/// </summary>
|
|||
public class ProcessJsonResponse<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>() |
|||
.AddFilter<RequireHttpRequest>() |
|||
.UseSingletonHandler<ProcessJsonResponse<TContext>>() |
|||
.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] TContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.Response == null) |
|||
{ |
|||
throw new InvalidOperationException("This handler cannot be invoked without a response attached."); |
|||
} |
|||
|
|||
// 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.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response); |
|||
|
|||
using (var buffer = new MemoryStream()) |
|||
using (var writer = new JsonTextWriter(new StreamWriter(buffer))) |
|||
{ |
|||
var serializer = JsonSerializer.CreateDefault(); |
|||
serializer.Serialize(writer, context.Response); |
|||
|
|||
writer.Flush(); |
|||
|
|||
if (!string.IsNullOrEmpty(context.Response.Error)) |
|||
{ |
|||
if (context.Issuer == null) |
|||
{ |
|||
throw new InvalidOperationException("The issuer address cannot be inferred from the current request."); |
|||
} |
|||
|
|||
request.HttpContext.Response.StatusCode = 401; |
|||
|
|||
request.HttpContext.Response.Headers[HeaderNames.WWWAuthenticate] = new StringBuilder() |
|||
.Append(Schemes.Bearer) |
|||
.Append(' ') |
|||
.Append(Parameters.Realm) |
|||
.Append("=\"") |
|||
.Append(context.Issuer.AbsoluteUri) |
|||
.Append('"') |
|||
.ToString(); |
|||
} |
|||
|
|||
request.HttpContext.Response.ContentLength = buffer.Length; |
|||
request.HttpContext.Response.ContentType = "application/json;charset=UTF-8"; |
|||
|
|||
buffer.Seek(offset: 0, loc: SeekOrigin.Begin); |
|||
await buffer.CopyToAsync(request.HttpContext.Response.Body, 4096, request.HttpContext.RequestAborted); |
|||
} |
|||
|
|||
context.HandleRequest(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,92 @@ |
|||
/* |
|||
* 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 JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.Http; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.AspNetCore; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace Microsoft.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes companion extensions for the OpenIddict/ASP.NET Core integration.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationAspNetCoreHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// Retrieves the <see cref="HttpRequest"/> instance stored in the <see cref="OpenIddictValidationTransaction"/> properties.
|
|||
/// </summary>
|
|||
/// <param name="transaction">The transaction instance.</param>
|
|||
/// <returns>The <see cref="HttpRequest"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static HttpRequest GetHttpRequest([NotNull] this OpenIddictValidationTransaction transaction) |
|||
{ |
|||
if (transaction == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(transaction)); |
|||
} |
|||
|
|||
if (!transaction.Properties.TryGetValue(typeof(HttpRequest).FullName, out object property)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (property is WeakReference<HttpRequest> reference && reference.TryGetTarget(out HttpRequest request)) |
|||
{ |
|||
return request; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictValidationEndpointType"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationEndpointType"/>.</returns>
|
|||
public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType([NotNull] this HttpContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction?.EndpointType ?? default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictRequest"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static OpenIddictRequest GetOpenIddictValidationRequest([NotNull] this HttpContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction?.Request; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictResponse"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static OpenIddictResponse GetOpenIddictValidationResponse([NotNull] this HttpContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Features.Get<OpenIddictValidationAspNetCoreFeature>()?.Transaction?.Response; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
/* |
|||
* 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 Microsoft.AspNetCore.Authentication; |
|||
|
|||
namespace OpenIddict.Validation.AspNetCore |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict ASP.NET Core validation integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationAspNetCoreOptions : AuthenticationSchemeOptions |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
/* |
|||
* 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.IO; |
|||
using System.Security.Claims; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
public interface IOpenIddictValidationDataProtectionFormatter |
|||
{ |
|||
ClaimsPrincipal ReadToken([NotNull] BinaryReader reader); |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>ASP.NET Core Data Protection integration package for the OpenIddict validation services.</Description> |
|||
<PackageTags>$(PackageTags);validation;dataprotection</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.DataProtection" Version="$(AspNetCoreVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,105 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.DataProtection; |
|||
using OpenIddict.Validation.DataProtection; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure the
|
|||
/// OpenIddict ASP.NET Core Data Protection integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationDataProtectionBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationDataProtectionBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationDataProtectionBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation ASP.NET Core Data Protection configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationDataProtectionBuilder"/>.</returns>
|
|||
public OpenIddictValidationDataProtectionBuilder Configure([NotNull] Action<OpenIddictValidationDataProtectionOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures OpenIddict to use a specific data protection provider
|
|||
/// instead of relying on the default instance provided by the DI container.
|
|||
/// </summary>
|
|||
/// <param name="provider">The data protection provider used to create token protectors.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationDataProtectionBuilder"/>.</returns>
|
|||
public OpenIddictValidationDataProtectionBuilder UseDataProtectionProvider([NotNull] IDataProtectionProvider provider) |
|||
{ |
|||
if (provider == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(provider)); |
|||
} |
|||
|
|||
return Configure(options => options.DataProtectionProvider = provider); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures OpenIddict to use a specific formatter instead of relying on the default instance.
|
|||
/// </summary>
|
|||
/// <param name="formatter">The formatter used to read and write tokens.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationDataProtectionBuilder"/>.</returns>
|
|||
public OpenIddictValidationDataProtectionBuilder UseFormatter([NotNull] IOpenIddictValidationDataProtectionFormatter formatter) |
|||
{ |
|||
if (formatter == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(formatter)); |
|||
} |
|||
|
|||
return Configure(options => options.Formatter = formatter); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,64 @@ |
|||
/* |
|||
* 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 JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.DataProtection; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict ASP.NET Core Data Protection configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationDataProtectionConfiguration : IConfigureOptions<OpenIddictValidationOptions>, |
|||
IPostConfigureOptions<OpenIddictValidationDataProtectionOptions> |
|||
{ |
|||
private readonly IDataProtectionProvider _dataProtectionProvider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationDataProtectionConfiguration"/> class.
|
|||
/// </summary>
|
|||
/// <param name="dataProtectionProvider">The ASP.NET Core Data Protection provider.</param>
|
|||
public OpenIddictValidationDataProtectionConfiguration([NotNull] IDataProtectionProvider dataProtectionProvider) |
|||
=> _dataProtectionProvider = dataProtectionProvider; |
|||
|
|||
public void Configure([NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Use empty token validation parameters to ensure the core OpenIddict validation components
|
|||
// don't throw an exception stating that an issuer or a metadata address was not set.
|
|||
options.TokenValidationParameters = new TokenValidationParameters(); |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict Data Protection validation components.
|
|||
foreach (var handler in OpenIddictValidationDataProtectionHandlers.DefaultHandlers) |
|||
{ |
|||
options.DefaultHandlers.Add(handler); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Populates the default OpenIddict ASP.NET Core Data Protection validation options
|
|||
/// and ensures that the configuration is in a consistent and valid state.
|
|||
/// </summary>
|
|||
/// <param name="name">The authentication scheme associated with the handler instance.</param>
|
|||
/// <param name="options">The options instance to initialize.</param>
|
|||
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationDataProtectionOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
options.DataProtectionProvider ??= _dataProtectionProvider; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,57 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
public static class OpenIddictValidationDataProtectionConstants |
|||
{ |
|||
public static class Properties |
|||
{ |
|||
public const string AccessTokenLifetime = ".access_token_lifetime"; |
|||
public const string AuthorizationCodeLifetime = ".authorization_code_lifetime"; |
|||
public const string Audiences = ".audiences"; |
|||
public const string CodeChallenge = ".code_challenge"; |
|||
public const string CodeChallengeMethod = ".code_challenge_method"; |
|||
public const string DataProtector = ".data_protector"; |
|||
public const string Expires = ".expires"; |
|||
public const string IdentityTokenLifetime = ".identity_token_lifetime"; |
|||
public const string InternalAuthorizationId = ".internal_authorization_id"; |
|||
public const string InternalTokenId = ".internal_token_id"; |
|||
public const string Issued = ".issued"; |
|||
public const string Nonce = ".nonce"; |
|||
public const string OriginalRedirectUri = ".original_redirect_uri"; |
|||
public const string Presenters = ".presenters"; |
|||
public const string RefreshTokenLifetime = ".refresh_token_lifetime"; |
|||
public const string Resources = ".resources"; |
|||
public const string Scopes = ".scopes"; |
|||
public const string TokenId = ".token_id"; |
|||
public const string TokenUsage = ".token_usage"; |
|||
} |
|||
|
|||
public static class Purposes |
|||
{ |
|||
public static class Features |
|||
{ |
|||
public const string ReferenceTokens = "UseReferenceTokens"; |
|||
} |
|||
|
|||
public static class Formats |
|||
{ |
|||
public const string AccessToken = "AccessTokenFormat"; |
|||
} |
|||
|
|||
public static class Handlers |
|||
{ |
|||
public const string Server = "OpenIdConnectServerHandler"; |
|||
} |
|||
|
|||
public static class Schemes |
|||
{ |
|||
public const string Server = "ASOS"; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.DataProtection; |
|||
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionHandlers; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict ASP.NET Core Data Protection validation services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationDataProtectionExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationDataProtectionBuilder UseDataProtection([NotNull] this OpenIddictValidationBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
builder.Services.AddDataProtection(); |
|||
|
|||
// Register the built-in validation event handlers used by the OpenIddict Data Protection components.
|
|||
// Note: the order used here is not important, as the actual order is set in the options.
|
|||
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
|||
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
|
|||
builder.Services.TryAddEnumerable(new[] |
|||
{ |
|||
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationDataProtectionConfiguration>(), |
|||
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictValidationDataProtectionOptions>, OpenIddictValidationDataProtectionConfiguration>() |
|||
}); |
|||
|
|||
return new OpenIddictValidationDataProtectionBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict ASP.NET Core Data Protection validation services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder UseDataProtection( |
|||
[NotNull] this OpenIddictValidationBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationDataProtectionBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.UseDataProtection()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,183 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using JetBrains.Annotations; |
|||
using Newtonsoft.Json.Linq; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using Properties = OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants.Properties; |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
public class OpenIddictValidationDataProtectionFormatter : IOpenIddictValidationDataProtectionFormatter |
|||
{ |
|||
public ClaimsPrincipal ReadToken([NotNull] BinaryReader reader) |
|||
{ |
|||
if (reader == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(reader)); |
|||
} |
|||
|
|||
var (principal, properties) = Read(reader, version: 5); |
|||
if (principal == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
// Tokens serialized using the ASP.NET Core Data Protection stack are compound
|
|||
// of both claims and special authentication properties. To ensure existing tokens
|
|||
// can be reused, well-known properties are manually mapped to their claims equivalents.
|
|||
|
|||
return principal |
|||
.SetAudiences(GetArrayProperty(properties, Properties.Audiences)) |
|||
.SetCreationDate(GetDateProperty(properties, Properties.Issued)) |
|||
.SetExpirationDate(GetDateProperty(properties, Properties.Expires)) |
|||
.SetPresenters(GetArrayProperty(properties, Properties.Presenters)) |
|||
.SetScopes(GetArrayProperty(properties, Properties.Scopes)) |
|||
|
|||
.SetClaim(Claims.Private.AccessTokenLifetime, GetProperty(properties, Properties.AccessTokenLifetime)) |
|||
.SetClaim(Claims.Private.AuthorizationCodeLifetime, GetProperty(properties, Properties.AuthorizationCodeLifetime)) |
|||
.SetClaim(Claims.Private.AuthorizationId, GetProperty(properties, Properties.InternalAuthorizationId)) |
|||
.SetClaim(Claims.Private.CodeChallenge, GetProperty(properties, Properties.CodeChallenge)) |
|||
.SetClaim(Claims.Private.CodeChallengeMethod, GetProperty(properties, Properties.CodeChallengeMethod)) |
|||
.SetClaim(Claims.Private.IdentityTokenLifetime, GetProperty(properties, Properties.IdentityTokenLifetime)) |
|||
.SetClaim(Claims.Private.Nonce, GetProperty(properties, Properties.Nonce)) |
|||
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri)) |
|||
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime)) |
|||
.SetClaim(Claims.Private.TokenId, GetProperty(properties, Properties.InternalTokenId)); |
|||
|
|||
static (ClaimsPrincipal principal, ImmutableDictionary<string, string> properties) Read(BinaryReader reader, int version) |
|||
{ |
|||
if (version != reader.ReadInt32()) |
|||
{ |
|||
return (null, ImmutableDictionary.Create<string, string>()); |
|||
} |
|||
|
|||
// Read the authentication scheme associated to the ticket.
|
|||
_ = reader.ReadString(); |
|||
|
|||
// Read the number of identities stored in the serialized payload.
|
|||
var count = reader.ReadInt32(); |
|||
if (count < 0) |
|||
{ |
|||
return (null, ImmutableDictionary.Create<string, string>()); |
|||
} |
|||
|
|||
var identities = new ClaimsIdentity[count]; |
|||
for (var index = 0; index != count; ++index) |
|||
{ |
|||
identities[index] = ReadIdentity(reader); |
|||
} |
|||
|
|||
var properties = ReadProperties(reader, version); |
|||
|
|||
return (new ClaimsPrincipal(identities), properties); |
|||
} |
|||
|
|||
static ClaimsIdentity ReadIdentity(BinaryReader reader) |
|||
{ |
|||
var identity = new ClaimsIdentity( |
|||
authenticationType: reader.ReadString(), |
|||
nameType: ReadWithDefault(reader, ClaimsIdentity.DefaultNameClaimType), |
|||
roleType: ReadWithDefault(reader, ClaimsIdentity.DefaultRoleClaimType)); |
|||
|
|||
// Read the number of claims contained in the serialized identity.
|
|||
var count = reader.ReadInt32(); |
|||
|
|||
for (int index = 0; index != count; ++index) |
|||
{ |
|||
var claim = ReadClaim(reader, identity); |
|||
|
|||
identity.AddClaim(claim); |
|||
} |
|||
|
|||
// Determine whether the identity has a bootstrap context attached.
|
|||
if (reader.ReadBoolean()) |
|||
{ |
|||
identity.BootstrapContext = reader.ReadString(); |
|||
} |
|||
|
|||
// Determine whether the identity has an actor identity attached.
|
|||
if (reader.ReadBoolean()) |
|||
{ |
|||
identity.Actor = ReadIdentity(reader); |
|||
} |
|||
|
|||
return identity; |
|||
} |
|||
|
|||
static Claim ReadClaim(BinaryReader reader, ClaimsIdentity identity) |
|||
{ |
|||
var type = ReadWithDefault(reader, identity.NameClaimType); |
|||
var value = reader.ReadString(); |
|||
var valueType = ReadWithDefault(reader, ClaimValueTypes.String); |
|||
var issuer = ReadWithDefault(reader, ClaimsIdentity.DefaultIssuer); |
|||
var originalIssuer = ReadWithDefault(reader, issuer); |
|||
|
|||
var claim = new Claim(type, value, valueType, issuer, originalIssuer, identity); |
|||
|
|||
// Read the number of properties stored in the claim.
|
|||
var count = reader.ReadInt32(); |
|||
|
|||
for (var index = 0; index != count; ++index) |
|||
{ |
|||
var key = reader.ReadString(); |
|||
var propertyValue = reader.ReadString(); |
|||
|
|||
claim.Properties.Add(key, propertyValue); |
|||
} |
|||
|
|||
return claim; |
|||
} |
|||
|
|||
static ImmutableDictionary<string, string> ReadProperties(BinaryReader reader, int version) |
|||
{ |
|||
if (version != reader.ReadInt32()) |
|||
{ |
|||
return ImmutableDictionary.Create<string, string>(); |
|||
} |
|||
|
|||
var properties = ImmutableDictionary.CreateBuilder<string, string>(StringComparer.Ordinal); |
|||
var count = reader.ReadInt32(); |
|||
for (var index = 0; index != count; ++index) |
|||
{ |
|||
properties.Add(reader.ReadString(), reader.ReadString()); |
|||
} |
|||
|
|||
return properties.ToImmutable(); |
|||
} |
|||
|
|||
static string ReadWithDefault(BinaryReader reader, string defaultValue) |
|||
{ |
|||
var value = reader.ReadString(); |
|||
|
|||
if (string.Equals(value, "\0", StringComparison.Ordinal)) |
|||
{ |
|||
return defaultValue; |
|||
} |
|||
|
|||
return value; |
|||
} |
|||
|
|||
static string GetProperty(IReadOnlyDictionary<string, string> properties, string name) |
|||
=> properties.TryGetValue(name, out var value) ? value : null; |
|||
|
|||
static IEnumerable<string> GetArrayProperty(IReadOnlyDictionary<string, string> properties, string name) |
|||
=> properties.TryGetValue(name, out var value) ? JArray.Parse(value).Values<string>() : Enumerable.Empty<string>(); |
|||
|
|||
static DateTimeOffset? GetDateProperty(IReadOnlyDictionary<string, string> properties, string name) |
|||
=> properties.TryGetValue(name, out var value) ? (DateTimeOffset?) |
|||
DateTimeOffset.ParseExact(value, "r", CultureInfo.InvariantCulture) : null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,232 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.IO; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.AspNetCore.DataProtection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.DataProtection.OpenIddictValidationDataProtectionConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlers; |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static partial class OpenIddictValidationDataProtectionHandlers |
|||
{ |
|||
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Authentication processing: |
|||
*/ |
|||
ValidateReferenceDataProtectionToken.Descriptor, |
|||
ValidateSelfContainedDataProtectionToken.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication
|
|||
/// demands that use an invalid reference Data Protection token.
|
|||
/// Note: this handler is not used when the degraded mode is enabled.
|
|||
/// </summary>
|
|||
public class ValidateReferenceDataProtectionToken : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IOpenIddictTokenManager _tokenManager; |
|||
private readonly IOptionsMonitor<OpenIddictValidationDataProtectionOptions> _options; |
|||
|
|||
public ValidateReferenceDataProtectionToken() => throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The core services must be registered when enabling the OpenIddict server feature.") |
|||
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") |
|||
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") |
|||
.Append("Alternatively, you can disable the built-in database-based server features by enabling ") |
|||
.Append("the degraded mode with 'services.AddOpenIddict().AddValidation().EnableDegradedMode()'.") |
|||
.ToString()); |
|||
|
|||
public ValidateReferenceDataProtectionToken( |
|||
[NotNull] IOpenIddictTokenManager tokenManager, |
|||
[NotNull] IOptionsMonitor<OpenIddictValidationDataProtectionOptions> options) |
|||
{ |
|||
_tokenManager = tokenManager; |
|||
_options = options; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.AddFilter<RequireReferenceTokensEnabled>() |
|||
.UseScopedHandler<ValidateReferenceDataProtectionToken>() |
|||
.SetOrder(ValidateReferenceToken.Descriptor.Order + 500) |
|||
.Build(); |
|||
|
|||
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If a principal was already attached, don't overwrite it.
|
|||
if (context.Principal != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
var identifier = context.Request.AccessToken; |
|||
if (string.IsNullOrEmpty(identifier)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var token = await _tokenManager.FindByReferenceIdAsync(identifier); |
|||
if (token == null || !string.Equals(await _tokenManager.GetTypeAsync(token), |
|||
TokenUsages.AccessToken, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var payload = await _tokenManager.GetPayloadAsync(token); |
|||
if (string.IsNullOrEmpty(payload)) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The payload associated with a reference token cannot be retrieved.") |
|||
.Append("This may indicate that the token entry was corrupted.") |
|||
.ToString()); |
|||
} |
|||
|
|||
// Create a Data Protection protector using the provider registered in the options.
|
|||
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( |
|||
Purposes.Handlers.Server, |
|||
Purposes.Formats.AccessToken, |
|||
Purposes.Features.ReferenceTokens, |
|||
Purposes.Schemes.Server); |
|||
|
|||
ClaimsPrincipal principal = null; |
|||
|
|||
try |
|||
{ |
|||
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(payload))); |
|||
using var reader = new BinaryReader(buffer); |
|||
|
|||
principal = _options.CurrentValue.Formatter.ReadToken(reader); |
|||
} |
|||
|
|||
catch (Exception exception) |
|||
{ |
|||
context.Logger.LogTrace(exception, "An exception occured while deserializing a token."); |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
if (principal == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Attach the principal extracted from the authorization code to the parent event context
|
|||
// and restore the creation/expiration dates/identifiers from the token entry metadata.
|
|||
context.Principal = principal |
|||
.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) |
|||
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) |
|||
.SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) |
|||
.SetInternalTokenId(await _tokenManager.GetIdAsync(token)) |
|||
.SetClaim(Claims.Private.TokenUsage, await _tokenManager.GetTypeAsync(token)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands
|
|||
/// that specify an invalid self-contained Data Protection token.
|
|||
/// </summary>
|
|||
public class ValidateSelfContainedDataProtectionToken : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IOptionsMonitor<OpenIddictValidationDataProtectionOptions> _options; |
|||
|
|||
public ValidateSelfContainedDataProtectionToken([NotNull] IOptionsMonitor<OpenIddictValidationDataProtectionOptions> options) |
|||
=> _options = options; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.AddFilter<RequireReferenceTokensDisabled>() |
|||
.UseSingletonHandler<ValidateSelfContainedDataProtectionToken>() |
|||
.SetOrder(ValidateSelfContainedToken.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If a principal was already attached, don't overwrite it.
|
|||
if (context.Principal != null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
var token = context.Request.AccessToken; |
|||
if (string.IsNullOrEmpty(token)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Create a Data Protection protector using the provider registered in the options.
|
|||
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector( |
|||
Purposes.Handlers.Server, |
|||
Purposes.Formats.AccessToken, |
|||
Purposes.Schemes.Server); |
|||
|
|||
ClaimsPrincipal principal = null; |
|||
|
|||
try |
|||
{ |
|||
using var buffer = new MemoryStream(protector.Unprotect(Base64UrlEncoder.DecodeBytes(token))); |
|||
using var reader = new BinaryReader(buffer); |
|||
|
|||
principal = _options.CurrentValue.Formatter.ReadToken(reader); |
|||
} |
|||
|
|||
catch (Exception exception) |
|||
{ |
|||
context.Logger.LogTrace(exception, "An exception occured while deserializing a token."); |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
if (principal == null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Note: since the data format relies on a data protector using different "purposes" strings
|
|||
// per token type, the token processed at this stage is guaranteed to be of the expected type.
|
|||
context.Principal = principal.SetClaim(Claims.Private.TokenUsage, TokenUsages.AccessToken); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
/* |
|||
* 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 Microsoft.AspNetCore.DataProtection; |
|||
|
|||
namespace OpenIddict.Validation.DataProtection |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict validation handler.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationDataProtectionOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the data protection provider used to create the default
|
|||
/// data protectors used by the OpenIddict Data Protection validation services.
|
|||
/// When this property is set to <c>null</c>, the data protection provider
|
|||
/// is directly retrieved from the dependency injection container.
|
|||
/// </summary>
|
|||
public IDataProtectionProvider DataProtectionProvider { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the formatter used to read and write Data Protection tokens,
|
|||
/// serialized using the same format as the ASP.NET Core authentication tickets.
|
|||
/// </summary>
|
|||
public IOpenIddictValidationDataProtectionFormatter Formatter { get; set; } |
|||
= new OpenIddictValidationDataProtectionFormatter(); |
|||
} |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>net461;net472</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>OWIN/ASP.NET 4.x integration package for the OpenIddict validation services.</Description> |
|||
<PackageTags>$(PackageTags);validation;aspnet;katana;owin</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="Microsoft.Owin.Security" Version="$(OwinVersion)" /> |
|||
<PackageReference Include="Newtonsoft.Json.Bson" Version="$(JsonNetBsonVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,73 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using JetBrains.Annotations; |
|||
using OpenIddict.Validation.Owin; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure
|
|||
/// the OpenIddict validation OWIN/Katana integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationOwinBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationOwinBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation OWIN/Katana configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationOwinBuilder"/>.</returns>
|
|||
public OpenIddictValidationOwinBuilder Configure([NotNull] Action<OpenIddictValidationOwinOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/* |
|||
* 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.Diagnostics; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict validation configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinConfiguration : IConfigureNamedOptions<OpenIddictValidationOptions> |
|||
{ |
|||
public void Configure([NotNull] OpenIddictValidationOptions options) |
|||
=> Debug.Fail("This infrastructure method shouldn't be called"); |
|||
|
|||
public void Configure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict OWIN validation components.
|
|||
foreach (var handler in OpenIddictValidationOwinHandlers.DefaultHandlers) |
|||
{ |
|||
options.DefaultHandlers.Add(handler); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes common constants used by the OpenIddict OWIN host.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationOwinConstants |
|||
{ |
|||
public static class Cache |
|||
{ |
|||
public const string AuthorizationRequest = "openiddict-authorization-request:"; |
|||
public const string LogoutRequest = "openiddict-logout-request:"; |
|||
} |
|||
|
|||
public static class Properties |
|||
{ |
|||
public const string Error = ".error"; |
|||
public const string ErrorDescription = ".error_description"; |
|||
public const string ErrorUri = ".error_uri"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
/* |
|||
* 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 Microsoft.Owin.Security; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the default values used by the OpenIddict validation handler.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationOwinDefaults |
|||
{ |
|||
/// <summary>
|
|||
/// Default value for <see cref="AuthenticationOptions.AuthenticationType"/>.
|
|||
/// </summary>
|
|||
public const string AuthenticationType = "OpenIddict.Validation.Owin"; |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.Owin; |
|||
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlerFilters; |
|||
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlers; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationOwinExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation services for OWIN in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationOwinBuilder"/>.</returns>
|
|||
public static OpenIddictValidationOwinBuilder UseOwin([NotNull] this OpenIddictValidationBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
builder.Services.AddWebEncoders(); |
|||
|
|||
// Note: unlike regular OWIN middleware, the OpenIddict validation middleware is registered
|
|||
// as a scoped service in the DI container. This allows containers that support middleware
|
|||
// resolution (like Autofac) to use it without requiring additional configuration.
|
|||
builder.Services.TryAddScoped<OpenIddictValidationOwinMiddleware>(); |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict OWIN validation components.
|
|||
// Note: the order used here is not important, as the actual order is set in the options.
|
|||
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
|||
|
|||
// Register the built-in filters used by the default OpenIddict OWIN validation event handlers.
|
|||
builder.Services.TryAddSingleton<RequireOwinRequest>(); |
|||
|
|||
// Register the option initializers used by the OpenIddict OWIN validation integration services.
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializers are only registered once.
|
|||
builder.Services.TryAddEnumerable(new[] |
|||
{ |
|||
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationOwinConfiguration>() |
|||
}); |
|||
|
|||
return new OpenIddictValidationOwinBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation services for OWIN in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder UseOwin( |
|||
[NotNull] this OpenIddictValidationBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationOwinBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.UseOwin()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,214 @@ |
|||
/* |
|||
* 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.Security.Claims; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Owin; |
|||
using Microsoft.Owin.Security; |
|||
using Microsoft.Owin.Security.Infrastructure; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using Properties = OpenIddict.Validation.Owin.OpenIddictValidationOwinConstants.Properties; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the entry point necessary to register the OpenIddict validation in an OWIN pipeline.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinHandler : AuthenticationHandler<OpenIddictValidationOwinOptions> |
|||
{ |
|||
private readonly ILogger _logger; |
|||
private readonly IOpenIddictValidationProvider _provider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationOwinHandler"/> class.
|
|||
/// </summary>
|
|||
/// <param name="logger">The logger used by this instance.</param>
|
|||
/// <param name="provider">The OpenIddict validation OWIN provider used by this instance.</param>
|
|||
public OpenIddictValidationOwinHandler( |
|||
[NotNull] ILogger logger, |
|||
[NotNull] IOpenIddictValidationProvider provider) |
|||
{ |
|||
_logger = logger; |
|||
_provider = provider; |
|||
} |
|||
|
|||
public override async Task<bool> InvokeAsync() |
|||
{ |
|||
// Note: the transaction may be already attached when replaying an OWIN request
|
|||
// (e.g when using a status code pages middleware re-invoking the OWIN pipeline).
|
|||
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName); |
|||
if (transaction == null) |
|||
{ |
|||
// Create a new transaction and attach the OWIN request to make it available to the OWIN handlers.
|
|||
transaction = await _provider.CreateTransactionAsync(); |
|||
transaction.Properties[typeof(IOwinRequest).FullName] = new WeakReference<IOwinRequest>(Request); |
|||
|
|||
// Attach the OpenIddict validation transaction to the OWIN shared dictionary
|
|||
// so that it can retrieved while performing sign-in/sign-out operations.
|
|||
Context.Set(typeof(OpenIddictValidationTransaction).FullName, transaction); |
|||
} |
|||
|
|||
var context = new ProcessRequestContext(transaction); |
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.IsRequestHandled) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
else if (context.IsRequestSkipped) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
var notification = new ProcessErrorContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = context.Error ?? Errors.InvalidRequest, |
|||
ErrorDescription = context.ErrorDescription, |
|||
ErrorUri = context.ErrorUri |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
else if (notification.IsRequestSkipped) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The OpenID Connect response was not correctly processed. This may indicate ") |
|||
.Append("that the event handler responsible of processing OpenID Connect responses ") |
|||
.Append("was not registered or was explicitly removed from the handlers list.") |
|||
.ToString()); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
protected override async Task<AuthenticationTicket> AuthenticateCoreAsync() |
|||
{ |
|||
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName); |
|||
if (transaction?.Request == null) |
|||
{ |
|||
throw new InvalidOperationException("An identity cannot be extracted from this request."); |
|||
} |
|||
|
|||
var context = new ProcessAuthenticationContext(transaction); |
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.Principal == null || context.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
_logger.LogError("An error occurred while authenticating the current request: {Error} ; {Description}", |
|||
/* Error: */ context.Error ?? Errors.InvalidToken, |
|||
/* Description: */ context.ErrorDescription); |
|||
|
|||
return new AuthenticationTicket(identity: null, new AuthenticationProperties |
|||
{ |
|||
Dictionary = |
|||
{ |
|||
[Parameters.Error] = context.Error, |
|||
[Parameters.ErrorDescription] = context.ErrorDescription, |
|||
[Parameters.ErrorUri] = context.ErrorUri |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return new AuthenticationTicket((ClaimsIdentity) context.Principal.Identity, new AuthenticationProperties()); |
|||
} |
|||
|
|||
protected override async Task TeardownCoreAsync() |
|||
{ |
|||
// Note: OWIN authentication handlers cannot reliabily write to the response stream
|
|||
// from ApplyResponseGrantAsync or ApplyResponseChallengeAsync because these methods
|
|||
// are susceptible to be invoked from AuthenticationHandler.OnSendingHeaderCallback,
|
|||
// where calling Write or WriteAsync on the response stream may result in a deadlock
|
|||
// on hosts using streamed responses. To work around this limitation, this handler
|
|||
// doesn't implement ApplyResponseGrantAsync but TeardownCoreAsync, which is never called
|
|||
// by AuthenticationHandler.OnSendingHeaderCallback. In theory, this would prevent
|
|||
// OpenIddictValidationOwinMiddleware from both applying the response grant and allowing
|
|||
// the next middleware in the pipeline to alter the response stream but in practice,
|
|||
// OpenIddictValidationOwinMiddleware is assumed to be the only middleware allowed to write
|
|||
// to the response stream when a response grant (sign-in/out or challenge) was applied.
|
|||
|
|||
var challenge = Helper.LookupChallenge(Options.AuthenticationType, Options.AuthenticationMode); |
|||
if (challenge != null) |
|||
{ |
|||
var transaction = Context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName); |
|||
if (transaction == null) |
|||
{ |
|||
throw new InvalidOperationException("An OpenID Connect response cannot be returned from this endpoint."); |
|||
} |
|||
|
|||
var context = new ProcessChallengeContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = GetProperty(challenge.Properties, Properties.Error), |
|||
ErrorDescription = GetProperty(challenge.Properties, Properties.ErrorDescription), |
|||
ErrorUri = GetProperty(challenge.Properties, Properties.ErrorUri) |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(context); |
|||
|
|||
if (context.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
else if (context.IsRejected) |
|||
{ |
|||
var notification = new ProcessErrorContext(transaction) |
|||
{ |
|||
Response = new OpenIddictResponse |
|||
{ |
|||
Error = context.Error ?? Errors.InvalidRequest, |
|||
ErrorDescription = context.ErrorDescription, |
|||
ErrorUri = context.ErrorUri |
|||
} |
|||
}; |
|||
|
|||
await _provider.DispatchAsync(notification); |
|||
|
|||
if (notification.IsRequestHandled || context.IsRequestSkipped) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("The OpenID Connect response was not correctly processed. This may indicate ") |
|||
.Append("that the event handler responsible of processing OpenID Connect responses ") |
|||
.Append("was not registered or was explicitly removed from the handlers list.") |
|||
.ToString()); |
|||
} |
|||
|
|||
static string GetProperty(AuthenticationProperties properties, string name) |
|||
=> properties != null && properties.Dictionary.TryGetValue(name, out string value) ? value : null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
/* |
|||
* 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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Owin; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Contains a collection of event handler filters commonly used by the OWIN handlers.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationOwinHandlerFilters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if no OWIN request can be found.
|
|||
/// </summary>
|
|||
public class RequireOwinRequest : IOpenIddictValidationHandlerFilter<BaseContext> |
|||
{ |
|||
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new ValueTask<bool>(context.Transaction.GetOwinRequest() != null); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,302 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.IO; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Newtonsoft.Json; |
|||
using OpenIddict.Abstractions; |
|||
using Owin; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using static OpenIddict.Validation.Owin.OpenIddictValidationOwinHandlerFilters; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static partial class OpenIddictValidationOwinHandlers |
|||
{ |
|||
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Request top-level processing: |
|||
*/ |
|||
InferIssuerFromHost.Descriptor, |
|||
ExtractGetOrPostRequest.Descriptor, |
|||
ExtractAccessToken.Descriptor, |
|||
|
|||
/* |
|||
* Response processing: |
|||
*/ |
|||
ProcessJsonResponse<ProcessChallengeContext>.Descriptor, |
|||
ProcessJsonResponse<ProcessErrorContext>.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of infering the default issuer from the HTTP request host and validating it.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class InferIssuerFromHost : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireOwinRequest>() |
|||
.UseSingletonHandler<InferIssuerFromHost>() |
|||
.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] ProcessRequestContext 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."); |
|||
} |
|||
|
|||
// Only use the current host as the issuer if the
|
|||
// issuer was not explicitly set in the options.
|
|||
if (context.Issuer != null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(request.Host.Value)) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidRequest, |
|||
description: "The mandatory 'Host' header is missing."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer) || |
|||
!issuer.IsWellFormedOriginalString()) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidRequest, |
|||
description: "The specified 'Host' header is invalid."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
context.Issuer = issuer; |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of extracting OpenID Connect requests from GET or POST HTTP requests.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class ExtractGetOrPostRequest : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireOwinRequest>() |
|||
.UseSingletonHandler<ExtractGetOrPostRequest>() |
|||
.SetOrder(InferIssuerFromHost.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)); |
|||
} |
|||
|
|||
// 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 (string.Equals(request.Method, "GET", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
context.Request = new OpenIddictRequest(request.Query); |
|||
} |
|||
|
|||
else if (string.Equals(request.Method, "POST", StringComparison.OrdinalIgnoreCase) && |
|||
!string.IsNullOrEmpty(request.ContentType) && |
|||
request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
context.Request = new OpenIddictRequest(await request.ReadFormAsync()); |
|||
} |
|||
|
|||
else |
|||
{ |
|||
context.Request = new OpenIddictRequest(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of extracting an access token from the standard HTTP Authorization header.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class ExtractAccessToken : IOpenIddictValidationHandler<ProcessRequestContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessRequestContext>() |
|||
.AddFilter<RequireOwinRequest>() |
|||
.UseSingletonHandler<ExtractAccessToken>() |
|||
.SetOrder(ExtractGetOrPostRequest.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] ProcessRequestContext 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."); |
|||
} |
|||
|
|||
string header = request.Headers["Authorization"]; |
|||
if (string.IsNullOrEmpty(header) || !header.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Attach the access token to the request message.
|
|||
context.Request.AccessToken = header.Substring("Bearer ".Length); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of processing OpenID Connect responses that must be returned as JSON.
|
|||
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
|
|||
/// </summary>
|
|||
public class ProcessJsonResponse<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseRequestContext |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>() |
|||
.AddFilter<RequireOwinRequest>() |
|||
.UseSingletonHandler<ProcessJsonResponse<TContext>>() |
|||
.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] TContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.Response == null) |
|||
{ |
|||
throw new InvalidOperationException("This handler cannot be invoked without a response attached."); |
|||
} |
|||
|
|||
// 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.Logger.LogInformation("The response was successfully returned as a JSON document: {Response}.", context.Response); |
|||
|
|||
using (var buffer = new MemoryStream()) |
|||
using (var writer = new JsonTextWriter(new StreamWriter(buffer))) |
|||
{ |
|||
var serializer = JsonSerializer.CreateDefault(); |
|||
serializer.Serialize(writer, context.Response); |
|||
|
|||
writer.Flush(); |
|||
|
|||
if (!string.IsNullOrEmpty(context.Response.Error)) |
|||
{ |
|||
if (context.Issuer == null) |
|||
{ |
|||
throw new InvalidOperationException("The issuer address cannot be inferred from the current request."); |
|||
} |
|||
|
|||
request.Context.Response.StatusCode = 401; |
|||
|
|||
request.Context.Response.Headers["WWW-Authenticate"] = new StringBuilder() |
|||
.Append(Schemes.Bearer) |
|||
.Append(' ') |
|||
.Append(Parameters.Realm) |
|||
.Append("=\"") |
|||
.Append(context.Issuer.AbsoluteUri) |
|||
.Append('"') |
|||
.ToString(); |
|||
} |
|||
|
|||
request.Context.Response.ContentLength = buffer.Length; |
|||
request.Context.Response.ContentType = "application/json;charset=UTF-8"; |
|||
|
|||
buffer.Seek(offset: 0, loc: SeekOrigin.Begin); |
|||
await buffer.CopyToAsync(request.Context.Response.Body, 4096, request.CallCancelled); |
|||
} |
|||
|
|||
context.HandleRequest(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
/* |
|||
* 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 JetBrains.Annotations; |
|||
using Microsoft.Owin; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.Owin; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes companion extensions for the OpenIddict/OWIN integration.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationOwinHelpers |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation OWIN middleware in the application pipeline.
|
|||
/// Note: when using a dependency injection container supporting per-request
|
|||
/// middleware resolution (like Autofac), calling this method is NOT recommended.
|
|||
/// </summary>
|
|||
/// <param name="app">The application builder used to register middleware instances.</param>
|
|||
/// <returns>The <see cref="IAppBuilder"/>.</returns>
|
|||
public static IAppBuilder UseOpenIddictValidation([NotNull] this IAppBuilder app) |
|||
{ |
|||
if (app == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(app)); |
|||
} |
|||
|
|||
return app.Use<OpenIddictValidationOwinMiddlewareFactory>(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="IOwinRequest"/> instance stored in the <see cref="OpenIddictValidationTransaction"/> properties.
|
|||
/// </summary>
|
|||
/// <param name="transaction">The transaction instance.</param>
|
|||
/// <returns>The <see cref="IOwinRequest"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static IOwinRequest GetOwinRequest([NotNull] this OpenIddictValidationTransaction transaction) |
|||
{ |
|||
if (transaction == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(transaction)); |
|||
} |
|||
|
|||
if (!transaction.Properties.TryGetValue(typeof(IOwinRequest).FullName, out object property)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
if (property is WeakReference<IOwinRequest> reference && reference.TryGetTarget(out IOwinRequest request)) |
|||
{ |
|||
return request; |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictValidationEndpointType"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationEndpointType"/>.</returns>
|
|||
public static OpenIddictValidationEndpointType GetOpenIddictValidationEndpointType([NotNull] this IOwinContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName)?.EndpointType ?? default; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictRequest"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictRequest"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static OpenIddictRequest GetOpenIddictValidationRequest([NotNull] this IOwinContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName)?.Request; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Retrieves the <see cref="OpenIddictResponse"/> instance stored in <see cref="BaseContext"/>.
|
|||
/// </summary>
|
|||
/// <param name="context">The context instance.</param>
|
|||
/// <returns>The <see cref="OpenIddictResponse"/> instance or <c>null</c> if it couldn't be found.</returns>
|
|||
public static OpenIddictResponse GetOpenIddictValidationResponse([NotNull] this IOwinContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return context.Get<OpenIddictValidationTransaction>(typeof(OpenIddictValidationTransaction).FullName)?.Response; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
/* |
|||
* 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 JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.Owin; |
|||
using Microsoft.Owin.Security.Infrastructure; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the entry point necessary to register the OpenIddict validation in an OWIN pipeline.
|
|||
/// Note: this middleware is intented to be used with dependency injection containers
|
|||
/// that support middleware resolution, like Autofac. Since it depends on scoped services,
|
|||
/// it is NOT recommended to instantiate it as a singleton like a regular OWIN middleware.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinMiddleware : AuthenticationMiddleware<OpenIddictValidationOwinOptions> |
|||
{ |
|||
private readonly ILogger<OpenIddictValidationOwinMiddleware> _logger; |
|||
private readonly IOpenIddictValidationProvider _provider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationOwinMiddleware"/> class.
|
|||
/// </summary>
|
|||
/// <param name="next">The next middleware in the pipeline, if applicable.</param>
|
|||
/// <param name="logger">The logger used by this middleware.</param>
|
|||
/// <param name="options">The OpenIddict validation OWIN options.</param>
|
|||
/// <param name="provider">The OpenIddict validation provider.</param>
|
|||
public OpenIddictValidationOwinMiddleware( |
|||
[CanBeNull] OwinMiddleware next, |
|||
[NotNull] ILogger<OpenIddictValidationOwinMiddleware> logger, |
|||
[NotNull] IOptionsMonitor<OpenIddictValidationOwinOptions> options, |
|||
[NotNull] IOpenIddictValidationProvider provider) |
|||
: base(next, options.CurrentValue) |
|||
{ |
|||
_logger = logger; |
|||
_provider = provider; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates and returns a new <see cref="OpenIddictValidationOwinHandler"/> instance.
|
|||
/// </summary>
|
|||
/// <returns>A new instance of the <see cref="OpenIddictValidationOwinHandler"/> class.</returns>
|
|||
protected override AuthenticationHandler<OpenIddictValidationOwinOptions> CreateHandler() |
|||
=> new OpenIddictValidationOwinHandler(_logger, _provider); |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
/* |
|||
* 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.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.Owin; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Provides the entry point necessary to instantiate and register the scoped
|
|||
/// <see cref="OpenIddictValidationOwinMiddleware"/> in an OWIN/Katana pipeline.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinMiddlewareFactory : OwinMiddleware |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationOwinMiddlewareFactory"/> class.
|
|||
/// </summary>
|
|||
/// <param name="next">The next middleware in the pipeline, if applicable.</param>
|
|||
public OpenIddictValidationOwinMiddlewareFactory([CanBeNull] OwinMiddleware next) |
|||
: base(next) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Resolves the <see cref="IServiceProvider"/> instance from the OWIN context
|
|||
/// and creates a new instance of the <see cref="OpenIddictValidationOwinMiddleware"/> class,
|
|||
/// which is used to register <see cref="OpenIddictValidationOwinHandler"/> in the pipeline.
|
|||
/// </summary>
|
|||
/// <param name="context">The <see cref="IOwinContext"/>.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public override Task Invoke([NotNull] IOwinContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
var provider = context.Get<IServiceProvider>(typeof(IServiceProvider).FullName); |
|||
if (provider == null) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.Append("No service provider was found in the OWIN context. For the OpenIddict validation ") |
|||
.Append("services to work correctly, a per-request 'IServiceProvider' must be attached ") |
|||
.AppendLine("to the OWIN environment with the dictionary key 'System.IServiceProvider'.") |
|||
.Append("Note: when using a dependency injection container supporting middleware resolution ") |
|||
.Append("(like Autofac), the 'app.UseOpenIddictValidation()' extension MUST NOT be called.") |
|||
.ToString()); |
|||
} |
|||
|
|||
// Note: the Microsoft.Extensions.DependencyInjection container doesn't support resolving services
|
|||
// with arbitrary parameters, which prevents the validation OWIN middleware from being resolved directly
|
|||
// from the DI container, as the next middleware in the pipeline cannot be specified as a parameter.
|
|||
// To work around this limitation, the validation OWIN middleware is manually instantiated and invoked.
|
|||
var middleware = new OpenIddictValidationOwinMiddleware( |
|||
next: Next, |
|||
logger: GetRequiredService<ILogger<OpenIddictValidationOwinMiddleware>>(provider), |
|||
options: GetRequiredService<IOptionsMonitor<OpenIddictValidationOwinOptions>>(provider), |
|||
provider: GetRequiredService<IOpenIddictValidationProvider>(provider)); |
|||
|
|||
return middleware.Invoke(context); |
|||
|
|||
static T GetRequiredService<T>(IServiceProvider provider) |
|||
=> provider.GetService<T>() ?? throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The OpenIddict validation authentication services cannot be resolved from the DI container.") |
|||
.Append("To register the OWIN services, use 'services.AddOpenIddict().AddValidation().UseOwin()'.") |
|||
.ToString()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
/* |
|||
* 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 Microsoft.Owin.Security; |
|||
|
|||
namespace OpenIddict.Validation.Owin |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict OWIN validation integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOwinOptions : AuthenticationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationOwinOptions"/> class.
|
|||
/// </summary>
|
|||
public OpenIddictValidationOwinOptions() |
|||
: base(OpenIddictValidationOwinDefaults.AuthenticationType) |
|||
=> AuthenticationMode = AuthenticationMode.Passive; |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>OpenID Connect validation/server integration for OpenIddict.</Description> |
|||
<PackageTags>$(PackageTags);server;validation</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Server\OpenIddict.Server.csproj" /> |
|||
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,72 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using JetBrains.Annotations; |
|||
using OpenIddict.Validation.ServerIntegration; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationServerIntegrationBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationServerIntegrationBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation/server integration configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationServerIntegrationBuilder"/>.</returns>
|
|||
public OpenIddictValidationServerIntegrationBuilder Configure([NotNull] Action<OpenIddictValidationServerIntegrationOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Server; |
|||
|
|||
namespace OpenIddict.Validation.ServerIntegration |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict validation/server integration configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationServerIntegrationConfiguration : IConfigureOptions<OpenIddictValidationOptions> |
|||
{ |
|||
private readonly IOptionsMonitor<OpenIddictServerOptions> _options; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationServerIntegrationConfiguration"/> class.
|
|||
/// </summary>
|
|||
/// <param name="options">The OpenIddict server options.</param>
|
|||
public OpenIddictValidationServerIntegrationConfiguration([NotNull] IOptionsMonitor<OpenIddictServerOptions> options) |
|||
=> _options = options; |
|||
|
|||
/// <summary>
|
|||
/// Populates the default OpenIddict validation/server integration options
|
|||
/// and ensures that the configuration is in a consistent and valid state.
|
|||
/// </summary>
|
|||
/// <param name="options">The options instance to initialize.</param>
|
|||
public void Configure([NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Note: the issuer may be null. In this case, it will be usually be provided by
|
|||
// a validation handler registered by the host (e.g ASP.NET Core or OWIN/Katana)
|
|||
options.Issuer = _options.CurrentValue.Issuer; |
|||
|
|||
// Import the token validation parameters from the server configuration.
|
|||
options.TokenValidationParameters = new TokenValidationParameters |
|||
{ |
|||
IssuerSigningKeys = (from credentials in _options.CurrentValue.SigningCredentials |
|||
select credentials.Key).ToList() |
|||
}; |
|||
|
|||
// Import the symmetric encryption keys from the server configuration.
|
|||
foreach (var credentials in _options.CurrentValue.EncryptionCredentials) |
|||
{ |
|||
if (credentials.Key is SymmetricSecurityKey) |
|||
{ |
|||
options.EncryptionCredentials.Add(credentials); |
|||
} |
|||
} |
|||
|
|||
options.UseReferenceTokens = _options.CurrentValue.UseReferenceTokens; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,69 @@ |
|||
/* |
|||
* 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 JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.ServerIntegration; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict validation/server integration services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationServerIntegrationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation/server integration services in the DI container
|
|||
/// and automatically imports the configuration from the local OpenIddict server.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationServerIntegrationBuilder UseLocalServer([NotNull] this OpenIddictValidationBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
|
|||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< |
|||
IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationServerIntegrationConfiguration>()); |
|||
|
|||
return new OpenIddictValidationServerIntegrationBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation/server integration services in the DI container
|
|||
/// and automatically imports the configuration from the local OpenIddict server.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder UseLocalServer( |
|||
[NotNull] this OpenIddictValidationBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationServerIntegrationBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.UseLocalServer()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,15 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.ServerIntegration |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict validation/server integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationServerIntegrationOptions |
|||
{ |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>OpenID Connect validation/System.Net.Http integration for OpenIddict.</Description> |
|||
<PackageTags>$(PackageTags);http;httpclient;validation</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Validation\OpenIddict.Validation.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
<PackageReference Include="System.Linq.Async" Version="$(LinqAsyncVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,82 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.Net.Http; |
|||
using JetBrains.Annotations; |
|||
using OpenIddict.Validation.SystemNetHttp; |
|||
using Polly; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure the OpenIddict validation/System.Net.Http integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationSystemNetHttpBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationSystemNetHttpBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation/server integration configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationSystemNetHttpBuilder"/>.</returns>
|
|||
public OpenIddictValidationSystemNetHttpBuilder Configure([NotNull] Action<OpenIddictValidationSystemNetHttpOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Replaces the default HTTP error policy used by the OpenIddict validation services.
|
|||
/// </summary>
|
|||
/// <param name="policy">The HTTP Polly error policy.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationSystemNetHttpBuilder"/>.</returns>
|
|||
public OpenIddictValidationSystemNetHttpBuilder SetHttpErrorPolicy([CanBeNull] IAsyncPolicy<HttpResponseMessage> policy) |
|||
=> Configure(options => options.HttpErrorPolicy = policy); |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,74 @@ |
|||
/* |
|||
* 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.Diagnostics; |
|||
using System.Net.Http.Headers; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Http; |
|||
using Microsoft.Extensions.Options; |
|||
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants; |
|||
|
|||
namespace OpenIddict.Validation.SystemNetHttp |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict validation/System.Net.Http integration configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationSystemNetHttpConfiguration : IConfigureOptions<OpenIddictValidationOptions>, |
|||
IConfigureNamedOptions<HttpClientFactoryOptions> |
|||
{ |
|||
public void Configure([NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
// Register the built-in event handlers used by the OpenIddict System.Net.Http validation components.
|
|||
foreach (var handler in OpenIddictValidationSystemNetHttpHandlers.DefaultHandlers) |
|||
{ |
|||
options.DefaultHandlers.Add(handler); |
|||
} |
|||
} |
|||
|
|||
public void Configure([NotNull] HttpClientFactoryOptions options) |
|||
=> Debug.Fail("This infrastructure method shouldn't be called."); |
|||
|
|||
public void Configure([CanBeNull] string name, [NotNull] HttpClientFactoryOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
if (!string.Equals(name, Clients.Discovery, StringComparison.Ordinal)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
options.HttpClientActions.Add(client => |
|||
{ |
|||
var name = typeof(OpenIddictValidationSystemNetHttpConfiguration).Assembly.GetName(); |
|||
|
|||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue( |
|||
productName: name.Name, |
|||
productVersion: name.Version.ToString())); |
|||
}); |
|||
|
|||
options.HttpMessageHandlerBuilderActions.Add(builder => |
|||
{ |
|||
var options = builder.Services.GetRequiredService<IOptionsMonitor<OpenIddictValidationSystemNetHttpOptions>>(); |
|||
|
|||
var policy = options.CurrentValue.HttpErrorPolicy; |
|||
if (policy != null) |
|||
{ |
|||
builder.AdditionalHandlers.Add(new PolicyHttpMessageHandler(policy)); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation.SystemNetHttp |
|||
{ |
|||
public static class OpenIddictValidationSystemNetHttpConstants |
|||
{ |
|||
public static class Clients |
|||
{ |
|||
public const string Discovery = "OpenIddict.Validation.SystemNetHttp.Discovery"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Http; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using OpenIddict.Validation.SystemNetHttp; |
|||
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpHandlers; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict validation/System.Net.Http integration services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationSystemNetHttpExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation/System.Net.Http integration services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationSystemNetHttpBuilder UseSystemNetHttp([NotNull] this OpenIddictValidationBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
builder.Services.AddHttpClient(); |
|||
builder.Services.AddMemoryCache(); |
|||
|
|||
// Register the built-in validation event handlers used by the OpenIddict System.Net.Http components.
|
|||
// Note: the order used here is not important, as the actual order is set in the options.
|
|||
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
|||
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializers are registered only once.
|
|||
builder.Services.TryAddEnumerable(new[] |
|||
{ |
|||
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationSystemNetHttpConfiguration>(), |
|||
ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, OpenIddictValidationSystemNetHttpConfiguration>() |
|||
}); |
|||
|
|||
return new OpenIddictValidationSystemNetHttpBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict validation/System.Net.Http integration services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder UseSystemNetHttp( |
|||
[NotNull] this OpenIddictValidationBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationSystemNetHttpBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.UseSystemNetHttp()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,300 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.ComponentModel; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Net.Http; |
|||
using System.Net.Http.Headers; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Caching.Memory; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using Newtonsoft.Json; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlers; |
|||
using static OpenIddict.Validation.SystemNetHttp.OpenIddictValidationSystemNetHttpConstants; |
|||
|
|||
namespace OpenIddict.Validation.SystemNetHttp |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static partial class OpenIddictValidationSystemNetHttpHandlers |
|||
{ |
|||
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Authentication processing: |
|||
*/ |
|||
PopulateTokenValidationParametersFromMemoryCache.Descriptor, |
|||
PopulateTokenValidationParametersFromProviderConfiguration.Descriptor, |
|||
CacheTokenValidationParameters.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of populating the token validation parameters from the memory cache.
|
|||
/// </summary>
|
|||
public class PopulateTokenValidationParametersFromMemoryCache : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IMemoryCache _cache; |
|||
|
|||
public PopulateTokenValidationParametersFromMemoryCache([NotNull] IMemoryCache cache) |
|||
=> _cache = cache; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<PopulateTokenValidationParametersFromMemoryCache>() |
|||
.SetOrder(PopulateTokenValidationParametersFromProviderConfiguration.Descriptor.Order - 1_000) |
|||
.Build(); |
|||
|
|||
public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If token validation parameters were already attached, don't overwrite them.
|
|||
if (context.TokenValidationParameters != null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
|
|||
if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && |
|||
!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Resolve the token validation parameters from the memory cache.
|
|||
if (_cache.TryGetValue( |
|||
key: string.Concat("af84c073-c27c-49fd-a54f-584fd60320d3", "\x1e", context.Issuer?.AbsoluteUri), |
|||
value: out TokenValidationParameters parameters)) |
|||
{ |
|||
context.TokenValidationParameters = parameters; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of populating the token validation
|
|||
/// parameters using OAuth 2.0/OpenID Connect discovery.
|
|||
/// </summary>
|
|||
public class PopulateTokenValidationParametersFromProviderConfiguration : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IHttpClientFactory _factory; |
|||
|
|||
public PopulateTokenValidationParametersFromProviderConfiguration([NotNull] IHttpClientFactory factory) |
|||
=> _factory = factory; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<PopulateTokenValidationParametersFromProviderConfiguration>() |
|||
.SetOrder(ValidateTokenValidationParameters.Descriptor.Order - 1_000) |
|||
.Build(); |
|||
|
|||
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If token validation parameters were already attached, don't overwrite them.
|
|||
if (context.TokenValidationParameters != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
|
|||
if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && |
|||
!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
using var client = _factory.CreateClient(Clients.Discovery); |
|||
var response = await SendHttpRequestMessageAsync(context.Options.MetadataAddress); |
|||
|
|||
// Ensure the JWKS endpoint URL is present and valid.
|
|||
if (!response.TryGetParameter(Metadata.JwksUri, out var endpoint) || OpenIddictParameter.IsNullOrEmpty(endpoint)) |
|||
{ |
|||
throw new InvalidOperationException("A discovery response containing an empty JWKS endpoint URL was returned."); |
|||
} |
|||
|
|||
if (!Uri.TryCreate((string) endpoint, UriKind.Absolute, out Uri uri)) |
|||
{ |
|||
throw new InvalidOperationException("A discovery response containing an invalid JWKS endpoint URL was returned."); |
|||
} |
|||
|
|||
context.TokenValidationParameters = new TokenValidationParameters |
|||
{ |
|||
ValidIssuer = (string) response[Metadata.Issuer], |
|||
IssuerSigningKeys = await GetSigningKeysAsync(uri).ToListAsync() |
|||
}; |
|||
|
|||
async IAsyncEnumerable<SecurityKey> GetSigningKeysAsync(Uri address) |
|||
{ |
|||
var response = await SendHttpRequestMessageAsync(address); |
|||
|
|||
var keys = response[JsonWebKeySetParameterNames.Keys]; |
|||
if (keys == null) |
|||
{ |
|||
throw new InvalidOperationException("The OAuth 2.0/OpenID Connect cryptography didn't contain any JSON web key"); |
|||
} |
|||
|
|||
foreach (var payload in keys.Value.GetParameters()) |
|||
{ |
|||
var type = (string) payload.Value[JsonWebKeyParameterNames.Kty]; |
|||
if (string.IsNullOrEmpty(type)) |
|||
{ |
|||
throw new InvalidOperationException("A JWKS response containing an invalid key was returned."); |
|||
} |
|||
|
|||
var key = type switch |
|||
{ |
|||
JsonWebAlgorithmsKeyTypes.RSA => new JsonWebKey |
|||
{ |
|||
Kty = JsonWebAlgorithmsKeyTypes.RSA, |
|||
E = (string) payload.Value[JsonWebKeyParameterNames.E], |
|||
N = (string) payload.Value[JsonWebKeyParameterNames.N] |
|||
}, |
|||
|
|||
JsonWebAlgorithmsKeyTypes.EllipticCurve => new JsonWebKey |
|||
{ |
|||
Kty = JsonWebAlgorithmsKeyTypes.EllipticCurve, |
|||
Crv = (string) payload.Value[JsonWebKeyParameterNames.Crv], |
|||
X = (string) payload.Value[JsonWebKeyParameterNames.X], |
|||
Y = (string) payload.Value[JsonWebKeyParameterNames.Y] |
|||
}, |
|||
|
|||
_ => throw new InvalidOperationException("A JWKS response containing an unsupported key was returned.") |
|||
}; |
|||
|
|||
key.KeyId = (string) payload.Value[JsonWebKeyParameterNames.Kid]; |
|||
key.X5t = (string) payload.Value[JsonWebKeyParameterNames.X5t]; |
|||
key.X5tS256 = (string) payload.Value[JsonWebKeyParameterNames.X5tS256]; |
|||
|
|||
if (payload.Value.TryGetParameter(JsonWebKeyParameterNames.X5c, out var chain)) |
|||
{ |
|||
foreach (var certificate in chain.GetParameters()) |
|||
{ |
|||
var value = (string) certificate.Value; |
|||
if (string.IsNullOrEmpty(value)) |
|||
{ |
|||
throw new InvalidOperationException("A JWKS response containing an invalid key was returned."); |
|||
} |
|||
|
|||
key.X5c.Add(value); |
|||
} |
|||
} |
|||
|
|||
yield return key; |
|||
} |
|||
} |
|||
|
|||
async ValueTask<OpenIddictResponse> SendHttpRequestMessageAsync(Uri address) |
|||
{ |
|||
using var request = new HttpRequestMessage(HttpMethod.Get, address); |
|||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); |
|||
|
|||
using var response = await client.SendAsync(request, HttpCompletionOption.ResponseContentRead); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, |
|||
"The OAuth 2.0/OpenID Connect discovery failed because an invalid response was received:" + |
|||
"the identity provider returned returned a {0} response with the following payload: {1} {2}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync())); |
|||
} |
|||
|
|||
var media = response.Content?.Headers.ContentType?.MediaType; |
|||
if (!string.Equals(media, "application/json", StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, |
|||
"The OAuth 2.0/OpenID Connect discovery failed because an invalid content type was received:" + |
|||
"the identity provider returned returned a {0} response with the following payload: {1} {2}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync())); |
|||
} |
|||
|
|||
using var stream = await response.Content.ReadAsStreamAsync(); |
|||
using var reader = new JsonTextReader(new StreamReader(stream)); |
|||
|
|||
var serializer = JsonSerializer.CreateDefault(); |
|||
return serializer.Deserialize<OpenIddictResponse>(reader); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of caching the token validation parameters.
|
|||
/// </summary>
|
|||
public class CacheTokenValidationParameters : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IMemoryCache _cache; |
|||
|
|||
public CacheTokenValidationParameters([NotNull] IMemoryCache cache) |
|||
=> _cache = cache; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<CacheTokenValidationParameters>() |
|||
.SetOrder(ValidateTokenValidationParameters.Descriptor.Order + 500) |
|||
.Build(); |
|||
|
|||
public ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.TokenValidationParameters == null) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// If the metadata address is not an HTTP/HTTPS address, let another handler populate the validation parameters.
|
|||
if (!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase) && |
|||
!string.Equals(context.Options.MetadataAddress.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// Store the token validation parameters in the memory cache.
|
|||
_ = _cache.GetOrCreate( |
|||
key: string.Concat("af84c073-c27c-49fd-a54f-584fd60320d3", "\x1e", context.Issuer?.AbsoluteUri), |
|||
factory: entry => |
|||
{ |
|||
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(30)); |
|||
entry.SetPriority(CacheItemPriority.NeverRemove); |
|||
|
|||
return context.TokenValidationParameters; |
|||
}); |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/* |
|||
* 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.Net; |
|||
using System.Net.Http; |
|||
using Polly; |
|||
using Polly.Extensions.Http; |
|||
|
|||
namespace OpenIddict.Validation.SystemNetHttp |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict validation/System.Net.Http integration.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationSystemNetHttpOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the HTTP Polly error policy used by the internal OpenIddict HTTP clients.
|
|||
/// </summary>
|
|||
public IAsyncPolicy<HttpResponseMessage> HttpErrorPolicy { get; set; } |
|||
= HttpPolicyExtensions.HandleTransientHttpError() |
|||
.OrResult(response => response.StatusCode == HttpStatusCode.NotFound) |
|||
.WaitAndRetryAsync(4, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))); |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
/* |
|||
* 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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a handler able to process <typeparamref name="TContext"/> events.
|
|||
/// </summary>
|
|||
/// <typeparam name="TContext">The type of the context associated with events handled by this instance.</typeparam>
|
|||
public interface IOpenIddictValidationHandler<in TContext> where TContext : BaseContext |
|||
{ |
|||
/// <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>
|
|||
ValueTask HandleAsync([NotNull] TContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
/* |
|||
* 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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
public interface IOpenIddictValidationHandlerFilter<in TContext> where TContext : BaseContext |
|||
{ |
|||
ValueTask<bool> IsActiveAsync([NotNull] TContext context); |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
/* |
|||
* 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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
public interface IOpenIddictValidationProvider |
|||
{ |
|||
ValueTask<OpenIddictValidationTransaction> CreateTransactionAsync(); |
|||
ValueTask DispatchAsync<TContext>([NotNull] TContext context) where TContext : BaseContext; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFrameworks>netstandard2.0;netstandard2.1</TargetFrameworks> |
|||
</PropertyGroup> |
|||
|
|||
<PropertyGroup> |
|||
<Description>OpenID Connect validation components for OpenIddict.</Description> |
|||
<PackageTags>$(PackageTags);validation</PackageTags> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\OpenIddict.Abstractions\OpenIddict.Abstractions.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="JetBrains.Annotations" Version="$(JetBrainsVersion)" PrivateAssets="All" /> |
|||
<PackageReference Include="Microsoft.Extensions.Logging" Version="$(ExtensionsVersion)" /> |
|||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="$(IdentityModelVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,657 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using System.Runtime.InteropServices; |
|||
using System.Security.Cryptography; |
|||
using System.Security.Cryptography.X509Certificates; |
|||
using System.Text; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Validation; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes the necessary methods required to configure the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationBuilder |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of <see cref="OpenIddictValidationBuilder"/>.
|
|||
/// </summary>
|
|||
/// <param name="services">The services collection.</param>
|
|||
public OpenIddictValidationBuilder([NotNull] IServiceCollection services) |
|||
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the services collection.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public IServiceCollection Services { get; } |
|||
|
|||
/// <summary>
|
|||
/// Registers an event handler using the specified configuration delegate.
|
|||
/// </summary>
|
|||
/// <typeparam name="TContext">The event context type.</typeparam>
|
|||
/// <param name="configuration">The configuration delegate.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictValidationBuilder AddEventHandler<TContext>( |
|||
[NotNull] Action<OpenIddictValidationHandlerDescriptor.Builder<TContext>> configuration) |
|||
where TContext : OpenIddictValidationEvents.BaseContext |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
var builder = OpenIddictValidationHandlerDescriptor.CreateBuilder<TContext>(); |
|||
configuration(builder); |
|||
|
|||
return AddEventHandler(builder.Build()); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers an event handler using the specified descriptor.
|
|||
/// </summary>
|
|||
/// <param name="descriptor">The handler descriptor.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictValidationBuilder AddEventHandler([NotNull] OpenIddictValidationHandlerDescriptor descriptor) |
|||
{ |
|||
if (descriptor == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(descriptor)); |
|||
} |
|||
|
|||
// Register the handler in the services collection.
|
|||
Services.Add(descriptor.ServiceDescriptor); |
|||
|
|||
return Configure(options => options.CustomHandlers.Add(descriptor)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Removes the event handler that matches the specified descriptor.
|
|||
/// </summary>
|
|||
/// <param name="descriptor">The descriptor corresponding to the handler to remove.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public OpenIddictValidationBuilder RemoveEventHandler([NotNull] OpenIddictValidationHandlerDescriptor descriptor) |
|||
{ |
|||
if (descriptor == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(descriptor)); |
|||
} |
|||
|
|||
Services.RemoveAll(descriptor.ServiceDescriptor.ServiceType); |
|||
|
|||
Services.PostConfigure<OpenIddictValidationOptions>(options => |
|||
{ |
|||
for (var index = options.CustomHandlers.Count - 1; index >= 0; index--) |
|||
{ |
|||
if (options.CustomHandlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) |
|||
{ |
|||
options.CustomHandlers.RemoveAt(index); |
|||
} |
|||
} |
|||
|
|||
for (var index = options.DefaultHandlers.Count - 1; index >= 0; index--) |
|||
{ |
|||
if (options.DefaultHandlers[index].ServiceDescriptor.ServiceType == descriptor.ServiceDescriptor.ServiceType) |
|||
{ |
|||
options.DefaultHandlers.RemoveAt(index); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Amends the default OpenIddict validation configuration.
|
|||
/// </summary>
|
|||
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder Configure([NotNull] Action<OpenIddictValidationOptions> configuration) |
|||
{ |
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
Services.Configure(configuration); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the <see cref="EncryptingCredentials"/> used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="credentials">The encrypting credentials.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCredentials([NotNull] EncryptingCredentials credentials) |
|||
{ |
|||
if (credentials == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(credentials)); |
|||
} |
|||
|
|||
return Configure(options => options.EncryptionCredentials.Add(credentials)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="SecurityKey"/> used to decrypt the access tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="key">The security key.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionKey([NotNull] SecurityKey key) |
|||
{ |
|||
if (key == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(key)); |
|||
} |
|||
|
|||
// If the encryption key is an asymmetric security key, ensure it has a private key.
|
|||
if (key is AsymmetricSecurityKey asymmetricSecurityKey && |
|||
asymmetricSecurityKey.PrivateKeyStatus == PrivateKeyStatus.DoesNotExist) |
|||
{ |
|||
throw new InvalidOperationException("The asymmetric encryption key doesn't contain the required private key."); |
|||
} |
|||
|
|||
if (IsAlgorithmSupported(key, SecurityAlgorithms.Aes256KW)) |
|||
{ |
|||
return AddEncryptionCredentials(new EncryptingCredentials(key, |
|||
SecurityAlgorithms.Aes256KW, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|||
} |
|||
|
|||
if (IsAlgorithmSupported(key, SecurityAlgorithms.RsaOAEP)) |
|||
{ |
|||
return AddEncryptionCredentials(new EncryptingCredentials(key, |
|||
SecurityAlgorithms.RsaOAEP, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|||
} |
|||
|
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("An encryption algorithm cannot be automatically inferred from the encrypting key.") |
|||
.Append("Consider using 'options.AddEncryptionCredentials(EncryptingCredentials)' instead.") |
|||
.ToString()); |
|||
|
|||
static bool IsAlgorithmSupported(SecurityKey key, string algorithm) => |
|||
key.CryptoProviderFactory.IsSupportedAlgorithm(algorithm, key); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers (and generates if necessary) a user-specific development
|
|||
/// certificate used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddDevelopmentEncryptionCertificate() |
|||
=> AddDevelopmentEncryptionCertificate(new X500DistinguishedName("CN=OpenIddict Validation Encryption Certificate")); |
|||
|
|||
/// <summary>
|
|||
/// Registers (and generates if necessary) a user-specific development
|
|||
/// certificate used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="subject">The subject name associated with the certificate.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddDevelopmentEncryptionCertificate([NotNull] X500DistinguishedName subject) |
|||
{ |
|||
if (subject == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(subject)); |
|||
} |
|||
|
|||
using var store = new X509Store(StoreName.My, StoreLocation.CurrentUser); |
|||
store.Open(OpenFlags.ReadWrite); |
|||
|
|||
// Try to retrieve the development certificate from the specified store.
|
|||
// If a certificate was found but is not yet or no longer valid, remove it
|
|||
// from the store before creating and persisting a new encryption certificate.
|
|||
var certificate = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName, subject.Name, validOnly: false) |
|||
.OfType<X509Certificate2>() |
|||
.SingleOrDefault(); |
|||
|
|||
if (certificate != null && (certificate.NotBefore > DateTime.Now || certificate.NotAfter < DateTime.Now)) |
|||
{ |
|||
store.Remove(certificate); |
|||
certificate = null; |
|||
} |
|||
|
|||
#if SUPPORTS_CERTIFICATE_GENERATION
|
|||
// If no appropriate certificate can be found, generate and persist a new certificate in the specified store.
|
|||
if (certificate == null) |
|||
{ |
|||
using var algorithm = RSA.Create(keySizeInBits: 2048); |
|||
|
|||
var request = new CertificateRequest(subject, algorithm, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); |
|||
request.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.KeyEncipherment, critical: true)); |
|||
|
|||
certificate = request.CreateSelfSigned(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddYears(2)); |
|||
|
|||
// Note: setting the friendly name is not supported on Unix machines (including Linux and macOS).
|
|||
// To ensure an exception is not thrown by the property setter, an OS runtime check is used here.
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) |
|||
{ |
|||
certificate.FriendlyName = "OpenIddict Validation Development Encryption Certificate"; |
|||
} |
|||
|
|||
// Note: CertificateRequest.CreateSelfSigned() doesn't mark the key set associated with the certificate
|
|||
// as "persisted", which eventually prevents X509Store.Add() from correctly storing the private key.
|
|||
// To work around this issue, the certificate payload is manually exported and imported back
|
|||
// into a new X509Certificate2 instance specifying the X509KeyStorageFlags.PersistKeySet flag.
|
|||
var data = certificate.Export(X509ContentType.Pfx, string.Empty); |
|||
|
|||
try |
|||
{ |
|||
var flags = X509KeyStorageFlags.PersistKeySet; |
|||
|
|||
// Note: macOS requires marking the certificate private key as exportable.
|
|||
// If this flag is not set, a CryptographicException is thrown at runtime.
|
|||
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) |
|||
{ |
|||
flags |= X509KeyStorageFlags.Exportable; |
|||
} |
|||
|
|||
certificate = new X509Certificate2(data, string.Empty, flags); |
|||
} |
|||
|
|||
finally |
|||
{ |
|||
Array.Clear(data, 0, data.Length); |
|||
} |
|||
|
|||
store.Add(certificate); |
|||
} |
|||
|
|||
return AddEncryptionCertificate(certificate); |
|||
#else
|
|||
throw new PlatformNotSupportedException("X.509 certificate generation is not supported on this platform."); |
|||
#endif
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a new ephemeral key used to decrypt the tokens issued by OpenIddict: the key
|
|||
/// is discarded when the application shuts down and tokens encrypted using this key are
|
|||
/// automatically invalidated. This method should only be used during development.
|
|||
/// On production, using a X.509 certificate stored in the machine store is recommended.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEphemeralEncryptionKey() |
|||
=> AddEphemeralEncryptionKey(SecurityAlgorithms.RsaOAEP); |
|||
|
|||
/// <summary>
|
|||
/// Registers a new ephemeral key used to decrypt the tokens issued by OpenIddict: the key
|
|||
/// is discarded when the application shuts down and tokens encrypted using this key are
|
|||
/// automatically invalidated. This method should only be used during development.
|
|||
/// On production, using a X.509 certificate stored in the machine store is recommended.
|
|||
/// </summary>
|
|||
/// <param name="algorithm">The algorithm associated with the encryption key.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEphemeralEncryptionKey([NotNull] string algorithm) |
|||
{ |
|||
if (string.IsNullOrEmpty(algorithm)) |
|||
{ |
|||
throw new ArgumentException("The algorithm cannot be null or empty.", nameof(algorithm)); |
|||
} |
|||
|
|||
switch (algorithm) |
|||
{ |
|||
case SecurityAlgorithms.Aes256KW: |
|||
return AddEncryptionCredentials(new EncryptingCredentials(CreateSymmetricSecurityKey(256), |
|||
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|||
|
|||
case SecurityAlgorithms.RsaOAEP: |
|||
case SecurityAlgorithms.RsaOaepKeyWrap: |
|||
return AddEncryptionCredentials(new EncryptingCredentials(CreateRsaSecurityKey(2048), |
|||
algorithm, SecurityAlgorithms.Aes256CbcHmacSha512)); |
|||
|
|||
default: throw new InvalidOperationException("The specified algorithm is not supported."); |
|||
} |
|||
|
|||
static SymmetricSecurityKey CreateSymmetricSecurityKey(int size) |
|||
{ |
|||
var data = new byte[size / 8]; |
|||
|
|||
#if SUPPORTS_STATIC_RANDOM_NUMBER_GENERATOR_METHODS
|
|||
RandomNumberGenerator.Fill(data); |
|||
#else
|
|||
using var generator = RandomNumberGenerator.Create(); |
|||
generator.GetBytes(data); |
|||
#endif
|
|||
|
|||
return new SymmetricSecurityKey(data); |
|||
} |
|||
|
|||
static RsaSecurityKey CreateRsaSecurityKey(int size) |
|||
{ |
|||
#if SUPPORTS_DIRECT_KEY_CREATION_WITH_SPECIFIED_SIZE
|
|||
return new RsaSecurityKey(RSA.Create(size)); |
|||
#else
|
|||
// Note: a 1024-bit key might be returned by RSA.Create() on .NET Desktop/Mono,
|
|||
// where RSACryptoServiceProvider is still the default implementation and
|
|||
// where custom implementations can be registered via CryptoConfig.
|
|||
// To ensure the key size is always acceptable, replace it if necessary.
|
|||
var algorithm = RSA.Create(); |
|||
if (algorithm.KeySize < size) |
|||
{ |
|||
algorithm.KeySize = size; |
|||
} |
|||
|
|||
if (algorithm.KeySize < size && algorithm is RSACryptoServiceProvider) |
|||
{ |
|||
algorithm.Dispose(); |
|||
algorithm = new RSACryptoServiceProvider(size); |
|||
} |
|||
|
|||
if (algorithm.KeySize < size) |
|||
{ |
|||
throw new InvalidOperationException("RSA key generation failed."); |
|||
} |
|||
|
|||
return new RsaSecurityKey(algorithm); |
|||
#endif
|
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> that is used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="certificate">The certificate used to decrypt the security tokens issued by the validation.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate([NotNull] X509Certificate2 certificate) |
|||
{ |
|||
if (certificate == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(certificate)); |
|||
} |
|||
|
|||
if (certificate.NotBefore > DateTime.Now) |
|||
{ |
|||
throw new InvalidOperationException("The specified certificate is not yet valid."); |
|||
} |
|||
|
|||
if (certificate.NotAfter < DateTime.Now) |
|||
{ |
|||
throw new InvalidOperationException("The specified certificate is no longer valid."); |
|||
} |
|||
|
|||
if (!certificate.HasPrivateKey) |
|||
{ |
|||
throw new InvalidOperationException("The specified certificate doesn't contain the required private key."); |
|||
} |
|||
|
|||
return AddEncryptionKey(new X509SecurityKey(certificate)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> retrieved from an
|
|||
/// embedded resource and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="assembly">The assembly containing the certificate.</param>
|
|||
/// <param name="resource">The name of the embedded resource.</param>
|
|||
/// <param name="password">The password used to open the certificate.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate( |
|||
[NotNull] Assembly assembly, [NotNull] string resource, [NotNull] string password) |
|||
#if SUPPORTS_EPHEMERAL_KEY_SETS
|
|||
// Note: ephemeral key sets are currently not supported on macOS.
|
|||
=> AddEncryptionCertificate(assembly, resource, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? |
|||
X509KeyStorageFlags.MachineKeySet : |
|||
X509KeyStorageFlags.EphemeralKeySet); |
|||
#else
|
|||
=> AddEncryptionCertificate(assembly, resource, password, X509KeyStorageFlags.MachineKeySet); |
|||
#endif
|
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> retrieved from an
|
|||
/// embedded resource and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="assembly">The assembly containing the certificate.</param>
|
|||
/// <param name="resource">The name of the embedded resource.</param>
|
|||
/// <param name="password">The password used to open the certificate.</param>
|
|||
/// <param name="flags">An enumeration of flags indicating how and where to store the private key of the certificate.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate( |
|||
[NotNull] Assembly assembly, [NotNull] string resource, |
|||
[NotNull] string password, X509KeyStorageFlags flags) |
|||
{ |
|||
if (assembly == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(assembly)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(resource)) |
|||
{ |
|||
throw new ArgumentException("The resource cannot be null or empty.", nameof(resource)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(password)) |
|||
{ |
|||
throw new ArgumentException("The password cannot be null or empty.", nameof(password)); |
|||
} |
|||
|
|||
using var stream = assembly.GetManifestResourceStream(resource); |
|||
if (stream == null) |
|||
{ |
|||
throw new InvalidOperationException("The certificate was not found in the specified assembly."); |
|||
} |
|||
|
|||
return AddEncryptionCertificate(stream, password, flags); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> extracted from a
|
|||
/// stream and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream containing the certificate.</param>
|
|||
/// <param name="password">The password used to open the certificate.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate([NotNull] Stream stream, [NotNull] string password) |
|||
#if SUPPORTS_EPHEMERAL_KEY_SETS
|
|||
// Note: ephemeral key sets are currently not supported on macOS.
|
|||
=> AddEncryptionCertificate(stream, password, RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? |
|||
X509KeyStorageFlags.MachineKeySet : |
|||
X509KeyStorageFlags.EphemeralKeySet); |
|||
#else
|
|||
=> AddEncryptionCertificate(stream, password, X509KeyStorageFlags.MachineKeySet); |
|||
#endif
|
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> extracted from a
|
|||
/// stream and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="stream">The stream containing the certificate.</param>
|
|||
/// <param name="password">The password used to open the certificate.</param>
|
|||
/// <param name="flags">
|
|||
/// An enumeration of flags indicating how and where
|
|||
/// to store the private key of the certificate.
|
|||
/// </param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate( |
|||
[NotNull] Stream stream, [NotNull] string password, X509KeyStorageFlags flags) |
|||
{ |
|||
if (stream == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(stream)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(password)) |
|||
{ |
|||
throw new ArgumentException("The password cannot be null or empty.", nameof(password)); |
|||
} |
|||
|
|||
using var buffer = new MemoryStream(); |
|||
stream.CopyTo(buffer); |
|||
|
|||
return AddEncryptionCertificate(new X509Certificate2(buffer.ToArray(), password, flags)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> retrieved from the X.509
|
|||
/// machine store and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate([NotNull] string thumbprint) |
|||
{ |
|||
if (string.IsNullOrEmpty(thumbprint)) |
|||
{ |
|||
throw new ArgumentException("The thumbprint cannot be null or empty.", nameof(thumbprint)); |
|||
} |
|||
|
|||
var certificate = GetCertificate(StoreLocation.CurrentUser, thumbprint) ?? GetCertificate(StoreLocation.LocalMachine, thumbprint); |
|||
if (certificate == null) |
|||
{ |
|||
throw new InvalidOperationException("The certificate corresponding to the specified thumbprint was not found."); |
|||
} |
|||
|
|||
return AddEncryptionCertificate(certificate); |
|||
|
|||
static X509Certificate2 GetCertificate(StoreLocation location, string thumbprint) |
|||
{ |
|||
using var store = new X509Store(StoreName.My, location); |
|||
store.Open(OpenFlags.ReadOnly); |
|||
|
|||
return store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) |
|||
.OfType<X509Certificate2>() |
|||
.SingleOrDefault(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers a <see cref="X509Certificate2"/> retrieved from the given
|
|||
/// X.509 store and used to decrypt the tokens issued by OpenIddict.
|
|||
/// </summary>
|
|||
/// <param name="thumbprint">The thumbprint of the certificate used to identify it in the X.509 store.</param>
|
|||
/// <param name="name">The name of the X.509 store.</param>
|
|||
/// <param name="location">The location of the X.509 store.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddEncryptionCertificate( |
|||
[NotNull] string thumbprint, StoreName name, StoreLocation location) |
|||
{ |
|||
if (string.IsNullOrEmpty(thumbprint)) |
|||
{ |
|||
throw new ArgumentException("The thumbprint cannot be null or empty.", nameof(thumbprint)); |
|||
} |
|||
|
|||
using var store = new X509Store(name, location); |
|||
store.Open(OpenFlags.ReadOnly); |
|||
|
|||
var certificate = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, validOnly: false) |
|||
.OfType<X509Certificate2>() |
|||
.SingleOrDefault(); |
|||
|
|||
if (certificate == null) |
|||
{ |
|||
throw new InvalidOperationException("The certificate corresponding to the specified thumbprint was not found."); |
|||
} |
|||
|
|||
return AddEncryptionCertificate(certificate); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the specified values as valid audiences. Setting the audiences is recommended
|
|||
/// when the authorization server issues access tokens for multiple distinct resource servers.
|
|||
/// </summary>
|
|||
/// <param name="audiences">The audiences valid for this resource server.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder AddAudiences([NotNull] params string[] audiences) |
|||
{ |
|||
if (audiences == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(audiences)); |
|||
} |
|||
|
|||
if (audiences.Any(audience => string.IsNullOrEmpty(audience))) |
|||
{ |
|||
throw new ArgumentException("Audiences cannot be null or empty.", nameof(audiences)); |
|||
} |
|||
|
|||
return Configure(options => options.Audiences.UnionWith(audiences)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Enables authorization validation so that a database call is made for each API request
|
|||
/// to ensure the authorization associated with the access token is still valid.
|
|||
/// Note: enabling this option may have an impact on performance.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder EnableAuthorizationValidation() |
|||
=> Configure(options => options.EnableAuthorizationValidation = true); |
|||
|
|||
/// <summary>
|
|||
/// Sets the issuer address, which is used to determine the actual location of the
|
|||
/// OAuth 2.0/OpenID Connect configuration document when using provider discovery.
|
|||
/// </summary>
|
|||
/// <param name="address">The issuer address.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder SetIssuer([NotNull] Uri address) |
|||
{ |
|||
if (address == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(address)); |
|||
} |
|||
|
|||
return Configure(options => options.Issuer = address); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the static token validation parameters.
|
|||
/// </summary>
|
|||
/// <param name="parameters">The issuer address.</param>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder SetTokenValidationParameters([NotNull] TokenValidationParameters parameters) |
|||
{ |
|||
if (parameters == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(parameters)); |
|||
} |
|||
|
|||
return Configure(options => options.TokenValidationParameters = parameters); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures OpenIddict to use reference tokens, so that authorization codes,
|
|||
/// access tokens and refresh tokens are stored as ciphertext in the database
|
|||
/// (only an identifier is returned to the client application). Enabling this option
|
|||
/// is useful to keep track of all the issued tokens, when storing a very large
|
|||
/// number of claims in the authorization codes, access tokens and refresh tokens
|
|||
/// or when immediate revocation of reference access tokens is desired.
|
|||
/// Note: this option cannot be used when configuring JWT as the access token format.
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public OpenIddictValidationBuilder UseReferenceTokens() |
|||
=> Configure(options => options.UseReferenceTokens = true); |
|||
|
|||
/// <summary>
|
|||
/// Determines whether the specified object is equal to the current object.
|
|||
/// </summary>
|
|||
/// <param name="obj">The object to compare with the current object.</param>
|
|||
/// <returns><c>true</c> if the specified object is equal to the current object; otherwise, false.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override bool Equals([CanBeNull] object obj) => base.Equals(obj); |
|||
|
|||
/// <summary>
|
|||
/// Serves as the default hash function.
|
|||
/// </summary>
|
|||
/// <returns>A hash code for the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override int GetHashCode() => base.GetHashCode(); |
|||
|
|||
/// <summary>
|
|||
/// Returns a string that represents the current object.
|
|||
/// </summary>
|
|||
/// <returns>A string that represents the current object.</returns>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public override string ToString() => base.ToString(); |
|||
} |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
/* |
|||
* 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.Diagnostics; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Contains the methods required to ensure that the OpenIddict validation configuration is valid.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationConfiguration : IPostConfigureOptions<OpenIddictValidationOptions> |
|||
{ |
|||
/// <summary>
|
|||
/// Populates the default OpenIddict validation options and ensures
|
|||
/// that the configuration is in a consistent and valid state.
|
|||
/// </summary>
|
|||
/// <param name="name">The authentication scheme associated with the handler instance.</param>
|
|||
/// <param name="options">The options instance to initialize.</param>
|
|||
public void PostConfigure([CanBeNull] string name, [NotNull] OpenIddictValidationOptions options) |
|||
{ |
|||
if (options == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(options)); |
|||
} |
|||
|
|||
if (options.SecurityTokenHandler == null) |
|||
{ |
|||
throw new InvalidOperationException("The security token handler cannot be null."); |
|||
} |
|||
|
|||
if (options.TokenValidationParameters == null) |
|||
{ |
|||
if (options.Issuer == null && options.MetadataAddress == null) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The authority or an absolute metadata endpoint address must be provided.") |
|||
.Append("Alternatively, token validation parameters can be manually set by calling ") |
|||
.AppendLine("'services.AddOpenIddict().AddValidation().SetTokenValidationParameters()'.") |
|||
.Append("To use the server configuration of a local OpenIddict server instance, ") |
|||
.Append("reference the 'OpenIddict.Validation.ServerIntegration' package ") |
|||
.Append("and call 'services.AddOpenIddict().AddValidation().UseLocalServer()'.") |
|||
.ToString()); |
|||
} |
|||
|
|||
if (options.MetadataAddress == null) |
|||
{ |
|||
options.MetadataAddress = new Uri(".well-known/openid-configuration", UriKind.Relative); |
|||
} |
|||
|
|||
if (!options.MetadataAddress.IsAbsoluteUri) |
|||
{ |
|||
if (options.Issuer == null || !options.Issuer.IsAbsoluteUri) |
|||
{ |
|||
throw new InvalidOperationException("The authority must be provided and must be an absolute URL."); |
|||
} |
|||
|
|||
if (!string.IsNullOrEmpty(options.Issuer.Fragment) || !string.IsNullOrEmpty(options.Issuer.Query)) |
|||
{ |
|||
throw new InvalidOperationException("The authority cannot contain a fragment or a query string."); |
|||
} |
|||
|
|||
if (!options.Issuer.OriginalString.EndsWith("/")) |
|||
{ |
|||
options.Issuer = new Uri(options.Issuer.OriginalString + "/", UriKind.Absolute); |
|||
} |
|||
|
|||
options.MetadataAddress = new Uri(options.Issuer, options.MetadataAddress); |
|||
} |
|||
} |
|||
|
|||
foreach (var key in options.EncryptionCredentials.Select(credentials => credentials.Key)) |
|||
{ |
|||
if (!string.IsNullOrEmpty(key.KeyId)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
key.KeyId = GetKeyIdentifier(key); |
|||
} |
|||
|
|||
static string GetKeyIdentifier(SecurityKey key) |
|||
{ |
|||
// When no key identifier can be retrieved from the security keys, a value is automatically
|
|||
// inferred from the hexadecimal representation of the certificate thumbprint (SHA-1)
|
|||
// when the key is bound to a X.509 certificate or from the public part of the signing key.
|
|||
|
|||
if (key is X509SecurityKey x509SecurityKey) |
|||
{ |
|||
return x509SecurityKey.Certificate.Thumbprint; |
|||
} |
|||
|
|||
if (key is RsaSecurityKey rsaSecurityKey) |
|||
{ |
|||
// Note: if the RSA parameters are not attached to the signing key,
|
|||
// extract them by calling ExportParameters on the RSA instance.
|
|||
var parameters = rsaSecurityKey.Parameters; |
|||
if (parameters.Modulus == null) |
|||
{ |
|||
parameters = rsaSecurityKey.Rsa.ExportParameters(includePrivateParameters: false); |
|||
|
|||
Debug.Assert(parameters.Modulus != null, |
|||
"A null modulus shouldn't be returned by RSA.ExportParameters()."); |
|||
} |
|||
|
|||
// Only use the 40 first chars of the base64url-encoded modulus.
|
|||
var identifier = Base64UrlEncoder.Encode(parameters.Modulus); |
|||
return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); |
|||
} |
|||
|
|||
#if SUPPORTS_ECDSA
|
|||
if (key is ECDsaSecurityKey ecsdaSecurityKey) |
|||
{ |
|||
// Extract the ECDSA parameters from the signing credentials.
|
|||
var parameters = ecsdaSecurityKey.ECDsa.ExportParameters(includePrivateParameters: false); |
|||
|
|||
Debug.Assert(parameters.Q.X != null, |
|||
"Invalid coordinates shouldn't be returned by ECDsa.ExportParameters()."); |
|||
|
|||
// Only use the 40 first chars of the base64url-encoded X coordinate.
|
|||
var identifier = Base64UrlEncoder.Encode(parameters.Q.X); |
|||
return identifier.Substring(0, Math.Min(identifier.Length, 40)).ToUpperInvariant(); |
|||
} |
|||
#endif
|
|||
|
|||
return null; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
/* |
|||
* 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. |
|||
*/ |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the type of an OpenIddict validation endpoint.
|
|||
/// </summary>
|
|||
public enum OpenIddictValidationEndpointType |
|||
{ |
|||
/// <summary>
|
|||
/// Unknown endpoint.
|
|||
/// </summary>
|
|||
Unknown = 0 |
|||
} |
|||
} |
|||
@ -0,0 +1,271 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.ComponentModel; |
|||
using System.Security.Claims; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
public static partial class OpenIddictValidationEvents |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an abstract base class used for certain event contexts.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public abstract class BaseContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="BaseContext"/> class.
|
|||
/// </summary>
|
|||
protected BaseContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
=> Transaction = transaction ?? throw new ArgumentNullException(nameof(transaction)); |
|||
|
|||
/// <summary>
|
|||
/// Gets the environment associated with the current request being processed.
|
|||
/// </summary>
|
|||
public OpenIddictValidationTransaction Transaction { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the endpoint type that handled the request, if applicable.
|
|||
/// </summary>
|
|||
public OpenIddictValidationEndpointType EndpointType |
|||
{ |
|||
get => Transaction.EndpointType; |
|||
set => Transaction.EndpointType = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the issuer address associated with the current transaction, if available.
|
|||
/// </summary>
|
|||
public Uri Issuer |
|||
{ |
|||
get => Transaction.Issuer; |
|||
set => Transaction.Issuer = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the logger responsible of logging processed operations.
|
|||
/// </summary>
|
|||
public ILogger Logger => Transaction.Logger; |
|||
|
|||
/// <summary>
|
|||
/// Gets the OpenIddict validation options.
|
|||
/// </summary>
|
|||
public OpenIddictValidationOptions Options => Transaction.Options; |
|||
|
|||
/// <summary>
|
|||
/// Gets the dictionary containing the properties associated with this event.
|
|||
/// </summary>
|
|||
public IDictionary<string, object> Properties { get; } |
|||
= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the OpenIddict request or <c>null</c> if it couldn't be extracted.
|
|||
/// </summary>
|
|||
public OpenIddictRequest Request |
|||
{ |
|||
get => Transaction.Request; |
|||
set => Transaction.Request = value; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the OpenIddict response, if applicable.
|
|||
/// </summary>
|
|||
public OpenIddictResponse Response |
|||
{ |
|||
get => Transaction.Response; |
|||
set => Transaction.Response = value; |
|||
} |
|||
} |
|||
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public abstract class BaseRequestContext : BaseContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="BaseRequestContext"/> class.
|
|||
/// </summary>
|
|||
protected BaseRequestContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a boolean indicating whether the request was fully handled.
|
|||
/// </summary>
|
|||
public bool IsRequestHandled { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets a boolean indicating whether the request processing was skipped.
|
|||
/// </summary>
|
|||
public bool IsRequestSkipped { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Marks the request as fully handled. Once declared handled,
|
|||
/// a request shouldn't be processed further by the underlying host.
|
|||
/// </summary>
|
|||
public void HandleRequest() => IsRequestHandled = true; |
|||
|
|||
/// <summary>
|
|||
/// Marks the request as skipped. Once declared skipped, a request
|
|||
/// shouldn't be processed further by OpenIddict but should be allowed
|
|||
/// to go through the next components in the processing pipeline
|
|||
/// (if this pattern is supported by the underlying host).
|
|||
/// </summary>
|
|||
public void SkipRequest() => IsRequestSkipped = true; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an abstract base class used for certain event contexts.
|
|||
/// </summary>
|
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public abstract class BaseValidatingContext : BaseRequestContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="BaseValidatingContext"/> class.
|
|||
/// </summary>
|
|||
protected BaseValidatingContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets a boolean indicating whether the request will be rejected.
|
|||
/// </summary>
|
|||
public bool IsRejected { get; protected set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the "error" parameter returned to the client application.
|
|||
/// </summary>
|
|||
public string Error { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the "error_description" parameter returned to the client application.
|
|||
/// </summary>
|
|||
public string ErrorDescription { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the "error_uri" parameter returned to the client application.
|
|||
/// </summary>
|
|||
public string ErrorUri { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Rejects the request.
|
|||
/// </summary>
|
|||
public virtual void Reject() => IsRejected = true; |
|||
|
|||
/// <summary>
|
|||
/// Rejects the request.
|
|||
/// </summary>
|
|||
/// <param name="error">The "error" parameter returned to the client application.</param>
|
|||
public virtual void Reject(string error) |
|||
{ |
|||
Error = error; |
|||
|
|||
Reject(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rejects the request.
|
|||
/// </summary>
|
|||
/// <param name="error">The "error" parameter returned to the client application.</param>
|
|||
/// <param name="description">The "error_description" parameter returned to the client application.</param>
|
|||
public virtual void Reject(string error, string description) |
|||
{ |
|||
Error = error; |
|||
ErrorDescription = description; |
|||
|
|||
Reject(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Rejects the request.
|
|||
/// </summary>
|
|||
/// <param name="error">The "error" parameter returned to the client application.</param>
|
|||
/// <param name="description">The "error_description" parameter returned to the client application.</param>
|
|||
/// <param name="uri">The "error_uri" parameter returned to the client application.</param>
|
|||
public virtual void Reject(string error, string description, string uri) |
|||
{ |
|||
Error = error; |
|||
ErrorDescription = description; |
|||
ErrorUri = uri; |
|||
|
|||
Reject(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an event called when processing an incoming request.
|
|||
/// </summary>
|
|||
public class ProcessRequestContext : BaseValidatingContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="ProcessRequestContext"/> class.
|
|||
/// </summary>
|
|||
public ProcessRequestContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an event called when processing an errored response.
|
|||
/// </summary>
|
|||
public class ProcessErrorContext : BaseRequestContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="ProcessErrorContext"/> class.
|
|||
/// </summary>
|
|||
public ProcessErrorContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an event called when processing an authentication operation.
|
|||
/// </summary>
|
|||
public class ProcessAuthenticationContext : BaseValidatingContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="ProcessAuthenticationContext"/> class.
|
|||
/// </summary>
|
|||
public ProcessAuthenticationContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the token validation parameters used for the current request.
|
|||
/// </summary>
|
|||
public TokenValidationParameters TokenValidationParameters { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the security principal.
|
|||
/// </summary>
|
|||
public ClaimsPrincipal Principal { get; set; } |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents an event called when processing a challenge response.
|
|||
/// </summary>
|
|||
public class ProcessChallengeContext : BaseValidatingContext |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="ProcessChallengeContext"/> class.
|
|||
/// </summary>
|
|||
public ProcessChallengeContext([NotNull] OpenIddictValidationTransaction transaction) |
|||
: base(transaction) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,83 @@ |
|||
/* |
|||
* 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.Linq; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection.Extensions; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Validation; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlers; |
|||
|
|||
namespace Microsoft.Extensions.DependencyInjection |
|||
{ |
|||
/// <summary>
|
|||
/// Exposes extensions allowing to register the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public static class OpenIddictValidationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Registers the OpenIddict token validation services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictValidationBuilder"/>.</returns>
|
|||
public static OpenIddictValidationBuilder AddValidation([NotNull] this OpenIddictBuilder builder) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
builder.Services.AddLogging(); |
|||
builder.Services.AddOptions(); |
|||
|
|||
builder.Services.TryAddScoped<IOpenIddictValidationProvider, OpenIddictValidationProvider>(); |
|||
|
|||
// Register the built-in validation event handlers used by the OpenIddict validation components.
|
|||
// Note: the order used here is not important, as the actual order is set in the options.
|
|||
builder.Services.TryAdd(DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
|||
|
|||
// Register the built-in filters used by the default OpenIddict validation event handlers.
|
|||
builder.Services.TryAddSingleton<RequireAuthorizationValidationEnabled>(); |
|||
builder.Services.TryAddSingleton<RequireReferenceTokensDisabled>(); |
|||
builder.Services.TryAddSingleton<RequireReferenceTokensEnabled>(); |
|||
|
|||
// Note: TryAddEnumerable() is used here to ensure the initializer is registered only once.
|
|||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton< |
|||
IPostConfigureOptions<OpenIddictValidationOptions>, OpenIddictValidationConfiguration>()); |
|||
|
|||
return new OpenIddictValidationBuilder(builder.Services); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Registers the OpenIddict token validation services in the DI container.
|
|||
/// </summary>
|
|||
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
|||
/// <param name="configuration">The configuration delegate used to configure the validation services.</param>
|
|||
/// <remarks>This extension can be safely called multiple times.</remarks>
|
|||
/// <returns>The <see cref="OpenIddictBuilder"/>.</returns>
|
|||
public static OpenIddictBuilder AddValidation( |
|||
[NotNull] this OpenIddictBuilder builder, |
|||
[NotNull] Action<OpenIddictValidationBuilder> configuration) |
|||
{ |
|||
if (builder == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(builder)); |
|||
} |
|||
|
|||
if (configuration == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(configuration)); |
|||
} |
|||
|
|||
configuration(builder.AddValidation()); |
|||
|
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,39 @@ |
|||
/* |
|||
* 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.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a handler able to process <typeparamref name="TContext"/> events.
|
|||
/// </summary>
|
|||
/// <typeparam name="TContext">The type of the events handled by this instance.</typeparam>
|
|||
public class OpenIddictValidationHandler<TContext> : IOpenIddictValidationHandler<TContext> where TContext : BaseContext |
|||
{ |
|||
private readonly Func<TContext, ValueTask> _handler; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new event using the specified handler delegate.
|
|||
/// </summary>
|
|||
/// <param name="handler">The event handler delegate.</param>
|
|||
public OpenIddictValidationHandler([NotNull] Func<TContext, ValueTask> handler) |
|||
=> _handler = handler ?? throw new ArgumentNullException(nameof(handler)); |
|||
|
|||
/// <summary>
|
|||
/// Processes the event.
|
|||
/// </summary>
|
|||
/// <param name="context">The event to process.</param>
|
|||
/// <returns>
|
|||
/// A <see cref="ValueTask"/> that can be used to monitor the asynchronous operation.
|
|||
/// </returns>
|
|||
public ValueTask HandleAsync([NotNull] TContext context) |
|||
=> _handler(context ?? throw new ArgumentNullException(nameof(context))); |
|||
} |
|||
} |
|||
@ -0,0 +1,199 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Diagnostics; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents an immutable descriptor of an OpenIddict validation event handler.
|
|||
/// </summary>
|
|||
[DebuggerDisplay("{ServiceDescriptor?.ServiceType}")] |
|||
public class OpenIddictValidationHandlerDescriptor |
|||
{ |
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationHandlerDescriptor"/> class.
|
|||
/// </summary>
|
|||
private OpenIddictValidationHandlerDescriptor() { } |
|||
|
|||
/// <summary>
|
|||
/// Gets the context type associated with the event.
|
|||
/// </summary>
|
|||
public Type ContextType { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the list of filters responsible of excluding the handler
|
|||
/// from the activated handlers if it doesn't meet the criteria.
|
|||
/// </summary>
|
|||
public ImmutableArray<Type> FilterTypes { get; private set; } = ImmutableArray.Create<Type>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the order assigned to the handler.
|
|||
/// </summary>
|
|||
public int Order { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the service descriptor associated with the handler.
|
|||
/// </summary>
|
|||
public ServiceDescriptor ServiceDescriptor { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// Creates a builder allowing to initialize an immutable descriptor.
|
|||
/// </summary>
|
|||
/// <typeparam name="TContext">The event context type.</typeparam>
|
|||
/// <returns>A new descriptor builder.</returns>
|
|||
public static Builder<TContext> CreateBuilder<TContext>() where TContext : BaseContext |
|||
=> new Builder<TContext>(); |
|||
|
|||
/// <summary>
|
|||
/// Contains methods allowing to build a descriptor instance.
|
|||
/// </summary>
|
|||
/// <typeparam name="TContext">The event context type.</typeparam>
|
|||
public class Builder<TContext> where TContext : BaseContext |
|||
{ |
|||
private ServiceDescriptor _descriptor; |
|||
private readonly List<Type> _filterTypes = new List<Type>(); |
|||
private int _order; |
|||
|
|||
/// <summary>
|
|||
/// Adds the type of a handler filter to the filters list.
|
|||
/// </summary>
|
|||
/// <param name="type">The event handler filter type.</param>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> AddFilter([NotNull] Type type) |
|||
{ |
|||
if (type == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(type)); |
|||
} |
|||
|
|||
if (!typeof(IOpenIddictValidationHandlerFilter<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) |
|||
{ |
|||
throw new InvalidOperationException("The specified service type is not valid."); |
|||
} |
|||
|
|||
_filterTypes.Add(type); |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Adds the type of a handler filter to the filters list.
|
|||
/// </summary>
|
|||
/// <typeparam name="TFilter">The event handler filter type.</typeparam>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> AddFilter<TFilter>() |
|||
where TFilter : IOpenIddictValidationHandlerFilter<TContext> |
|||
=> AddFilter(typeof(TFilter)); |
|||
|
|||
/// <summary>
|
|||
/// Sets the service descriptor.
|
|||
/// </summary>
|
|||
/// <param name="descriptor">The service descriptor.</param>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> SetServiceDescriptor([NotNull] ServiceDescriptor descriptor) |
|||
{ |
|||
if (descriptor == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(descriptor)); |
|||
} |
|||
|
|||
var type = descriptor.ServiceType; |
|||
if (!typeof(IOpenIddictValidationHandler<>).MakeGenericType(typeof(TContext)).IsAssignableFrom(type)) |
|||
{ |
|||
throw new InvalidOperationException("The specified service type is not valid."); |
|||
} |
|||
|
|||
_descriptor = descriptor; |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Sets the order in which the event handler will be invoked.
|
|||
/// </summary>
|
|||
/// <param name="order">The handler order.</param>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> SetOrder(int order) |
|||
{ |
|||
_order = order; |
|||
|
|||
return this; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures the descriptor to use the specified inline handler.
|
|||
/// </summary>
|
|||
/// <param name="handler">The handler instance.</param>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> UseInlineHandler([NotNull] Func<TContext, ValueTask> handler) |
|||
{ |
|||
if (handler == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(handler)); |
|||
} |
|||
|
|||
return UseSingletonHandler(new OpenIddictValidationHandler<TContext>(handler)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Configures the descriptor to use the specified scoped handler.
|
|||
/// </summary>
|
|||
/// <typeparam name="THandler">The handler type.</typeparam>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> UseScopedHandler<THandler>() |
|||
where THandler : IOpenIddictValidationHandler<TContext> |
|||
=> SetServiceDescriptor(new ServiceDescriptor( |
|||
typeof(THandler), typeof(THandler), ServiceLifetime.Scoped)); |
|||
|
|||
/// <summary>
|
|||
/// Configures the descriptor to use the specified singleton handler.
|
|||
/// </summary>
|
|||
/// <typeparam name="THandler">The handler type.</typeparam>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> UseSingletonHandler<THandler>() |
|||
where THandler : IOpenIddictValidationHandler<TContext> |
|||
=> SetServiceDescriptor(new ServiceDescriptor( |
|||
typeof(THandler), typeof(THandler), ServiceLifetime.Singleton)); |
|||
|
|||
/// <summary>
|
|||
/// Configures the descriptor to use the specified singleton handler.
|
|||
/// </summary>
|
|||
/// <typeparam name="THandler">The handler type.</typeparam>
|
|||
/// <param name="handler">The handler instance.</param>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public Builder<TContext> UseSingletonHandler<THandler>([NotNull] THandler handler) |
|||
where THandler : IOpenIddictValidationHandler<TContext> |
|||
{ |
|||
if (handler == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(handler)); |
|||
} |
|||
|
|||
return SetServiceDescriptor(new ServiceDescriptor(typeof(THandler), handler)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Build a new descriptor instance, based on the parameters that were previously set.
|
|||
/// </summary>
|
|||
/// <returns>The builder instance, so that calls can be easily chained.</returns>
|
|||
public OpenIddictValidationHandlerDescriptor Build() => new OpenIddictValidationHandlerDescriptor |
|||
{ |
|||
ContextType = typeof(TContext), |
|||
FilterTypes = _filterTypes.ToImmutableArray(), |
|||
Order = _order, |
|||
ServiceDescriptor = _descriptor ?? throw new InvalidOperationException("No service descriptor was set.") |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,66 @@ |
|||
/* |
|||
* 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.ComponentModel; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Advanced)] |
|||
public static class OpenIddictValidationHandlerFilters |
|||
{ |
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if authorization validation was not enabled.
|
|||
/// </summary>
|
|||
public class RequireAuthorizationValidationEnabled : IOpenIddictValidationHandlerFilter<BaseContext> |
|||
{ |
|||
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new ValueTask<bool>(context.Options.EnableAuthorizationValidation); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if reference tokens are enabled.
|
|||
/// </summary>
|
|||
public class RequireReferenceTokensDisabled : IOpenIddictValidationHandlerFilter<BaseContext> |
|||
{ |
|||
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new ValueTask<bool>(!context.Options.UseReferenceTokens); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Represents a filter that excludes the associated handlers if reference tokens are disabled.
|
|||
/// </summary>
|
|||
public class RequireReferenceTokensEnabled : IOpenIddictValidationHandlerFilter<BaseContext> |
|||
{ |
|||
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
return new ValueTask<bool>(context.Options.UseReferenceTokens); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,533 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.ComponentModel; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
using static OpenIddict.Validation.OpenIddictValidationHandlerFilters; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
[EditorBrowsable(EditorBrowsableState.Never)] |
|||
public static partial class OpenIddictValidationHandlers |
|||
{ |
|||
public static ImmutableArray<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
|||
/* |
|||
* Authentication processing: |
|||
*/ |
|||
ValidateTokenValidationParameters.Descriptor, |
|||
ValidateAccessTokenParameter.Descriptor, |
|||
ValidateReferenceToken.Descriptor, |
|||
ValidateSelfContainedToken.Descriptor, |
|||
ValidatePrincipal.Descriptor, |
|||
ValidateExpirationDate.Descriptor, |
|||
ValidateAudience.Descriptor, |
|||
ValidateAuthorizationEntry.Descriptor, |
|||
|
|||
/* |
|||
* Challenge processing: |
|||
*/ |
|||
AttachDefaultChallengeError.Descriptor); |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of ensuring the token validation parameters are populated.
|
|||
/// </summary>
|
|||
public class ValidateTokenValidationParameters : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidateTokenValidationParameters>() |
|||
.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// Note: at this stage, throw an exception if the token validation parameters cannot be found.
|
|||
var parameters = context.TokenValidationParameters ?? context.Options.TokenValidationParameters; |
|||
if (parameters == null) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The token validation parameters cannot be retrieved.") |
|||
.Append("To register the default client, reference the 'OpenIddict.Validation.SystemNetHttp' package ") |
|||
.AppendLine("and call 'services.AddOpenIddict().AddValidation().UseSystemNetHttp()'.") |
|||
.Append("Alternatively, you can manually provide the token validation parameters ") |
|||
.Append("by calling 'services.AddOpenIddict().AddValidation().SetTokenValidationParameters()'.") |
|||
.ToString()); |
|||
} |
|||
|
|||
// Clone the token validation parameters before mutating them to ensure the
|
|||
// shared token validation parameters registered as options are not modified.
|
|||
parameters = parameters.Clone(); |
|||
parameters.NameClaimType = Claims.Name; |
|||
parameters.PropertyBag = new Dictionary<string, object> { [Claims.Private.TokenUsage] = TokenUsages.AccessToken }; |
|||
parameters.RoleClaimType = Claims.Role; |
|||
parameters.TokenDecryptionKeys = context.Options.EncryptionCredentials.Select(credentials => credentials.Key); |
|||
parameters.ValidIssuer = context.Issuer?.AbsoluteUri; |
|||
parameters.ValidateAudience = false; |
|||
parameters.ValidateLifetime = false; |
|||
|
|||
context.TokenValidationParameters = parameters; |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of validating the access token resolved from the current request.
|
|||
/// </summary>
|
|||
public class ValidateAccessTokenParameter : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidateAccessTokenParameter>() |
|||
.SetOrder(ValidateTokenValidationParameters.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(context.Request.AccessToken)) |
|||
{ |
|||
context.Logger.LogError("The request was rejected because the access token was missing."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The access token is missing."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands that use an invalid reference token.
|
|||
/// Note: this handler is not used when the degraded mode is enabled.
|
|||
/// </summary>
|
|||
public class ValidateReferenceToken : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IOpenIddictTokenManager _tokenManager; |
|||
|
|||
public ValidateReferenceToken() => throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The core services must be registered when enabling reference tokens support.") |
|||
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") |
|||
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") |
|||
.ToString()); |
|||
|
|||
public ValidateReferenceToken([NotNull] IOpenIddictTokenManager tokenManager) |
|||
=> _tokenManager = tokenManager; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.AddFilter<RequireReferenceTokensEnabled>() |
|||
.UseScopedHandler<ValidateReferenceToken>() |
|||
.SetOrder(ValidateAccessTokenParameter.Descriptor.Order + 1_000) |
|||
.Build(); |
|||
|
|||
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If a principal was already attached, don't overwrite it.
|
|||
if (context.Principal != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the reference token cannot be found, return a generic error.
|
|||
var token = await _tokenManager.FindByReferenceIdAsync(context.Request.AccessToken); |
|||
if (token == null || !string.Equals(await _tokenManager.GetTypeAsync(token), |
|||
TokenUsages.AccessToken, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified token is not valid."); |
|||
|
|||
return; |
|||
} |
|||
|
|||
var payload = await _tokenManager.GetPayloadAsync(token); |
|||
if (string.IsNullOrEmpty(payload)) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The payload associated with a reference token cannot be retrieved.") |
|||
.Append("This may indicate that the token entry was corrupted.") |
|||
.ToString()); |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
if (!context.Options.SecurityTokenHandler.CanReadToken(payload)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
var result = await context.Options.SecurityTokenHandler.ValidateTokenStringAsync( |
|||
payload, context.TokenValidationParameters); |
|||
|
|||
if (result.ClaimsIdentity == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Attach the principal extracted from the authorization code to the parent event context
|
|||
// and restore the creation/expiration dates/identifiers from the token entry metadata.
|
|||
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity) |
|||
.SetCreationDate(await _tokenManager.GetCreationDateAsync(token)) |
|||
.SetExpirationDate(await _tokenManager.GetExpirationDateAsync(token)) |
|||
.SetInternalAuthorizationId(await _tokenManager.GetAuthorizationIdAsync(token)) |
|||
.SetInternalTokenId(await _tokenManager.GetIdAsync(token)) |
|||
.SetClaim(Claims.Private.TokenUsage, await _tokenManager.GetTypeAsync(token)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands that specify an invalid self-contained token.
|
|||
/// </summary>
|
|||
public class ValidateSelfContainedToken : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidateSelfContainedToken>() |
|||
.SetOrder(ValidateReferenceToken.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If a principal was already attached, don't overwrite it.
|
|||
if (context.Principal != null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
if (!context.Options.SecurityTokenHandler.CanReadToken(context.Request.AccessToken)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// If the token cannot be validated, don't return an error to allow another handle to validate it.
|
|||
var result = await context.Options.SecurityTokenHandler.ValidateTokenStringAsync( |
|||
context.Request.AccessToken, context.TokenValidationParameters); |
|||
|
|||
if (result.ClaimsIdentity == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Attach the principal extracted from the token to the parent event context.
|
|||
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands for which no valid principal was resolved.
|
|||
/// </summary>
|
|||
public class ValidatePrincipal : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidatePrincipal>() |
|||
.SetOrder(ValidateSelfContainedToken.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (context.Principal == null) |
|||
{ |
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified token is not valid."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands containing expired access tokens.
|
|||
/// </summary>
|
|||
public class ValidateExpirationDate : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidateExpirationDate>() |
|||
.SetOrder(ValidatePrincipal.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
var date = context.Principal.GetExpirationDate(); |
|||
if (date.HasValue && date.Value < DateTimeOffset.UtcNow) |
|||
{ |
|||
context.Logger.LogError("The request was rejected because the access token was expired."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified access token is no longer valid."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of rejecting authentication demands containing
|
|||
/// access tokens that were issued to be used by another audience/resource server.
|
|||
/// </summary>
|
|||
public class ValidateAudience : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.UseSingletonHandler<ValidateAudience>() |
|||
.SetOrder(ValidateExpirationDate.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] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
// If no explicit audience has been configured,
|
|||
// skip the default audience validation.
|
|||
if (context.Options.Audiences.Count == 0) |
|||
{ |
|||
return default; |
|||
} |
|||
|
|||
// If the access token doesn't have any audience attached, return an error.
|
|||
if (!context.Principal.HasAudience()) |
|||
{ |
|||
context.Logger.LogError("The request was rejected because the access token had no audience attached."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified access token doesn't contain any audience."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
// If the access token doesn't include any registered audience, return an error.
|
|||
if (context.Principal.GetAudiences().Intersect(context.Options.Audiences).IsEmpty) |
|||
{ |
|||
context.Logger.LogError("The request was rejected because the access token had no valid audience."); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The specified access token cannot be used with this resource server."); |
|||
|
|||
return default; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of authentication demands a token whose
|
|||
/// associated authorization entry is no longer valid (e.g was revoked).
|
|||
/// Note: this handler is not used when the degraded mode is enabled.
|
|||
/// </summary>
|
|||
public class ValidateAuthorizationEntry : IOpenIddictValidationHandler<ProcessAuthenticationContext> |
|||
{ |
|||
private readonly IOpenIddictAuthorizationManager _authorizationManager; |
|||
|
|||
public ValidateAuthorizationEntry() => throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine("The core services must be registered when enabling reference tokens support.") |
|||
.Append("To register the OpenIddict core services, reference the 'OpenIddict.Core' package ") |
|||
.AppendLine("and call 'services.AddOpenIddict().AddCore()' from 'ConfigureServices'.") |
|||
.ToString()); |
|||
|
|||
public ValidateAuthorizationEntry([NotNull] IOpenIddictAuthorizationManager authorizationManager) |
|||
=> _authorizationManager = authorizationManager; |
|||
|
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>() |
|||
.AddFilter<RequireAuthorizationValidationEnabled>() |
|||
.UseScopedHandler<ValidateAuthorizationEntry>() |
|||
.SetOrder(ValidateAudience.Descriptor.Order + 1_000) |
|||
.Build(); |
|||
|
|||
public async ValueTask HandleAsync([NotNull] ProcessAuthenticationContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
var identifier = context.Principal.GetInternalAuthorizationId(); |
|||
if (string.IsNullOrEmpty(identifier)) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var authorization = await _authorizationManager.FindByIdAsync(identifier); |
|||
if (authorization == null || !await _authorizationManager.IsValidAsync(authorization)) |
|||
{ |
|||
context.Logger.LogError("The authorization '{Identifier}' was no longer valid.", identifier); |
|||
|
|||
context.Reject( |
|||
error: Errors.InvalidToken, |
|||
description: "The authorization associated with the token is no longer valid."); |
|||
|
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Contains the logic responsible of ensuring that the challenge response contains an appropriate error.
|
|||
/// </summary>
|
|||
public class AttachDefaultChallengeError : IOpenIddictValidationHandler<ProcessChallengeContext> |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the default descriptor definition assigned to this handler.
|
|||
/// </summary>
|
|||
public static OpenIddictValidationHandlerDescriptor Descriptor { get; } |
|||
= OpenIddictValidationHandlerDescriptor.CreateBuilder<ProcessChallengeContext>() |
|||
.UseSingletonHandler<AttachDefaultChallengeError>() |
|||
.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] ProcessChallengeContext context) |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(context.Response.Error)) |
|||
{ |
|||
context.Response.Error = Errors.InvalidToken; |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(context.Response.ErrorDescription)) |
|||
{ |
|||
context.Response.ErrorDescription = "The access token is not valid."; |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Provides various settings needed to configure the OpenIddict validation handler.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationOptions |
|||
{ |
|||
/// <summary>
|
|||
/// Gets the list of credentials used to encrypt the tokens issued by the
|
|||
/// OpenIddict validation services. Note: only symmetric credentials are supported.
|
|||
/// </summary>
|
|||
public IList<EncryptingCredentials> EncryptionCredentials { get; } = new List<EncryptingCredentials>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the security token handler used to protect and unprotect tokens.
|
|||
/// </summary>
|
|||
public OpenIddictValidationTokenHandler SecurityTokenHandler { get; set; } = new OpenIddictValidationTokenHandler |
|||
{ |
|||
SetDefaultTimesOnTokenCreation = false |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Gets the list of the user-defined/custom handlers responsible of processing the OpenIddict validation requests.
|
|||
/// Note: the handlers added to this list must be also registered in the DI container using an appropriate lifetime.
|
|||
/// </summary>
|
|||
public IList<OpenIddictValidationHandlerDescriptor> CustomHandlers { get; } = |
|||
new List<OpenIddictValidationHandlerDescriptor>(); |
|||
|
|||
/// <summary>
|
|||
/// Gets the list of the built-in handlers responsible of processing the OpenIddict validation requests
|
|||
/// </summary>
|
|||
public IList<OpenIddictValidationHandlerDescriptor> DefaultHandlers { get; } = |
|||
new List<OpenIddictValidationHandlerDescriptor>(OpenIddictValidationHandlers.DefaultHandlers); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether a database call is made
|
|||
/// to validate the authorization associated with the received tokens.
|
|||
/// </summary>
|
|||
public bool EnableAuthorizationValidation { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a boolean indicating whether reference tokens should be used.
|
|||
/// When set to <c>true</c>, authorization codes, access tokens and refresh tokens
|
|||
/// are stored as ciphertext in the database and a crypto-secure random identifier
|
|||
/// is returned to the client application. Enabling this option is useful
|
|||
/// to keep track of all the issued tokens, when storing a very large number
|
|||
/// of claims in the authorization codes, access tokens and refresh tokens
|
|||
/// or when immediate revocation of reference access tokens is desired.
|
|||
/// Note: this option cannot be used when configuring JWT as the access token format.
|
|||
/// </summary>
|
|||
public bool UseReferenceTokens { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the absolute URL of the OAuth 2.0/OpenID Connect server.
|
|||
/// </summary>
|
|||
public Uri Issuer { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the URL of the OAuth 2.0/OpenID Connect server discovery endpoint.
|
|||
/// When the URL is relative, <see cref="Issuer"/> must be set and absolute.
|
|||
/// </summary>
|
|||
public Uri MetadataAddress { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the intended audiences of this resource server.
|
|||
/// Setting this property is recommended when the authorization
|
|||
/// server issues access tokens for multiple distinct resource servers.
|
|||
/// </summary>
|
|||
public ISet<string> Audiences { get; } = new HashSet<string>(StringComparer.Ordinal); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the token validation parameters used by the OpenIddict validation services.
|
|||
/// </summary>
|
|||
public TokenValidationParameters TokenValidationParameters { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,132 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using JetBrains.Annotations; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using static OpenIddict.Validation.OpenIddictValidationEvents; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
public class OpenIddictValidationProvider : IOpenIddictValidationProvider |
|||
{ |
|||
private readonly ILogger<OpenIddictValidationProvider> _logger; |
|||
private readonly IOptionsMonitor<OpenIddictValidationOptions> _options; |
|||
private readonly IServiceProvider _provider; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new instance of the <see cref="OpenIddictValidationProvider"/> class.
|
|||
/// </summary>
|
|||
public OpenIddictValidationProvider( |
|||
[NotNull] ILogger<OpenIddictValidationProvider> logger, |
|||
[NotNull] IOptionsMonitor<OpenIddictValidationOptions> options, |
|||
[NotNull] IServiceProvider provider) |
|||
{ |
|||
_logger = logger; |
|||
_options = options; |
|||
_provider = provider; |
|||
} |
|||
|
|||
public ValueTask<OpenIddictValidationTransaction> CreateTransactionAsync() |
|||
=> new ValueTask<OpenIddictValidationTransaction>(new OpenIddictValidationTransaction |
|||
{ |
|||
Issuer = _options.CurrentValue.Issuer, |
|||
Logger = _logger, |
|||
Options = _options.CurrentValue |
|||
}); |
|||
|
|||
public async ValueTask DispatchAsync<TContext>([NotNull] TContext context) where TContext : BaseContext |
|||
{ |
|||
if (context == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(context)); |
|||
} |
|||
|
|||
await foreach (var handler in GetHandlersAsync()) |
|||
{ |
|||
await handler.HandleAsync(context); |
|||
|
|||
switch (context) |
|||
{ |
|||
case BaseRequestContext notification when notification.IsRequestHandled: |
|||
_logger.LogDebug("The request was handled in user code."); |
|||
return; |
|||
|
|||
case BaseRequestContext notification when notification.IsRequestSkipped: |
|||
_logger.LogDebug("The default request handling was skipped from user code."); |
|||
return; |
|||
|
|||
case BaseValidatingContext notification when notification.IsRejected: |
|||
_logger.LogDebug("The request was rejected in user code."); |
|||
return; |
|||
|
|||
default: continue; |
|||
} |
|||
} |
|||
|
|||
async IAsyncEnumerable<IOpenIddictValidationHandler<TContext>> GetHandlersAsync() |
|||
{ |
|||
var descriptors = new List<OpenIddictValidationHandlerDescriptor>( |
|||
capacity: _options.CurrentValue.CustomHandlers.Count + |
|||
_options.CurrentValue.DefaultHandlers.Count); |
|||
|
|||
descriptors.AddRange(_options.CurrentValue.CustomHandlers); |
|||
descriptors.AddRange(_options.CurrentValue.DefaultHandlers); |
|||
|
|||
descriptors.Sort((left, right) => left.Order.CompareTo(right.Order)); |
|||
|
|||
for (var index = 0; index < descriptors.Count; index++) |
|||
{ |
|||
var descriptor = descriptors[index]; |
|||
if (descriptor.ContextType != typeof(TContext) || !await IsActiveAsync(descriptor)) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var handler = descriptor.ServiceDescriptor.ImplementationInstance != null ? |
|||
descriptor.ServiceDescriptor.ImplementationInstance as IOpenIddictValidationHandler<TContext> : |
|||
_provider.GetService(descriptor.ServiceDescriptor.ServiceType) as IOpenIddictValidationHandler<TContext>; |
|||
|
|||
if (handler == null) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine($"The event handler of type '{descriptor.ServiceDescriptor.ServiceType}' couldn't be resolved.") |
|||
.AppendLine("This may indicate that it was not properly registered in the dependency injection container.") |
|||
.Append("To register an event handler, use 'services.AddOpenIddict().AddValidation().AddEventHandler()'.") |
|||
.ToString()); |
|||
} |
|||
|
|||
yield return handler; |
|||
} |
|||
} |
|||
|
|||
async ValueTask<bool> IsActiveAsync(OpenIddictValidationHandlerDescriptor descriptor) |
|||
{ |
|||
for (var index = 0; index < descriptor.FilterTypes.Length; index++) |
|||
{ |
|||
if (!(_provider.GetService(descriptor.FilterTypes[index]) is IOpenIddictValidationHandlerFilter<TContext> filter)) |
|||
{ |
|||
throw new InvalidOperationException(new StringBuilder() |
|||
.AppendLine($"The event handler filter of type '{descriptor.FilterTypes[index]}' couldn't be resolved.") |
|||
.AppendLine("This may indicate that it was not properly registered in the dependency injection container.") |
|||
.ToString()); |
|||
} |
|||
|
|||
if (!await filter.IsActiveAsync(context)) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,95 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.IdentityModel.JsonWebTokens; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Abstractions; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
public class OpenIddictValidationTokenHandler : JsonWebTokenHandler |
|||
{ |
|||
public ValueTask<TokenValidationResult> ValidateTokenStringAsync(string token, TokenValidationParameters parameters) |
|||
{ |
|||
if (parameters == null) |
|||
{ |
|||
throw new ArgumentNullException(nameof(parameters)); |
|||
} |
|||
|
|||
if (parameters.PropertyBag == null) |
|||
{ |
|||
throw new InvalidOperationException("The property bag cannot be null."); |
|||
} |
|||
|
|||
if (!parameters.PropertyBag.TryGetValue(Claims.Private.TokenUsage, out var type) || string.IsNullOrEmpty((string) type)) |
|||
{ |
|||
throw new InvalidOperationException("The token usage cannot be null or empty."); |
|||
} |
|||
|
|||
if (!CanReadToken(token)) |
|||
{ |
|||
return new ValueTask<TokenValidationResult>(new TokenValidationResult |
|||
{ |
|||
Exception = new SecurityTokenException("The token was not compatible with the JWT format."), |
|||
IsValid = false |
|||
}); |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var result = base.ValidateToken(token, parameters); |
|||
if (result == null || !result.IsValid) |
|||
{ |
|||
return new ValueTask<TokenValidationResult>(new TokenValidationResult |
|||
{ |
|||
Exception = result?.Exception, |
|||
IsValid = false |
|||
}); |
|||
} |
|||
|
|||
var assertion = ((JsonWebToken) result.SecurityToken)?.InnerToken ?? (JsonWebToken) result.SecurityToken; |
|||
|
|||
if (!assertion.TryGetPayloadValue(Claims.Private.TokenUsage, out string usage) || |
|||
!string.Equals(usage, (string) type, StringComparison.OrdinalIgnoreCase)) |
|||
{ |
|||
return new ValueTask<TokenValidationResult>(new TokenValidationResult |
|||
{ |
|||
Exception = new SecurityTokenException("The token usage associated to the token does not match the expected type."), |
|||
IsValid = false |
|||
}); |
|||
} |
|||
|
|||
// Restore the claim destinations from the special oi_cl_dstn claim (represented as a dictionary/JSON object).
|
|||
if (assertion.TryGetPayloadValue(Claims.Private.ClaimDestinations, out IDictionary<string, string[]> definitions)) |
|||
{ |
|||
foreach (var definition in definitions) |
|||
{ |
|||
foreach (var claim in result.ClaimsIdentity.Claims.Where(claim => claim.Type == definition.Key)) |
|||
{ |
|||
claim.SetDestinations(definition.Value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return new ValueTask<TokenValidationResult>(result); |
|||
} |
|||
|
|||
catch (Exception exception) |
|||
{ |
|||
return new ValueTask<TokenValidationResult>(new TokenValidationResult |
|||
{ |
|||
Exception = exception, |
|||
IsValid = false |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
/* |
|||
* 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.Generic; |
|||
using Microsoft.Extensions.Logging; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace OpenIddict.Validation |
|||
{ |
|||
/// <summary>
|
|||
/// Represents the context associated with an OpenID Connect validation request.
|
|||
/// </summary>
|
|||
public class OpenIddictValidationTransaction |
|||
{ |
|||
/// <summary>
|
|||
/// Gets or sets the type of the endpoint processing the current request.
|
|||
/// </summary>
|
|||
public OpenIddictValidationEndpointType EndpointType { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the issuer address associated with the current transaction, if available.
|
|||
/// </summary>
|
|||
public Uri Issuer { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the logger associated with the current request.
|
|||
/// </summary>
|
|||
public ILogger Logger { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the options associated with the current request.
|
|||
/// </summary>
|
|||
public OpenIddictValidationOptions Options { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the additional properties associated with the current request.
|
|||
/// </summary>
|
|||
public IDictionary<string, object> Properties { get; } |
|||
= new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current OpenID Connect request.
|
|||
/// </summary>
|
|||
public OpenIddictRequest Request { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the current OpenID Connect response being returned.
|
|||
/// </summary>
|
|||
public OpenIddictResponse Response { get; set; } |
|||
} |
|||
} |
|||
Loading…
Reference in new issue