Browse Source

Introduce a new claims issuer option in the client and validation stacks

pull/2210/head
Kévin Chalet 1 year ago
parent
commit
e2c3ab9057
  1. 17
      gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs
  2. 13
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  3. 9
      src/OpenIddict.Client/OpenIddictClientHandlers.Introspection.cs
  4. 31
      src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs
  5. 9
      src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs
  6. 8
      src/OpenIddict.Client/OpenIddictClientHandlers.cs
  7. 11
      src/OpenIddict.Client/OpenIddictClientRegistration.cs
  8. 17
      src/OpenIddict.Validation/OpenIddictValidationBuilder.cs
  9. 3
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs
  10. 25
      src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs
  11. 11
      src/OpenIddict.Validation/OpenIddictValidationOptions.cs

17
gen/OpenIddict.Client.WebIntegration.Generators/OpenIddictClientWebIntegrationGenerator.cs

@ -52,6 +52,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection.Extensions;
@ -224,6 +225,22 @@ public sealed partial class OpenIddictClientWebIntegrationBuilder
return Set(registration => registration.Scopes.UnionWith(scopes));
}
/// <summary>
/// Sets the issuer that will be attached to the <see cref=""Claim""/>
/// instances created by the OpenIddict client stack for this provider.
/// </summary>
/// <param name=""issuer"">The claims issuer.</param>
/// <returns>The <see cref=""OpenIddictClientWebIntegrationBuilder.{{ provider.name }}""/> instance.</returns>
public {{ provider.name }} SetClaimsIssuer(string issuer)
{
if (string.IsNullOrEmpty(issuer))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(issuer));
}
return Set(registration => registration.ClaimsIssuer = issuer);
}
/// <summary>
/// Sets the provider name.
/// </summary>

13
src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs

@ -1255,6 +1255,11 @@ public static partial class OpenIddictClientWebIntegrationHandlers
context.Registration.TokenValidationParameters.NameClaimType,
context.Registration.TokenValidationParameters.RoleClaimType);
// Resolve the issuer that will be attached to the claims created by this handler.
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
foreach (var parameter in parameters)
{
// Note: in the typical case, the response parameters should be deserialized from a
@ -1269,11 +1274,11 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Top-level claims represented as arrays are split and mapped to multiple CLR claims
// to match the logic implemented by IdentityModel for JWT token deserialization.
case { ValueKind: JsonValueKind.Array } value:
identity.AddClaims(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaims(parameter.Key, value, issuer);
break;
case { ValueKind: _ } value:
identity.AddClaim(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaim(parameter.Key, value, issuer);
break;
}
}
@ -1323,7 +1328,9 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Note: a similar event handler exists in OpenIddict.Client to map these claims from
// the standard OpenID Connect claim types (see MapStandardWebServicesFederationClaims).
var issuer = context.Registration.Issuer.AbsoluteUri;
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
context.MergedPrincipal.SetClaim(ClaimTypes.Email, issuer: issuer, value: context.Registration.ProviderType switch
{

9
src/OpenIddict.Client/OpenIddictClientHandlers.Introspection.cs

@ -406,6 +406,11 @@ public static partial class OpenIddictClientHandlers
nameType: ClaimTypes.Name,
roleType: ClaimTypes.Role);
// Resolve the issuer that will be attached to the claims created by this handler.
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
foreach (var parameter in context.Response.GetParameters())
{
// Always exclude null keys as they can't be represented as valid claims.
@ -439,11 +444,11 @@ public static partial class OpenIddictClientHandlers
// Top-level claims represented as arrays are split and mapped to multiple CLR claims
// to match the logic implemented by IdentityModel for JWT token deserialization.
case { ValueKind: JsonValueKind.Array } value:
identity.AddClaims(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaims(parameter.Key, value, issuer);
break;
case { ValueKind: _ } value:
identity.AddClaim(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaim(parameter.Key, value, issuer);
break;
}
}

31
src/OpenIddict.Client/OpenIddictClientHandlers.Protection.cs

@ -399,8 +399,34 @@ public static partial class OpenIddictClientHandlers
token = token.InnerToken;
}
// Clone the identity and remove OpenIddict-specific claims from tokens that are not fully trusted.
var identity = result.ClaimsIdentity.Clone(claim => claim switch
ClaimsIdentity identity;
// If the token is not a state token and a different claims issuer value was set,
// override the issuer attached to all the claims returned by IdentityModel.
if (result.TokenType is not JsonWebTokenTypes.Private.StateToken &&
(context.Registration.ClaimsIssuer ?? context.Registration.ProviderName) is { Length: > 0 } issuer &&
!string.Equals(issuer, context.Registration.Issuer?.AbsoluteUri, StringComparison.Ordinal))
{
identity = new ClaimsIdentity(
result.ClaimsIdentity.AuthenticationType,
result.ClaimsIdentity.NameClaimType,
result.ClaimsIdentity.RoleClaimType);
foreach (var claim in result.ClaimsIdentity.Claims)
{
// Exclude claims starting with "oi_" from tokens that are not fully trusted.
if (claim.Type.StartsWith(Claims.Prefixes.Private))
{
continue;
}
identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity));
}
}
else
{
identity = result.ClaimsIdentity.Clone(claim => claim switch
{
// Exclude claims starting with "oi_", unless the token is a state token.
{ Type: string type } when type.StartsWith(Claims.Prefixes.Private) &&
@ -408,6 +434,7 @@ public static partial class OpenIddictClientHandlers
_ => true // Allow any other claim.
});
}
if (context.ValidTokenTypes.Contains(TokenTypeHints.StateToken))
{

9
src/OpenIddict.Client/OpenIddictClientHandlers.Userinfo.cs

@ -175,6 +175,11 @@ public static partial class OpenIddictClientHandlers
context.Registration.TokenValidationParameters.NameClaimType,
context.Registration.TokenValidationParameters.RoleClaimType);
// Resolve the issuer that will be attached to the claims created by this handler.
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
foreach (var parameter in context.Response.GetParameters())
{
// Always exclude null keys as they can't be represented as valid claims.
@ -208,11 +213,11 @@ public static partial class OpenIddictClientHandlers
// Top-level claims represented as arrays are split and mapped to multiple CLR claims
// to match the logic implemented by IdentityModel for JWT token deserialization.
case { ValueKind: JsonValueKind.Array } value:
identity.AddClaims(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaims(parameter.Key, value, issuer);
break;
case { ValueKind: _ } value:
identity.AddClaim(parameter.Key, value, context.Registration.Issuer.AbsoluteUri);
identity.AddClaim(parameter.Key, value, issuer);
break;
}
}

8
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -4333,7 +4333,9 @@ public static partial class OpenIddictClientHandlers
// Note: a similar event handler exists in OpenIddict.Client.WebIntegration to map these claims
// from non-standard/provider-specific claim types (see MapCustomWebServicesFederationClaims).
var issuer = context.Registration.Issuer.AbsoluteUri;
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
context.MergedPrincipal
.SetClaim(ClaimTypes.Email, context.MergedPrincipal.GetClaim(Claims.Email), issuer)
@ -6799,7 +6801,9 @@ public static partial class OpenIddictClientHandlers
// WS-Federation equivalent, this handler is responsible for mapping the standard OAuth 2.0 introspection nodes
// defined by https://datatracker.ietf.org/doc/html/rfc7662#section-2.2 to their WS-Federation equivalent.
var issuer = context.Registration.Issuer.AbsoluteUri;
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
context.Principal
.SetClaim(ClaimTypes.Name, context.Principal.GetClaim(Claims.Username), issuer)

11
src/OpenIddict.Client/OpenIddictClientRegistration.cs

@ -5,6 +5,7 @@
*/
using System.Diagnostics;
using System.Security.Claims;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
@ -113,6 +114,16 @@ public sealed class OpenIddictClientRegistration
/// </remarks>
public HashSet<string> ResponseTypes { get; } = new(StringComparer.Ordinal);
/// <summary>
/// Gets or sets the issuer that will be attached to the <see cref="Claim"/>
/// instances created by the OpenIddict client stack for this registration.
/// </summary>
/// <remarks>
/// Note: if this property is not explicitly set, the provider name (if set)
/// or the issuer URI are automatically used as a fallback value.
/// </remarks>
public string? ClaimsIssuer { get; set; }
/// <summary>
/// Gets or sets the URI of the authorization server.
/// </summary>

17
src/OpenIddict.Validation/OpenIddictValidationBuilder.cs

@ -8,6 +8,7 @@ using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.IdentityModel.Tokens;
@ -665,6 +666,22 @@ public sealed class OpenIddictValidationBuilder
return Configure(options => options.Configuration = configuration);
}
/// <summary>
/// Sets the issuer that will be attached to the <see cref="Claim"/>
/// instances created by the OpenIddict validation stack.
/// </summary>
/// <param name="issuer">The claims issuer.</param>
/// <returns>The <see cref="OpenIddictValidationBuilder"/> instance.</returns>
public OpenIddictValidationBuilder SetClaimsIssuer(string issuer)
{
if (string.IsNullOrEmpty(issuer))
{
throw new ArgumentException(SR.GetResourceString(SR.ID0124), nameof(issuer));
}
return Configure(options => options.ClaimsIssuer = issuer);
}
/// <summary>
/// Sets the client identifier client_id used when communicating
/// with the remote authorization server (e.g for introspection).

3
src/OpenIddict.Validation/OpenIddictValidationHandlers.Introspection.cs

@ -397,7 +397,8 @@ public static partial class OpenIddictValidationHandlers
context.Options.TokenValidationParameters.RoleClaimType);
// Resolve the issuer that will be attached to the claims created by this handler.
var issuer = context.Configuration.Issuer?.AbsoluteUri ??
var issuer = context.Options.ClaimsIssuer ??
context.Configuration.Issuer?.AbsoluteUri ??
context.BaseUri?.AbsoluteUri ?? ClaimsIdentity.DefaultIssuer;
foreach (var parameter in context.Response.GetParameters())

25
src/OpenIddict.Validation/OpenIddictValidationHandlers.Protection.cs

@ -334,9 +334,32 @@ public static partial class OpenIddictValidationHandlers
return;
}
ClaimsIdentity identity;
// If a different claims issuer value was set, override the
// issuer attached to all the claims returned by IdentityModel.
if (context.Options.ClaimsIssuer is { Length: > 0 } issuer &&
!string.Equals(issuer, (context.Configuration.Issuer ?? context.BaseUri)?.AbsoluteUri, StringComparison.Ordinal))
{
identity = new ClaimsIdentity(
result.ClaimsIdentity.AuthenticationType,
result.ClaimsIdentity.NameClaimType,
result.ClaimsIdentity.RoleClaimType);
foreach (var claim in result.ClaimsIdentity.Claims)
{
identity.AddClaim(new Claim(claim.Type, claim.Value, claim.ValueType, issuer, issuer, identity));
}
}
else
{
identity = result.ClaimsIdentity;
}
// Attach the principal extracted from the token to the parent event context and store
// the token type (resolved from "typ" or "token_usage") as a special private claim.
context.Principal = new ClaimsPrincipal(result.ClaimsIdentity).SetTokenType(result.TokenType switch
context.Principal = new ClaimsPrincipal(identity).SetTokenType(result.TokenType switch
{
null or { Length: 0 } => throw new InvalidOperationException(SR.GetResourceString(SR.ID0025)),

11
src/OpenIddict.Validation/OpenIddictValidationOptions.cs

@ -4,6 +4,7 @@
* the license and the contributors participating to this project.
*/
using System.Security.Claims;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
@ -102,6 +103,16 @@ public sealed class OpenIddictValidationOptions
/// </summary>
public bool EnableTokenEntryValidation { get; set; }
/// <summary>
/// Gets or sets the issuer that will be attached to the <see cref="Claim"/>
/// instances created by the OpenIddict validation stack.
/// </summary>
/// <remarks>
/// Note: if this property is not explicitly set, the
/// issuer URI is automatically used as a fallback value.
/// </remarks>
public string? ClaimsIssuer { get; set; }
/// <summary>
/// Gets or sets the absolute URI of the OAuth 2.0/OpenID Connect server.
/// </summary>

Loading…
Cancel
Save