Browse Source

Reintroduce the introspection endpoint

pull/785/head
Kévin Chalet 7 years ago
committed by GitHub
parent
commit
94e71b530b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      eng/Versions.props
  2. 2
      samples/Mvc.Client/Startup.cs
  3. 26
      src/OpenIddict.Abstractions/OpenIddictConstants.cs
  4. 79
      src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs
  5. 21
      src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs
  6. 97
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs
  7. 1
      src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs
  8. 32
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs
  9. 33
      src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs
  10. 97
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs
  11. 1
      src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs
  12. 3
      src/OpenIddict.Server/OpenIddictServerBuilder.cs
  13. 11
      src/OpenIddict.Server/OpenIddictServerConfiguration.cs
  14. 18
      src/OpenIddict.Server/OpenIddictServerConstants.cs
  15. 17
      src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs
  16. 1
      src/OpenIddict.Server/OpenIddictServerExtensions.cs
  17. 16
      src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs
  18. 1
      src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs
  19. 10
      src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs
  20. 1231
      src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs
  21. 27
      src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs
  22. 5
      src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs
  23. 5
      src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs
  24. 7
      src/OpenIddict.Server/OpenIddictServerHandlers.cs
  25. 4
      src/OpenIddict.Server/OpenIddictServerOptions.cs

2
eng/Versions.props

@ -17,7 +17,7 @@
<JetBrainsVersion>2019.1.3</JetBrainsVersion>
<JsonNetVersion>12.0.2</JsonNetVersion>
<JsonNetBsonVersion>1.0.1</JsonNetBsonVersion>
<IdentityModelVersion>6.2.0-preview-60806030202</IdentityModelVersion>
<IdentityModelVersion>6.2.0-preview-60906195846</IdentityModelVersion>
<ImmutableCollectionsVersion>1.5.0</ImmutableCollectionsVersion>
<LinqAsyncVersion>4.0.0-preview.6.build.801</LinqAsyncVersion>
<MongoDbVersion>2.9.0</MongoDbVersion>

2
samples/Mvc.Client/Startup.cs

@ -56,6 +56,8 @@ namespace Mvc.Client
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
options.AccessDeniedPath = "/";
});
services.AddMvc();

26
src/OpenIddict.Abstractions/OpenIddictConstants.cs

@ -75,6 +75,7 @@ namespace OpenIddict.Abstractions
public const string StreetAddress = "street_address";
public const string Subject = "sub";
public const string TokenType = "token_type";
public const string TokenUsage = "token_usage";
public const string UpdatedAt = "updated_at";
public const string Username = "username";
public const string Website = "website";
@ -93,7 +94,8 @@ namespace OpenIddict.Abstractions
public const string CodeChallenge = "oi_cd_chlg";
public const string CodeChallengeMethod = "oi_cd_chlg_meth";
public const string IdentityTokenLifetime = "oi_idt_lft";
public const string OriginalRedirectUri = "oi_reduri";
public const string Nonce = "oi_nce";
public const string RedirectUri = "oi_reduri";
public const string RefreshTokenLifetime = "oi_reft_lft";
public const string TokenUsage = "oi_tkn_use";
}
@ -310,29 +312,7 @@ namespace OpenIddict.Abstractions
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 Destinations = ".destinations";
public const string Error = ".error";
public const string ErrorDescription = ".error_description";
public const string ErrorUri = ".error_uri";
public const string Expires = ".expires";
public const string IdentityTokenLifetime = ".identity_token_lifetime";
public const string Issued = ".issued";
public const string Nonce = ".nonce";
public const string OriginalPrincipal = ".original_principal";
public const string OriginalRedirectUri = ".original_redirect_uri";
public const string PostLogoutRedirectUri = ".post_logout_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 const string ValidatedRedirectUri = ".validated_redirect_uri";
}
public static class ResponseModes

79
src/OpenIddict.Abstractions/Primitives/OpenIddictExtensions.cs

@ -1260,6 +1260,85 @@ namespace OpenIddict.Abstractions
return principal.GetClaim(Claims.JwtId);
}
/// <summary>
/// Gets the token usage associated with the claims principal.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns>The token usage or <c>null</c> if the claim cannot be found.</returns>
public static string GetTokenUsage([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return principal.GetClaim(Claims.Private.TokenUsage);
}
/// <summary>
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an access token.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal corresponds to an access token.</returns>
public static bool IsAccessToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.AccessToken, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an access token.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal corresponds to an authorization code.</returns>
public static bool IsAuthorizationCode([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.AuthorizationCode, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets a boolean value indicating whether the
/// claims principal corresponds to an identity token.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal corresponds to an identity token.</returns>
public static bool IsIdentityToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.IdToken, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Gets a boolean value indicating whether the
/// claims principal corresponds to a refresh token.
/// </summary>
/// <param name="principal">The claims principal.</param>
/// <returns><c>true</c> if the principal corresponds to a refresh token.</returns>
public static bool IsRefreshToken([NotNull] this ClaimsPrincipal principal)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
return string.Equals(principal.GetTokenUsage(), TokenUsages.RefreshToken, StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Determines whether the claims principal contains at least one audience.
/// </summary>

21
src/OpenIddict.Abstractions/Primitives/OpenIddictMessage.cs

@ -123,12 +123,13 @@ namespace OpenIddict.Abstractions
// not be present more than once but derived specifications like the
// token exchange RFC deliberately allow specifying multiple resource
// parameters with the same name to represent a multi-valued parameter.
switch (parameter.Value?.Length ?? 0)
AddParameter(parameter.Key, parameter.Value?.Length switch
{
case 0: AddParameter(parameter.Key, default); break;
case 1: AddParameter(parameter.Key, parameter.Value[0]); break;
default: AddParameter(parameter.Key, parameter.Value); break;
}
null => default,
0 => default,
1 => new OpenIddictParameter(parameter.Value[0]),
_ => new OpenIddictParameter(parameter.Value)
});
}
}
@ -154,12 +155,12 @@ namespace OpenIddict.Abstractions
// not be present more than once but derived specifications like the
// token exchange RFC deliberately allow specifying multiple resource
// parameters with the same name to represent a multi-valued parameter.
switch (parameter.Value.Count)
AddParameter(parameter.Key, parameter.Value.Count switch
{
case 0: AddParameter(parameter.Key, default); break;
case 1: AddParameter(parameter.Key, parameter.Value[0]); break;
default: AddParameter(parameter.Key, parameter.Value.ToArray()); break;
}
0 => default,
1 => new OpenIddictParameter(parameter.Value[0]),
_ => new OpenIddictParameter(parameter.Value.ToArray())
});
}
}

97
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.Introspection.cs

@ -0,0 +1,97 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore;
using static OpenIddict.Server.AspNetCore.OpenIddictServerAspNetCoreHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
namespace OpenIddict.Server.AspNetCore
{
public static partial class OpenIddictServerAspNetCoreHandlers
{
public static class Introspection
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Introspection request extraction:
*/
ExtractGetOrPostRequest<ExtractIntrospectionRequestContext>.Descriptor,
/*
* Introspection request handling:
*/
InferIssuerFromHost.Descriptor,
/*
* Introspection response processing:
*/
ProcessJsonResponse<ApplyIntrospectionResponseContext>.Descriptor);
/// <summary>
/// Contains the logic responsible of infering the issuer URL from the HTTP request host.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by ASP.NET Core.
/// </summary>
public class InferIssuerFromHost : IOpenIddictServerHandler<HandleIntrospectionRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleIntrospectionRequestContext>()
.AddFilter<RequireHttpRequest>()
.UseSingletonHandler<InferIssuerFromHost>()
.SetOrder(OpenIddictServerHandlers.Introspection.AttachMetadataClaims.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] HandleIntrospectionRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to ASP.NET Core requests. If the HTTP context cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetHttpRequest();
if (request == null)
{
throw new InvalidOperationException("The ASP.NET Core HTTP request cannot be resolved.");
}
// If the issuer was not populated by another handler (e.g from the server options),
// try to infer it from the request scheme/host/path base (which requires HTTP/1.1).
if (context.Issuer == null)
{
if (!request.Host.HasValue)
{
throw new InvalidOperationException("No host was attached to the HTTP request.");
}
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer))
{
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
context.Issuer = issuer.AbsoluteUri;
}
return default;
}
}
}
}
}

1
src/OpenIddict.Server.AspNetCore/OpenIddictServerAspNetCoreHandlers.cs

@ -37,6 +37,7 @@ namespace OpenIddict.Server.AspNetCore
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers)
.AddRange(Serialization.DefaultHandlers)
.AddRange(Session.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);

32
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionConstants.cs

@ -0,0 +1,32 @@
/*
* 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.Server.DataProtection
{
public static class OpenIddictServerDataProtectionConstants
{
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 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";
}
}
}

33
src/OpenIddict.Server.DataProtection/OpenIddictServerDataProtectionHandlers.Serialization.cs

@ -25,6 +25,7 @@ using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionHandlerFilters;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlers.Serialization;
using Properties = OpenIddict.Server.DataProtection.OpenIddictServerDataProtectionConstants.Properties;
namespace OpenIddict.Server.DataProtection
{
@ -98,7 +99,7 @@ namespace OpenIddict.Server.DataProtection
throw new ArgumentNullException(nameof(context));
}
if (!context.Properties.TryGetValue(typeof(IDataProtector).FullName, out var property) ||
if (!context.Properties.TryGetValue(Properties.DataProtector, out var property) ||
!(property is IDataProtector protector))
{
throw new InvalidOperationException(new StringBuilder()
@ -128,7 +129,7 @@ namespace OpenIddict.Server.DataProtection
SetProperty(properties, Properties.Issued,
context.Principal.GetCreationDate()?.ToString("r", CultureInfo.InvariantCulture));
SetProperty(properties, Properties.OriginalRedirectUri,
context.Principal.GetClaim(Claims.Private.OriginalRedirectUri));
context.Principal.GetClaim(Claims.Private.RedirectUri));
SetProperty(properties, Properties.RefreshTokenLifetime,
context.Principal.GetClaim(Claims.Private.RefreshTokenLifetime));
@ -306,7 +307,7 @@ namespace OpenIddict.Server.DataProtection
throw new ArgumentNullException(nameof(context));
}
if (!context.Properties.TryGetValue(typeof(IDataProtector).FullName, out var property) ||
if (!context.Properties.TryGetValue(Properties.DataProtector, out var property) ||
!(property is IDataProtector protector))
{
throw new InvalidOperationException(new StringBuilder()
@ -344,8 +345,12 @@ namespace OpenIddict.Server.DataProtection
.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.OriginalRedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime));
.SetClaim(Claims.Private.RedirectUri, GetProperty(properties, Properties.OriginalRedirectUri))
.SetClaim(Claims.Private.RefreshTokenLifetime, GetProperty(properties, Properties.RefreshTokenLifetime))
// 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.
.SetClaim(Claims.Private.TokenUsage, (string) context.Properties[Properties.TokenUsage]);
context.HandleDeserialization();
@ -532,7 +537,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.AccessToken;
return default;
}
@ -586,7 +592,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.AuthorizationCode;
return default;
}
@ -640,7 +647,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.RefreshToken;
return default;
}
@ -694,7 +702,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.AccessToken;
return default;
}
@ -748,7 +757,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.AuthorizationCode;
return default;
}
@ -802,7 +812,8 @@ namespace OpenIddict.Server.DataProtection
}
var protector = _options.CurrentValue.DataProtectionProvider.CreateProtector(purposes);
context.Properties[typeof(IDataProtector).FullName] = protector;
context.Properties[Properties.DataProtector] = protector;
context.Properties[Properties.TokenUsage] = TokenUsages.RefreshToken;
return default;
}

97
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.Introspection.cs

@ -0,0 +1,97 @@
/*
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
* See https://github.com/openiddict/openiddict-core for more information concerning
* the license and the contributors participating to this project.
*/
using System;
using System.Collections.Immutable;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Owin;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.Owin.OpenIddictServerOwinHandlerFilters;
namespace OpenIddict.Server.Owin
{
public static partial class OpenIddictServerOwinHandlers
{
public static class Introspection
{
public static ImmutableArray<OpenIddictServerHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create(
/*
* Introspection request extraction:
*/
ExtractGetOrPostRequest<ExtractIntrospectionRequestContext>.Descriptor,
/*
* Introspection request handling:
*/
InferIssuerFromHost.Descriptor,
/*
* Introspection response processing:
*/
ProcessJsonResponse<ApplyIntrospectionResponseContext>.Descriptor);
/// <summary>
/// Contains the logic responsible of infering the issuer URL from the HTTP request host.
/// Note: this handler is not used when the OpenID Connect request is not initially handled by OWIN.
/// </summary>
public class InferIssuerFromHost : IOpenIddictServerHandler<HandleIntrospectionRequestContext>
{
/// <summary>
/// Gets the default descriptor definition assigned to this handler.
/// </summary>
public static OpenIddictServerHandlerDescriptor Descriptor { get; }
= OpenIddictServerHandlerDescriptor.CreateBuilder<HandleIntrospectionRequestContext>()
.AddFilter<RequireOwinRequest>()
.UseSingletonHandler<InferIssuerFromHost>()
.SetOrder(OpenIddictServerHandlers.Introspection.AttachMetadataClaims.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] HandleIntrospectionRequestContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
// This handler only applies to OWIN requests. If The OWIN request cannot be resolved,
// this may indicate that the request was incorrectly processed by another server stack.
var request = context.Transaction.GetOwinRequest();
if (request == null)
{
throw new InvalidOperationException("The OWIN request cannot be resolved.");
}
// If the issuer was not populated by another handler (e.g from the server options),
// try to infer it from the request scheme/host/path base (which requires HTTP/1.1).
if (context.Issuer == null)
{
if (string.IsNullOrEmpty(request.Host.Value))
{
throw new InvalidOperationException("No host was attached to the HTTP request.");
}
if (!Uri.TryCreate(request.Scheme + "://" + request.Host + request.PathBase, UriKind.Absolute, out Uri issuer))
{
throw new InvalidOperationException("The issuer address cannot be inferred from the current request.");
}
context.Issuer = issuer.AbsoluteUri;
}
return default;
}
}
}
}
}

1
src/OpenIddict.Server.Owin/OpenIddictServerOwinHandlers.cs

@ -36,6 +36,7 @@ namespace OpenIddict.Server.Owin
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers)
.AddRange(Serialization.DefaultHandlers)
.AddRange(Session.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);

3
src/OpenIddict.Server/OpenIddictServerBuilder.cs

@ -136,8 +136,9 @@ namespace Microsoft.Extensions.DependencyInjection
}
/// <summary>
/// Makes client identification optional so that token and revocation
/// Makes client identification optional so that token, introspection and revocation
/// requests that don't specify a client_id are not automatically rejected.
/// Enabling this option is NOT recommended.
/// </summary>
/// <returns>The <see cref="OpenIddictServerBuilder"/>.</returns>
public OpenIddictServerBuilder AcceptAnonymousClients()

11
src/OpenIddict.Server/OpenIddictServerConfiguration.cs

@ -116,6 +116,17 @@ namespace OpenIddict.Server
.ToString());
}
if (options.IntrospectionEndpointUris.Count != 0 && !options.CustomHandlers.Any(
descriptor => descriptor.ContextType == typeof(ValidateIntrospectionRequestContext) &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))
{
throw new InvalidOperationException(new StringBuilder()
.Append("No custom introspection request validation handler was found. When enabling the degraded mode, ")
.Append("a custom 'IOpenIddictServerHandler<ValidateIntrospectionRequestContext>' must be implemented ")
.Append("to validate introspection requests (e.g to ensure the client_id and client_secret are valid).")
.ToString());
}
if (options.LogoutEndpointUris.Count != 0 && !options.CustomHandlers.Any(
descriptor => descriptor.ContextType == typeof(ValidateLogoutRequestContext) &&
descriptor.FilterTypes.All(type => !typeof(RequireDegradedModeDisabled).IsAssignableFrom(type))))

18
src/OpenIddict.Server/OpenIddictServerConstants.cs

@ -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.
*/
namespace OpenIddict.Server
{
public static class OpenIddictServerConstants
{
public static class Properties
{
public const string Principal = ".principal";
public const string ValidatedPostLogoutRedirectUri = ".validated_post_logout_redirect_uri";
public const string ValidatedRedirectUri = ".validated_redirect_uri";
}
}
}

17
src/OpenIddict.Server/OpenIddictServerEvents.Introspection.cs

@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using JetBrains.Annotations;
using OpenIddict.Abstractions;
@ -47,6 +48,11 @@ namespace OpenIddict.Server
/// introspection request, or <c>null</c> if it cannot be found.
/// </summary>
public string TokenTypeHint => Request.TokenTypeHint;
/// <summary>
/// Gets or sets the security principal extracted from the introspected token, if available.
/// </summary>
public ClaimsPrincipal Principal { get; set; }
}
/// <summary>
@ -64,16 +70,15 @@ namespace OpenIddict.Server
}
/// <summary>
/// Gets the additional claims returned to the caller.
/// Gets or sets the security principal extracted from the introspected token.
/// </summary>
public IDictionary<string, OpenIddictParameter> Claims { get; } =
new Dictionary<string, OpenIddictParameter>(StringComparer.Ordinal);
public ClaimsPrincipal Principal { get; set; }
/// <summary>
/// Gets or sets the flag indicating
/// whether the token is active or inactive.
/// Gets the additional claims returned to the caller.
/// </summary>
public bool Active { get; set; }
public IDictionary<string, OpenIddictParameter> Claims { get; } =
new Dictionary<string, OpenIddictParameter>(StringComparer.Ordinal);
/// <summary>
/// Gets the list of audiences returned to the caller

1
src/OpenIddict.Server/OpenIddictServerExtensions.cs

@ -47,7 +47,6 @@ namespace Microsoft.Extensions.DependencyInjection
builder.Services.TryAddSingleton<RequireAuthorizationCodeIncluded>();
builder.Services.TryAddSingleton<RequireClientIdParameter>();
builder.Services.TryAddSingleton<RequireDegradedModeDisabled>();
builder.Services.TryAddSingleton<RequireDegradedModeEnabled>();
builder.Services.TryAddSingleton<RequireEndpointPermissionsEnabled>();
builder.Services.TryAddSingleton<RequireGrantTypePermissionsEnabled>();
builder.Services.TryAddSingleton<RequireIdentityTokenIncluded>();

16
src/OpenIddict.Server/OpenIddictServerHandlerFilters.cs

@ -79,22 +79,6 @@ namespace OpenIddict.Server
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if the degraded mode was enabled.
/// </summary>
public class RequireDegradedModeEnabled : IOpenIddictServerHandlerFilter<BaseContext>
{
public ValueTask<bool> IsActiveAsync([NotNull] BaseContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
return new ValueTask<bool>(context.Options.EnableDegradedMode);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if endpoint permissions were disabled.
/// </summary>

1
src/OpenIddict.Server/OpenIddictServerHandlers.Authentication.cs

@ -14,6 +14,7 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{

10
src/OpenIddict.Server/OpenIddictServerHandlers.Exchange.cs

@ -18,6 +18,7 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{
@ -203,7 +204,7 @@ namespace OpenIddict.Server
}
// Store the security principal extracted from the authorization code/refresh token as an environment property.
context.Transaction.Properties[Properties.OriginalPrincipal] = notification.Principal;
context.Transaction.Properties[Properties.Principal] = notification.Principal;
context.Logger.LogInformation("The token request was successfully validated.");
}
@ -427,8 +428,7 @@ namespace OpenIddict.Server
}
/// <summary>
/// Contains the logic responsible of rejecting token requests that don't
/// specify a client identifier for the authorization code grant type.
/// Contains the logic responsible of rejecting token requests that don't specify a client identifier.
/// </summary>
public class ValidateClientIdParameter : IOpenIddictServerHandler<ValidateTokenRequestContext>
{
@ -1478,7 +1478,7 @@ namespace OpenIddict.Server
// if the authorization request didn't contain an explicit redirect_uri.
// See https://tools.ietf.org/html/rfc6749#section-4.1.3
// and http://openid.net/specs/openid-connect-core-1_0.html#TokenRequestValidation.
var address = context.Principal.GetClaim(Claims.Private.OriginalRedirectUri);
var address = context.Principal.GetClaim(Claims.Private.RedirectUri);
if (string.IsNullOrEmpty(address))
{
return default;
@ -1739,7 +1739,7 @@ namespace OpenIddict.Server
return default;
}
if (context.Transaction.Properties.TryGetValue(Properties.OriginalPrincipal, out var principal))
if (context.Transaction.Properties.TryGetValue(Properties.Principal, out var principal))
{
context.Principal ??= (ClaimsPrincipal) principal;
}

1231
src/OpenIddict.Server/OpenIddictServerHandlers.Introspection.cs

File diff suppressed because it is too large

27
src/OpenIddict.Server/OpenIddictServerHandlers.Serialization.cs

@ -8,7 +8,6 @@ using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
@ -16,7 +15,6 @@ using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
@ -109,37 +107,15 @@ namespace OpenIddict.Server
throw new InvalidOperationException("The token usage cannot be null or empty.");
}
var destinations = new Dictionary<string, string[]>(StringComparer.Ordinal);
var claims = new Dictionary<string, object>(StringComparer.Ordinal)
{
[Claims.Private.TokenUsage] = context.TokenUsage
};
var destinations = new Dictionary<string, string[]>(StringComparer.Ordinal);
foreach (var group in context.Principal.Claims.GroupBy(claim => claim.Type))
{
var collection = group.ToList();
switch (collection.Count)
{
case 1:
claims[group.Key] = collection[0].ValueType switch
{
ClaimValueTypes.Boolean => bool.Parse(collection[0].Value),
ClaimValueTypes.Double => double.Parse(collection[0].Value, NumberStyles.Number, CultureInfo.InvariantCulture),
ClaimValueTypes.Integer => int.Parse(collection[0].Value, NumberStyles.Integer, CultureInfo.InvariantCulture),
ClaimValueTypes.Integer32 => int.Parse(collection[0].Value, NumberStyles.Integer, CultureInfo.InvariantCulture),
ClaimValueTypes.Integer64 => long.Parse(collection[0].Value, NumberStyles.Integer, CultureInfo.InvariantCulture),
"JSON" => JObject.Parse(collection[0].Value),
"JSON_ARRAY" => JArray.Parse(collection[0].Value),
_ => (object) collection[0].Value
};
break;
default:
claims[group.Key] = collection.Select(claim => claim.Value).ToArray();
break;
}
// Note: destinations are attached to claims as special CLR properties. Such properties can't be serialized
// as part of classic JWT tokens. To work around this limitation, claim destinations are added to a special
@ -170,6 +146,7 @@ namespace OpenIddict.Server
context.Token = context.SecurityTokenHandler.CreateToken(new SecurityTokenDescriptor
{
Subject = (ClaimsIdentity) context.Principal.Identity,
Claims = new ReadOnlyDictionary<string, object>(claims),
EncryptingCredentials = context.EncryptingCredentials,
Issuer = context.Issuer?.AbsoluteUri,

5
src/OpenIddict.Server/OpenIddictServerHandlers.Session.cs

@ -14,6 +14,7 @@ using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using static OpenIddict.Server.OpenIddictServerHandlerFilters;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{
@ -184,7 +185,7 @@ namespace OpenIddict.Server
if (!string.IsNullOrEmpty(notification.PostLogoutRedirectUri))
{
// Store the validated post_logout_redirect_uri as an environment property.
context.Transaction.Properties[Properties.PostLogoutRedirectUri] = notification.PostLogoutRedirectUri;
context.Transaction.Properties[Properties.ValidatedPostLogoutRedirectUri] = notification.PostLogoutRedirectUri;
}
context.Logger.LogInformation("The logout request was successfully validated.");
@ -522,7 +523,7 @@ namespace OpenIddict.Server
// Note: at this stage, the validated redirect URI property may be null (e.g if an error
// is returned from the ExtractLogoutRequest/ValidateLogoutRequest events).
if (context.Transaction.Properties.TryGetValue(Properties.PostLogoutRedirectUri, out var property))
if (context.Transaction.Properties.TryGetValue(Properties.ValidatedPostLogoutRedirectUri, out var property))
{
context.PostLogoutRedirectUri = (string) property;
}

5
src/OpenIddict.Server/OpenIddictServerHandlers.Userinfo.cs

@ -16,6 +16,7 @@ using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using static OpenIddict.Abstractions.OpenIddictConstants;
using static OpenIddict.Server.OpenIddictServerEvents;
using Properties = OpenIddict.Server.OpenIddictServerConstants.Properties;
namespace OpenIddict.Server
{
@ -186,7 +187,7 @@ namespace OpenIddict.Server
}
// Store the security principal extracted from the authorization code/refresh token as an environment property.
context.Transaction.Properties[Properties.OriginalPrincipal] = notification.Principal;
context.Transaction.Properties[Properties.Principal] = notification.Principal;
context.Logger.LogInformation("The userinfo request was successfully validated.");
}
@ -497,7 +498,7 @@ namespace OpenIddict.Server
throw new ArgumentNullException(nameof(context));
}
if (context.Transaction.Properties.TryGetValue(Properties.OriginalPrincipal, out var principal))
if (context.Transaction.Properties.TryGetValue(Properties.Principal, out var principal))
{
context.Principal ??= (ClaimsPrincipal) principal;
}

7
src/OpenIddict.Server/OpenIddictServerHandlers.cs

@ -45,6 +45,7 @@ namespace OpenIddict.Server
.AddRange(Authentication.DefaultHandlers)
.AddRange(Discovery.DefaultHandlers)
.AddRange(Exchange.DefaultHandlers)
.AddRange(Introspection.DefaultHandlers)
.AddRange(Serialization.DefaultHandlers)
.AddRange(Session.DefaultHandlers)
.AddRange(Userinfo.DefaultHandlers);
@ -502,7 +503,7 @@ namespace OpenIddict.Server
// receiving a grant_type=authorization_code token request.
if (!string.IsNullOrEmpty(context.Request.RedirectUri))
{
principal.SetClaim(Claims.Private.OriginalRedirectUri, context.Request.RedirectUri);
principal.SetClaim(Claims.Private.RedirectUri, context.Request.RedirectUri);
}
// Attach the code challenge and the code challenge methods to allow the ValidateCodeVerifier
@ -521,7 +522,7 @@ namespace OpenIddict.Server
// the token endpoint as part of the JWT identity token.
if (!string.IsNullOrEmpty(context.Request.Nonce))
{
principal.SetClaim(Claims.Nonce, context.Request.Nonce);
principal.SetClaim(Claims.Private.Nonce, context.Request.Nonce);
}
var notification = new SerializeAuthorizationCodeContext(context.Transaction)
@ -701,7 +702,7 @@ namespace OpenIddict.Server
else if (context.EndpointType == OpenIddictServerEndpointType.Token)
{
var nonce = context.Principal.GetClaim(Claims.Nonce);
var nonce = context.Principal.GetClaim(Claims.Private.Nonce);
if (!string.IsNullOrEmpty(nonce))
{
principal.SetClaim(Claims.Nonce, nonce);

4
src/OpenIddict.Server/OpenIddictServerOptions.cs

@ -160,8 +160,8 @@ namespace OpenIddict.Server
/// <summary>
/// Gets or sets a boolean determining whether client identification is optional.
/// Enabling this option allows client applications to communicate with the token
/// and revocation endpoints without having to send their client identifier.
/// Enabling this option allows client applications to communicate with the token,
/// introspection and revocation endpoints without having to send their client identifier.
/// </summary>
public bool AcceptAnonymousClients { get; set; }

Loading…
Cancel
Save