Browse Source

Introduce a dynamic option allowing to disable userinfo validation

pull/1724/head
Kévin Chalet 3 years ago
parent
commit
32bd40cda8
  1. 8
      src/OpenIddict.Client/OpenIddictClientEvents.cs
  2. 1
      src/OpenIddict.Client/OpenIddictClientExtensions.cs
  3. 17
      src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs
  4. 112
      src/OpenIddict.Client/OpenIddictClientHandlers.cs

8
src/OpenIddict.Client/OpenIddictClientEvents.cs

@ -826,6 +826,14 @@ public static partial class OpenIddictClientEvents
/// Note: overriding the value of this property is generally not recommended.
/// </remarks>
public bool DisableFrontchannelIdentityTokenNonceValidation { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether userinfo validation should be disabled.
/// </summary>
/// <remarks>
/// Note: overriding the value of this property is generally not recommended.
/// </remarks>
public bool DisableUserinfoValidation { get; set; }
}
/// <summary>

1
src/OpenIddict.Client/OpenIddictClientExtensions.cs

@ -63,6 +63,7 @@ public static class OpenIddictClientExtensions
builder.Services.TryAddSingleton<RequireUserinfoRequest>();
builder.Services.TryAddSingleton<RequireUserinfoTokenExtracted>();
builder.Services.TryAddSingleton<RequireUserinfoTokenPrincipal>();
builder.Services.TryAddSingleton<RequireUserinfoValidationEnabled>();
// Register the built-in client event handlers used by the OpenIddict client components.
// Note: the order used here is not important, as the actual order is set in the options.

17
src/OpenIddict.Client/OpenIddictClientHandlerFilters.cs

@ -475,4 +475,21 @@ public static class OpenIddictClientHandlerFilters
return new(context.UserinfoTokenPrincipal is not null);
}
}
/// <summary>
/// Represents a filter that excludes the associated handlers if userinfo validation was disabled.
/// </summary>
public sealed class RequireUserinfoValidationEnabled : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext>
{
/// <inheritdoc/>
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context)
{
if (context is null)
{
throw new ArgumentNullException(nameof(context));
}
return new(!context.DisableUserinfoValidation);
}
}
}

112
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -3404,6 +3404,12 @@ public static partial class OpenIddictClientHandlers
_ => false
};
// The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
// but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
//
// As such, protocol requirements are only enforced if the server supports OpenID Connect.
context.DisableUserinfoValidation = !context.Configuration.ScopesSupported.Contains(Scopes.OpenId);
return default;
}
}
@ -3659,6 +3665,7 @@ public static partial class OpenIddictClientHandlers
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireUserinfoValidationEnabled>()
.AddFilter<RequireUserinfoTokenPrincipal>()
.UseSingletonHandler<ValidateUserinfoTokenWellknownClaims>()
.SetOrder(ValidateUserinfoToken.Descriptor.Order + 1_000)
@ -3675,28 +3682,21 @@ public static partial class OpenIddictClientHandlers
Debug.Assert(context.UserinfoTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
// but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
//
// As such, protocol requirements are only enforced if the server supports OpenID Connect.
if (context.Configuration.ScopesSupported.Contains(Scopes.OpenId))
foreach (var group in context.UserinfoTokenPrincipal.Claims
.GroupBy(claim => claim.Type)
.ToDictionary(group => group.Key, group => group.ToList()))
{
foreach (var group in context.UserinfoTokenPrincipal.Claims
.GroupBy(claim => claim.Type)
.ToDictionary(group => group.Key, group => group.ToList()))
if (ValidateClaimGroup(group))
{
if (ValidateClaimGroup(group))
{
continue;
}
continue;
}
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2131(group.Key),
uri: SR.FormatID8000(SR.ID2131));
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2131(group.Key),
uri: SR.FormatID8000(SR.ID2131));
return default;
}
return default;
}
return default;
@ -3725,6 +3725,7 @@ public static partial class OpenIddictClientHandlers
/// </summary>
public static OpenIddictClientHandlerDescriptor Descriptor { get; }
= OpenIddictClientHandlerDescriptor.CreateBuilder<ProcessAuthenticationContext>()
.AddFilter<RequireUserinfoValidationEnabled>()
.AddFilter<RequireUserinfoTokenPrincipal>()
.UseSingletonHandler<ValidateUserinfoTokenSubject>()
.SetOrder(ValidateUserinfoTokenWellknownClaims.Descriptor.Order + 1_000)
@ -3741,53 +3742,46 @@ public static partial class OpenIddictClientHandlers
Debug.Assert(context.UserinfoTokenPrincipal is { Identity: ClaimsIdentity }, SR.GetResourceString(SR.ID4006));
// The OpenIddict client is expected to be used with standard OpenID Connect userinfo endpoints
// but must also support non-standard implementations, that are common with OAuth 2.0-only servers.
//
// As such, protocol requirements are only enforced if the server supports OpenID Connect.
if (context.Configuration.ScopesSupported.Contains(Scopes.OpenId))
// Standard OpenID Connect userinfo responses/tokens MUST contain a "sub" claim. For more
// information, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (!context.UserinfoTokenPrincipal.HasClaim(Claims.Subject))
{
// Standard OpenID Connect userinfo responses/tokens MUST contain a "sub" claim. For more
// information, see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (!context.UserinfoTokenPrincipal.HasClaim(Claims.Subject))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2132(Claims.Subject),
uri: SR.FormatID8000(SR.ID2132));
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2132(Claims.Subject),
uri: SR.FormatID8000(SR.ID2132));
return default;
}
return default;
}
// The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
// returned in the frontchannel identity token, if one was returned. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (context.FrontchannelIdentityTokenPrincipal is not null && !string.Equals(
context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2133(Claims.Subject),
uri: SR.FormatID8000(SR.ID2133));
// The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
// returned in the frontchannel identity token, if one was returned. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (context.FrontchannelIdentityTokenPrincipal is not null && !string.Equals(
context.FrontchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2133(Claims.Subject),
uri: SR.FormatID8000(SR.ID2133));
return default;
}
return default;
}
// The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
// returned in the frontchannel identity token, if one was returned. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (context.BackchannelIdentityTokenPrincipal is not null && !string.Equals(
context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2133(Claims.Subject),
uri: SR.FormatID8000(SR.ID2133));
// The "sub" claim returned as part of the userinfo response/token MUST exactly match the value
// returned in the frontchannel identity token, if one was returned. For more information,
// see https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse.
if (context.BackchannelIdentityTokenPrincipal is not null && !string.Equals(
context.BackchannelIdentityTokenPrincipal.GetClaim(Claims.Subject),
context.UserinfoTokenPrincipal.GetClaim(Claims.Subject), StringComparison.Ordinal))
{
context.Reject(
error: Errors.InvalidRequest,
description: SR.FormatID2133(Claims.Subject),
uri: SR.FormatID8000(SR.ID2133));
return default;
}
return default;
}
return default;

Loading…
Cancel
Save