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();
return new AuthenticationTicket(context.MergedPrincipal?.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties);
return new AuthenticationTicket(context.MergedPrincipal.Identity as ClaimsIdentity ?? new ClaimsIdentity(), properties);
}
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
// the standard OpenID Connect claim types (see MapStandardWebServicesFederationClaims).
if (context.MergedPrincipal.Identity is not ClaimsIdentity identity)
{
return ValueTask.CompletedTask;
}
var issuer = context.Registration.ClaimsIssuer ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
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:
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:
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))
{
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:
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:
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))
{
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:
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:
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;

75
src/OpenIddict.Client/OpenIddictClientHandlers.cs

@ -10,6 +10,7 @@ using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using System.Text.Json;
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
// 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 ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
MapClaim(ClaimTypes.Email, Claims.Email);
MapClaim(ClaimTypes.Gender, Claims.Gender);
MapClaim(ClaimTypes.GivenName, Claims.GivenName);
MapClaim(ClaimTypes.Name, Claims.PreferredUsername, Claims.Name);
MapClaim(ClaimTypes.NameIdentifier, Claims.Subject);
MapClaim(ClaimTypes.OtherPhone, Claims.PhoneNumber);
MapClaim(ClaimTypes.Surname, Claims.FamilyName);
MapClaim(ClaimTypes.Email, ClaimValueTypes.String, [Claims.Email]);
MapClaim(ClaimTypes.Gender, ClaimValueTypes.String, [Claims.Gender]);
MapClaim(ClaimTypes.GivenName, ClaimValueTypes.String, [Claims.GivenName]);
MapClaim(ClaimTypes.Name, ClaimValueTypes.String, [Claims.PreferredUsername, Claims.Name]);
MapClaim(ClaimTypes.NameIdentifier, ClaimValueTypes.String, [Claims.Subject]);
MapClaim(ClaimTypes.OtherPhone, ClaimValueTypes.String, [Claims.PhoneNumber]);
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
// 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",
Claims.Private.ProviderName);
ClaimValueTypes.String, [Claims.Private.ProviderName]);
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;
}
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
// 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 ??
context.Registration.ProviderName ??
context.Registration.Issuer.AbsoluteUri;
context.Principal
.SetClaim(ClaimTypes.Name, context.Principal.GetClaim(Claims.Username), issuer)
.SetClaim(ClaimTypes.NameIdentifier, context.Principal.GetClaim(Claims.Subject), issuer);
MapClaim(ClaimTypes.Name, ClaimValueTypes.String, [Claims.Username]);
MapClaim(ClaimTypes.NameIdentifier, ClaimValueTypes.String, [Claims.Subject]);
// 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.
context.Principal.SetClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
context.Principal.GetClaim(Claims.Private.ProviderName));
MapClaim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
ClaimValueTypes.String, [Claims.Private.ProviderName]);
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