Browse Source

Explicitly attach a claim value type to the mapped WS-Federation claims

pull/2449/head
Kévin Chalet 2 months ago
parent
commit
5e65b72b9b
  1. 2
      src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs
  2. 40
      src/OpenIddict.Client.WebIntegration/OpenIddictClientWebIntegrationHandlers.cs
  3. 75
      src/OpenIddict.Client/OpenIddictClientHandlers.cs

2
src/OpenIddict.Client.Owin/OpenIddictClientOwinHandler.cs

@ -167,7 +167,7 @@ public sealed class OpenIddictClientOwinHandler : AuthenticationHandler<OpenIddi
{ {
var properties = CreateAuthenticationProperties(); var properties = CreateAuthenticationProperties();
return new AuthenticationTicket(context.MergedPrincipal?.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties); return new AuthenticationTicket(context.MergedPrincipal.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties);
} }
AuthenticationProperties CreateAuthenticationProperties() AuthenticationProperties CreateAuthenticationProperties()

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

@ -1426,13 +1426,18 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Note: a similar event handler exists in OpenIddict.Client to map these claims from // Note: a similar event handler exists in OpenIddict.Client to map these claims from
// the standard OpenID Connect claim types (see MapStandardWebServicesFederationClaims). // the standard OpenID Connect claim types (see MapStandardWebServicesFederationClaims).
if (context.MergedPrincipal.Identity is not ClaimsIdentity identity)
{
return ValueTask.CompletedTask;
}
var issuer = context.Registration.ClaimsIssuer ?? var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ?? context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri; context.Registration.Issuer.AbsoluteUri;
if (!context.MergedPrincipal.HasClaim(ClaimTypes.Email)) if (!context.MergedPrincipal.HasClaim(ClaimTypes.Email))
{ {
context.MergedPrincipal.SetClaim(ClaimTypes.Email, issuer: issuer, value: context.Registration.ProviderType switch var value = context.Registration.ProviderType switch
{ {
// Basecamp returns the email address as a custom "email_address" node: // Basecamp returns the email address as a custom "email_address" node:
ProviderTypes.Basecamp => (string?) context.UserInfoResponse?["email_address"], ProviderTypes.Basecamp => (string?) context.UserInfoResponse?["email_address"],
@ -1466,13 +1471,18 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Yandex returns the email address as a custom "default_email" node: // Yandex returns the email address as a custom "default_email" node:
ProviderTypes.Yandex => (string?) context.UserInfoResponse?["default_email"], ProviderTypes.Yandex => (string?) context.UserInfoResponse?["default_email"],
_ => context.MergedPrincipal.GetClaim(ClaimTypes.Email) _ => null
}); };
if (!string.IsNullOrEmpty(value))
{
context.MergedPrincipal.AddClaim(ClaimTypes.Email, value, issuer);
}
} }
if (!context.MergedPrincipal.HasClaim(ClaimTypes.Name)) if (!context.MergedPrincipal.HasClaim(ClaimTypes.Name))
{ {
context.MergedPrincipal.SetClaim(ClaimTypes.Name, issuer: issuer, value: context.Registration.ProviderType switch var value = context.Registration.ProviderType switch
{ {
// These providers return the username as a custom "username" node: // These providers return the username as a custom "username" node:
ProviderTypes.ArcGisOnline or ProviderTypes.Dailymotion or ProviderTypes.DeviantArt or ProviderTypes.ArcGisOnline or ProviderTypes.Dailymotion or ProviderTypes.DeviantArt or
@ -1554,13 +1564,18 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Zoho returns the username as a custom "Display_Name" node: // Zoho returns the username as a custom "Display_Name" node:
ProviderTypes.Zoho => (string?) context.UserInfoResponse?["Display_Name"], ProviderTypes.Zoho => (string?) context.UserInfoResponse?["Display_Name"],
_ => context.MergedPrincipal.GetClaim(ClaimTypes.Name) _ => null
}); };
if (!string.IsNullOrEmpty(value))
{
context.MergedPrincipal.AddClaim(ClaimTypes.Name, value, issuer);
}
} }
if (!context.MergedPrincipal.HasClaim(ClaimTypes.NameIdentifier)) if (!context.MergedPrincipal.HasClaim(ClaimTypes.NameIdentifier))
{ {
context.MergedPrincipal.SetClaim(ClaimTypes.NameIdentifier, issuer: issuer, value: context.Registration.ProviderType switch var value = context.Registration.ProviderType switch
{ {
// These providers return the user identifier as a custom "user_id" node: // These providers return the user identifier as a custom "user_id" node:
ProviderTypes.Amazon or ProviderTypes.HubSpot or ProviderTypes.Amazon or ProviderTypes.HubSpot or
@ -1655,8 +1670,13 @@ public static partial class OpenIddictClientWebIntegrationHandlers
// Zoho returns the user identifier as a custom "ZUID" node: // Zoho returns the user identifier as a custom "ZUID" node:
ProviderTypes.Zoho => (string?) context.UserInfoResponse?["ZUID"], ProviderTypes.Zoho => (string?) context.UserInfoResponse?["ZUID"],
_ => context.MergedPrincipal.GetClaim(ClaimTypes.NameIdentifier) _ => null
}); };
if (!string.IsNullOrEmpty(value))
{
context.MergedPrincipal.AddClaim(ClaimTypes.NameIdentifier, value, issuer);
}
} }
return ValueTask.CompletedTask; return ValueTask.CompletedTask;

75
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -10,6 +10,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Security.Claims; using System.Security.Claims;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -4737,38 +4738,48 @@ public static partial class OpenIddictClientHandlers
// Note: a similar event handler exists in OpenIddict.Client.WebIntegration to map these claims // Note: a similar event handler exists in OpenIddict.Client.WebIntegration to map these claims
// from non-standard/provider-specific claim types (see MapCustomWebServicesFederationClaims). // from non-standard/provider-specific claim types (see MapCustomWebServicesFederationClaims).
if (context.MergedPrincipal.Identity is not ClaimsIdentity identity)
{
return ValueTask.CompletedTask;
}
var issuer = context.Registration.ClaimsIssuer ?? var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ?? context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri; context.Registration.Issuer.AbsoluteUri;
MapClaim(ClaimTypes.Email, Claims.Email); MapClaim(ClaimTypes.Email, ClaimValueTypes.String, [Claims.Email]);
MapClaim(ClaimTypes.Gender, Claims.Gender); MapClaim(ClaimTypes.Gender, ClaimValueTypes.String, [Claims.Gender]);
MapClaim(ClaimTypes.GivenName, Claims.GivenName); MapClaim(ClaimTypes.GivenName, ClaimValueTypes.String, [Claims.GivenName]);
MapClaim(ClaimTypes.Name, Claims.PreferredUsername, Claims.Name); MapClaim(ClaimTypes.Name, ClaimValueTypes.String, [Claims.PreferredUsername, Claims.Name]);
MapClaim(ClaimTypes.NameIdentifier, Claims.Subject); MapClaim(ClaimTypes.NameIdentifier, ClaimValueTypes.String, [Claims.Subject]);
MapClaim(ClaimTypes.OtherPhone, Claims.PhoneNumber); MapClaim(ClaimTypes.OtherPhone, ClaimValueTypes.String, [Claims.PhoneNumber]);
MapClaim(ClaimTypes.Surname, Claims.FamilyName); MapClaim(ClaimTypes.Surname, ClaimValueTypes.String, [Claims.FamilyName]);
// Note: while this claim is not exposed by the BCL ClaimTypes class, it is used by both ASP.NET Identity // Note: while this claim is not exposed by the BCL ClaimTypes class, it is used by both ASP.NET Identity
// for ASP.NET 4.x and the System.Web.WebPages package, that requires it for antiforgery to work correctly. // for ASP.NET 4.x and the System.Web.WebPages package, that requires it for antiforgery to work correctly.
MapClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", MapClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
Claims.Private.ProviderName); ClaimValueTypes.String, [Claims.Private.ProviderName]);
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
void MapClaim(string destinationClaimType, string sourceClaimType, string? alternativeSourceClaimType = null) void MapClaim(string name, string type, ReadOnlySpan<string> names)
{ {
if (context.MergedPrincipal.HasClaim(destinationClaimType)) // Do not map the claim if the claim is already present in the merged principal (e.g because it was
// returned by the identity provider or because it was manually added from a custom event handler).
if (context.MergedPrincipal.HasClaim(name))
{ {
return; return;
} }
var claim = context.MergedPrincipal.GetClaim(sourceClaimType);
if (claim == null && alternativeSourceClaimType != null) // Use the first claim that matches one of the provided claim types.
for (var index = 0; index < names.Length; index++)
{ {
claim = context.MergedPrincipal.GetClaim(alternativeSourceClaimType); if (context.MergedPrincipal.FindFirst(names[index]) is Claim claim)
{
identity.AddClaim(new Claim(name, claim.Value, type, issuer, issuer, identity));
return;
}
} }
context.MergedPrincipal.SetClaim(destinationClaimType, claim, issuer);
} }
} }
} }
@ -8039,20 +8050,44 @@ public static partial class OpenIddictClientHandlers
// WS-Federation equivalent, this handler is responsible for mapping the standard OAuth 2.0 introspection nodes // 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. // defined by https://datatracker.ietf.org/doc/html/rfc7662#section-2.2 to their WS-Federation equivalent.
if (context.Principal.Identity is not ClaimsIdentity identity)
{
return ValueTask.CompletedTask;
}
var issuer = context.Registration.ClaimsIssuer ?? var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ?? context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri; context.Registration.Issuer.AbsoluteUri;
context.Principal MapClaim(ClaimTypes.Name, ClaimValueTypes.String, [Claims.Username]);
.SetClaim(ClaimTypes.Name, context.Principal.GetClaim(Claims.Username), issuer) MapClaim(ClaimTypes.NameIdentifier, ClaimValueTypes.String, [Claims.Subject]);
.SetClaim(ClaimTypes.NameIdentifier, context.Principal.GetClaim(Claims.Subject), issuer);
// Note: while this claim is not exposed by the BCL ClaimTypes class, it is used by both ASP.NET Identity // Note: while this claim is not exposed by the BCL ClaimTypes class, it is used by both ASP.NET Identity
// for ASP.NET 4.x and the System.Web.WebPages package, that requires it for antiforgery to work correctly. // for ASP.NET 4.x and the System.Web.WebPages package, that requires it for antiforgery to work correctly.
context.Principal.SetClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", MapClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
context.Principal.GetClaim(Claims.Private.ProviderName)); ClaimValueTypes.String, [Claims.Private.ProviderName]);
return ValueTask.CompletedTask; return ValueTask.CompletedTask;
void MapClaim(string name, string type, ReadOnlySpan<string> names)
{
// Do not map the claim if the claim is already present in the merged principal (e.g because it was
// returned by the identity provider or because it was manually added from a custom event handler).
if (context.Principal.HasClaim(name))
{
return;
}
// Use the first claim that matches one of the provided claim types.
for (var index = 0; index < names.Length; index++)
{
if (context.Principal.FindFirst(names[index]) is Claim claim)
{
identity.AddClaim(new Claim(name, claim.Value, type, issuer, issuer, identity));
return;
}
}
}
} }
} }

Loading…
Cancel
Save