diff --git a/src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj b/src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj index a520be5..27fc6a6 100644 --- a/src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj +++ b/src/ITfoxtec.Identity.Saml2.Mvc/ITfoxtec.Identity.Saml2.Mvc.csproj @@ -26,10 +26,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) and Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC en-US https://itfoxtec.com/favicon.ico - 4.10.8.0 - 4.10.8.0 + 4.10.9.1 + 4.10.9.1 Copyright © 2023 - 4.10.8.0 + 4.10.9-beta1 true ITfoxtec.SAML2.snk false diff --git a/src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj b/src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj index a85490b..be88200 100644 --- a/src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj +++ b/src/ITfoxtec.Identity.Saml2.MvcCore/ITfoxtec.Identity.Saml2.MvcCore.csproj @@ -31,10 +31,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 ASP.NET MVC Core en-US https://itfoxtec.com/favicon.ico - 4.10.8.0 - 4.10.8.0 + 4.10.9.1 + 4.10.9.1 Copyright © 2023 - 4.10.8.0 + 4.10.9-beta1 true ITfoxtec.SAML2.snk false diff --git a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2ArtifactBinding.cs b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2ArtifactBinding.cs index 1c90dd7..e83b334 100644 --- a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2ArtifactBinding.cs +++ b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2ArtifactBinding.cs @@ -22,7 +22,7 @@ public Saml2ArtifactBinding() CertificateIncludeOption = X509IncludeOption.EndCertOnly; } - protected internal override void BindInternal(Saml2Request saml2Request, string messageName) + protected override void BindInternal(Saml2Request saml2Request, string messageName) { if (!(saml2Request is Saml2ArtifactResolve saml2ArtifactResolve)) throw new ArgumentException("Only Saml2ArtifactResolve is supported"); diff --git a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2Binding.cs b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2Binding.cs index 040543d..71537a7 100644 --- a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2Binding.cs +++ b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2Binding.cs @@ -28,7 +28,7 @@ public abstract class Saml2Binding public Saml2Binding() { } - protected internal virtual void BindInternal(Saml2Request saml2RequestResponse, bool createXml = true) + protected virtual void BindInternal(Saml2Request saml2RequestResponse, bool createXml = true) { if (saml2RequestResponse == null) throw new ArgumentNullException(nameof(saml2RequestResponse)); @@ -54,7 +54,12 @@ protected internal virtual void BindInternal(Saml2Request saml2RequestResponse, } } - protected internal abstract void BindInternal(Saml2Request saml2RequestResponse, string messageName); + internal void ApplyBinding(Saml2Request saml2RequestResponse, string messageName) + { + BindInternal(saml2RequestResponse, messageName); + } + + protected abstract void BindInternal(Saml2Request saml2RequestResponse, string messageName); public Saml2Request Unbind(HttpRequest request, Saml2Request saml2Request) { diff --git a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2PostBinding.cs b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2PostBinding.cs index 272c5ab..798f687 100644 --- a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2PostBinding.cs +++ b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2PostBinding.cs @@ -28,7 +28,7 @@ public Saml2PostBinding() CertificateIncludeOption = X509IncludeOption.EndCertOnly; } - protected internal override void BindInternal(Saml2Request saml2RequestResponse, string messageName) + protected override void BindInternal(Saml2Request saml2RequestResponse, string messageName) { BindInternal(saml2RequestResponse); diff --git a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2RedirectBinding.cs b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2RedirectBinding.cs index a5f0ebe..24aece1 100644 --- a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2RedirectBinding.cs +++ b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2RedirectBinding.cs @@ -19,7 +19,7 @@ public class Saml2RedirectBinding : Saml2Binding public string Signature { get; protected set; } - protected internal override void BindInternal(Saml2Request saml2RequestResponse, string messageName) + protected override void BindInternal(Saml2Request saml2RequestResponse, string messageName) { base.BindInternal(saml2RequestResponse); diff --git a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2SoapEnvelope.cs b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2SoapEnvelope.cs index 7e75f4e..2991746 100644 --- a/src/ITfoxtec.Identity.Saml2/Bindings/Saml2SoapEnvelope.cs +++ b/src/ITfoxtec.Identity.Saml2/Bindings/Saml2SoapEnvelope.cs @@ -21,7 +21,7 @@ public class Saml2SoapEnvelope : Saml2Binding /// public string SoapResponseXml { get; set; } - protected internal override void BindInternal(Saml2Request saml2Request, string messageName) + protected override void BindInternal(Saml2Request saml2Request, string messageName) { if (!(saml2Request is Saml2ArtifactResponse)) throw new ArgumentException("Only Saml2ArtifactResponse is supported"); diff --git a/src/ITfoxtec.Identity.Saml2/Claims/Saml2ClaimTypes.cs b/src/ITfoxtec.Identity.Saml2/Claims/Saml2ClaimTypes.cs index 5002db3..391157b 100644 --- a/src/ITfoxtec.Identity.Saml2/Claims/Saml2ClaimTypes.cs +++ b/src/ITfoxtec.Identity.Saml2/Claims/Saml2ClaimTypes.cs @@ -5,6 +5,8 @@ public static class Saml2ClaimTypes { public const string NameId = "http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2nameid"; public const string NameIdFormat = "http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2nameidformat"; + public const string NameQualifier = "http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2namequalifier"; + public const string SPNameQualifier = "http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2spnamequalifier"; public const string SessionIndex = "http://schemas.itfoxtec.com/ws/2014/02/identity/claims/saml2sessionindex"; } } diff --git a/src/ITfoxtec.Identity.Saml2/Extensions/Saml2BindingExtensions.cs b/src/ITfoxtec.Identity.Saml2/Extensions/Saml2BindingExtensions.cs index 96db960..6dc67a3 100644 --- a/src/ITfoxtec.Identity.Saml2/Extensions/Saml2BindingExtensions.cs +++ b/src/ITfoxtec.Identity.Saml2/Extensions/Saml2BindingExtensions.cs @@ -10,19 +10,19 @@ public static class Saml2BindingExtensions { public static T Bind(this T binding, Saml2Request saml2Request) where T : Saml2Binding { - binding.BindInternal(saml2Request, Saml2Constants.Message.SamlRequest); + binding.ApplyBinding(saml2Request, Saml2Constants.Message.SamlRequest); return binding; } public static T Bind(this T binding, Saml2Response saml2Response) where T : Saml2Binding { - binding.BindInternal(saml2Response, Saml2Constants.Message.SamlResponse); + binding.ApplyBinding(saml2Response, Saml2Constants.Message.SamlResponse); return binding; } public static T Bind(this T binding, Saml2ArtifactResolve saml2ArtifactResolve) where T : Saml2Binding { - binding.BindInternal(saml2ArtifactResolve, Saml2Constants.Message.SamlArt); + binding.ApplyBinding(saml2ArtifactResolve, Saml2Constants.Message.SamlArt); return binding; } diff --git a/src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj b/src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj index 724574b..da0fbc1 100644 --- a/src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj +++ b/src/ITfoxtec.Identity.Saml2/ITfoxtec.Identity.Saml2.csproj @@ -32,10 +32,10 @@ Support the Danish NemLog-in 2 / OIOSAML 2 and NemLog-in 3 / OIOSAML 3.SAML SAML 2.0 SAML2.0 SAML2 SAML 2 SAML-P SAMLP SSO Identity Provider (IdP) Relying Party (RP) Authentication Metadata OIOSAML OIOSAML 2 OIOSAML 3 NemLogin NemLog-in 2 NemLog-in 3 en-US https://itfoxtec.com/favicon.ico - 4.10.8.0 - 4.10.8.0 + 4.10.9.1 + 4.10.9.1 Copyright © 2023 - 4.10.8.0 + 4.10.9-beta1 true ITfoxtec.SAML2.snk false diff --git a/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnRequest.cs b/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnRequest.cs index 2e30b23..e9d27a0 100644 --- a/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnRequest.cs +++ b/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnRequest.cs @@ -112,10 +112,19 @@ public class Saml2AuthnRequest : Saml2Request /// /// [Optional] /// If present, specifies an Audience - /// Part of the OIOSAML standard used for conditions on request. + /// Specifies the SAML conditions the requester expects to limit the validity and/or use of the resulting + /// assertion(s). /// public Condition Conditions { get; set; } + /// + /// [Optional] + /// Specifies a set of identity providers trusted by the requester to authenticate the presenter, as well as + /// limitations and context related to proxying of the <AuthnRequest> message to subsequent identity + /// providers by the responder. + /// + public Scoping Scoping { get; set; } + public Saml2AuthnRequest(Saml2Configuration config) : base(config) { if (config == null) throw new ArgumentNullException(nameof(config)); @@ -185,6 +194,11 @@ protected override IEnumerable GetXContent() { yield return RequestedAuthnContext.ToXElement(); } + + if (Scoping != null) + { + yield return Scoping.ToXElement(); + } } protected internal override void Read(string xml, bool validate = false, bool detectReplayedTokens = true) @@ -208,6 +222,8 @@ protected internal override void Read(string xml, bool validate = false, bool de NameIdPolicy = XmlDocument.DocumentElement[Saml2Constants.Message.NameIdPolicy, Saml2Constants.ProtocolNamespace.OriginalString].GetElementOrNull(); RequestedAuthnContext = XmlDocument.DocumentElement[Saml2Constants.Message.RequestedAuthnContext, Saml2Constants.ProtocolNamespace.OriginalString].GetElementOrNull(); + + Scoping = XmlDocument.DocumentElement[Saml2Constants.Message.Scoping, Saml2Constants.ProtocolNamespace.OriginalString].GetElementOrNull(); } protected override void ValidateElementName() diff --git a/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnResponse.cs b/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnResponse.cs index 93f34df..e3d69c8 100644 --- a/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnResponse.cs +++ b/src/ITfoxtec.Identity.Saml2/Request/Saml2AuthnResponse.cs @@ -284,7 +284,7 @@ protected override XmlElement GetAssertionElement() return assertionElementCache; } - private XmlElement GetAssertionElementReference() + protected XmlElement GetAssertionElementReference() { var assertionElements = XmlDocument.DocumentElement.SelectNodes($"//*[local-name()='{Schemas.Saml2Constants.Message.Assertion}']"); if (assertionElements.Count != 1) diff --git a/src/ITfoxtec.Identity.Saml2/Request/Saml2LogoutRequest.cs b/src/ITfoxtec.Identity.Saml2/Request/Saml2LogoutRequest.cs index bbb1d01..e984fc4 100644 --- a/src/ITfoxtec.Identity.Saml2/Request/Saml2LogoutRequest.cs +++ b/src/ITfoxtec.Identity.Saml2/Request/Saml2LogoutRequest.cs @@ -57,6 +57,16 @@ public Saml2LogoutRequest(Saml2Configuration config, ClaimsPrincipal currentPrin NameId = new Saml2NameIdentifier(ReadClaimValue(identity, Saml2ClaimTypes.NameId), new Uri(nameIdFormat)); } + var nameIdNameQualifier = ReadClaimValue(identity, Saml2ClaimTypes.NameQualifier, false); + if (!string.IsNullOrEmpty(nameIdNameQualifier)) + { + NameId.NameQualifier = nameIdNameQualifier; + } + var nameIdSPNameQualifier = ReadClaimValue(identity, Saml2ClaimTypes.SPNameQualifier, false); + if (!string.IsNullOrEmpty(nameIdSPNameQualifier)) + { + NameId.SPNameQualifier = nameIdSPNameQualifier; + } SessionIndex = ReadClaimValue(identity, Saml2ClaimTypes.SessionIndex, false); } } @@ -103,15 +113,20 @@ protected override IEnumerable GetXContent() if (NameId != null) { - object[] nameIdContent; + var nameIdContent = new List() { NameId.Value }; if (NameId.Format != null) { - nameIdContent = new object[] { NameId.Value, new XAttribute(Schemas.Saml2Constants.Message.Format, NameId.Format) }; + nameIdContent.Add(new XAttribute(Schemas.Saml2Constants.Message.Format, NameId.Format)); } - else + if (NameId.NameQualifier != null) + { + nameIdContent.Add(new XAttribute(Schemas.Saml2Constants.Message.NameQualifier, NameId.NameQualifier)); + } + if (NameId.SPNameQualifier != null) { - nameIdContent = new object[] { NameId.Value }; + nameIdContent.Add(new XAttribute(Schemas.Saml2Constants.Message.SpNameQualifier, NameId.SPNameQualifier)); } + yield return new XElement(Schemas.Saml2Constants.AssertionNamespaceX + Schemas.Saml2Constants.Message.NameId, nameIdContent); } @@ -126,6 +141,8 @@ protected internal override void Read(string xml, bool validate = false, bool de base.Read(xml, validate, detectReplayedTokens); NameId = XmlDocument.DocumentElement[Schemas.Saml2Constants.Message.NameId, Schemas.Saml2Constants.AssertionNamespace.OriginalString].GetValueOrNull(); + NameId.NameQualifier = XmlDocument.DocumentElement[Schemas.Saml2Constants.Message.NameId, Schemas.Saml2Constants.AssertionNamespace.OriginalString].GetAttribute(Schemas.Saml2Constants.Message.NameQualifier); + NameId.SPNameQualifier = XmlDocument.DocumentElement[Schemas.Saml2Constants.Message.NameId, Schemas.Saml2Constants.AssertionNamespace.OriginalString].GetAttribute(Schemas.Saml2Constants.Message.SpNameQualifier); SessionIndex = XmlDocument.DocumentElement[Schemas.Saml2Constants.Message.SessionIndex, Schemas.Saml2Constants.ProtocolNamespace.OriginalString].GetValueOrNull(); } diff --git a/src/ITfoxtec.Identity.Saml2/Schemas/Condition.cs b/src/ITfoxtec.Identity.Saml2/Schemas/Condition.cs index f87dff9..fa8a990 100644 --- a/src/ITfoxtec.Identity.Saml2/Schemas/Condition.cs +++ b/src/ITfoxtec.Identity.Saml2/Schemas/Condition.cs @@ -16,10 +16,22 @@ public class Condition /// public const string elementName = Saml2Constants.Message.Conditions; - public List Items { get; set; } + /// + /// [Any Number] + /// A condition of a type defined in an extension schema. + /// + public IEnumerable Items { get; set; } + /// + /// [Optional] + /// Specifies the earliest time instant at which the assertion is valid. + /// public DateTimeOffset? NotOnOrAfter { get; set; } + /// + /// [Optional] + /// Specifies the time instant at which the assertion has expired. + /// public DateTimeOffset? NotBefore { get; set; } public XElement ToXElement() diff --git a/src/ITfoxtec.Identity.Saml2/Schemas/IDPEntry.cs b/src/ITfoxtec.Identity.Saml2/Schemas/IDPEntry.cs new file mode 100644 index 0000000..b192a40 --- /dev/null +++ b/src/ITfoxtec.Identity.Saml2/Schemas/IDPEntry.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace ITfoxtec.Identity.Saml2.Schemas +{ + /// + /// The IDPEntry element specifies a single identity provider trusted by the requester to authenticate the + /// presenter.Its IDPEntryType complex type defines the following attributes: + /// + public class IDPEntry + { + public const string elementName = Saml2Constants.Message.IDPEntry; + + /// + /// [Required] + /// The unique identifier of the identity provider.See Section 8.3.6 for a description of such identifiers. + /// + public string ProviderID { get; set; } + + /// + /// [Optional] + /// A human-readable name for the identity provider. + /// + public string Name { get; set; } + + /// + /// [Optional] + /// A URI reference representing the location of a profile-specific endpoint supporting the authentication + /// request protocol.The binding to be used must be understood from the profile of use. + /// + public string Loc { get; set; } + + public XElement ToXElement() + { + var envelope = new XElement(Saml2Constants.AssertionNamespaceX + elementName); + + envelope.Add(GetXContent()); + + return envelope; + } + + protected virtual IEnumerable GetXContent() + { + yield return new XAttribute(Saml2Constants.AssertionNamespaceNameX, Saml2Constants.AssertionNamespaceX); + + if (ProviderID != null) + { + yield return new XAttribute(Saml2Constants.Message.ProviderID, ProviderID); + } + + if (Name != null) + { + yield return new XAttribute(Saml2Constants.Message.Name, Name); + } + + if (Loc != null) + { + yield return new XAttribute(Saml2Constants.Message.Loc, Loc); + } + } + } +} \ No newline at end of file diff --git a/src/ITfoxtec.Identity.Saml2/Schemas/IDPList.cs b/src/ITfoxtec.Identity.Saml2/Schemas/IDPList.cs new file mode 100644 index 0000000..25f0dac --- /dev/null +++ b/src/ITfoxtec.Identity.Saml2/Schemas/IDPList.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace ITfoxtec.Identity.Saml2.Schemas +{ + /// + /// The element specifies the identity providers trusted by the requester to authenticate the presenter. + /// + public class IDPList + { + public const string elementName = Saml2Constants.Message.IDPList; + + /// + /// [One or More] + /// Information about a single identity provider. + /// + public IEnumerable IDPEntry { get; set; } + + /// + /// [Optional] + /// If the IDPList is not complete, using this element specifies a URI reference that can be used to + /// retrieve the complete list. + /// + public string GetComplete { get; set; } + + public XElement ToXElement() + { + var envelope = new XElement(Saml2Constants.AssertionNamespaceX + elementName); + + envelope.Add(GetXContent()); + + return envelope; + } + + protected virtual IEnumerable GetXContent() + { + yield return new XAttribute(Saml2Constants.AssertionNamespaceNameX, Saml2Constants.AssertionNamespaceX); + + if (GetComplete != null) + { + yield return new XAttribute(Saml2Constants.Message.GetComplete, GetComplete); + } + + if (IDPEntry != null) + { + foreach (var entry in IDPEntry) + { + yield return entry.ToXElement(); + } + } + } + } +} \ No newline at end of file diff --git a/src/ITfoxtec.Identity.Saml2/Schemas/Saml2Constants.cs b/src/ITfoxtec.Identity.Saml2/Schemas/Saml2Constants.cs index d1c58ad..e07aadd 100644 --- a/src/ITfoxtec.Identity.Saml2/Schemas/Saml2Constants.cs +++ b/src/ITfoxtec.Identity.Saml2/Schemas/Saml2Constants.cs @@ -166,6 +166,8 @@ public static class Message internal const string AllowCreate = "AllowCreate"; + internal const string NameQualifier = "NameQualifier"; + internal const string SpNameQualifier = "SPNameQualifier"; internal const string Extensions = "Extensions"; @@ -193,6 +195,22 @@ public static class Message internal const string Envelope = "Envelope"; internal const string Body = "Body"; + + internal const string Scoping = "Scoping"; + + internal const string RequesterID = "RequesterID"; + + internal const string IDPList = "IDPList"; + + internal const string IDPEntry = "IDPEntry"; + + internal const string ProviderID = "ProviderID"; + + internal const string Name = "Name"; + + internal const string Loc = "Loc"; + + internal const string GetComplete = "GetComplete"; } } } diff --git a/src/ITfoxtec.Identity.Saml2/Schemas/Scoping.cs b/src/ITfoxtec.Identity.Saml2/Schemas/Scoping.cs new file mode 100644 index 0000000..61d00fd --- /dev/null +++ b/src/ITfoxtec.Identity.Saml2/Schemas/Scoping.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; +using System.Xml.Linq; + +namespace ITfoxtec.Identity.Saml2.Schemas +{ + /// + /// The element specifies the identity providers trusted by the requester to authenticate the + /// presenter, as well as limitations and context related to proxying of the AuthnRequest message to + /// subsequent identity providers by the responder. + /// + public class Scoping + { + /// + /// The XML Element name of this class + /// + public const string elementName = Saml2Constants.Message.Scoping; + + /// + /// [Optional] + /// An advisory list of identity providers and associated information that the requester deems acceptable + /// to respond to the request. + /// + public IDPList IDPList { get; set; } + + /// + /// [Zero or More] + /// Identifies the set of requesting entities on whose behalf the requester is acting. Used to communicate + /// the chain of requesters when proxying occurs. + /// + public IEnumerable RequesterID { get; set; } + + public XElement ToXElement() + { + var envelope = new XElement(Saml2Constants.AssertionNamespaceX + elementName); + + envelope.Add(GetXContent()); + + return envelope; + } + + protected virtual IEnumerable GetXContent() + { + yield return new XAttribute(Saml2Constants.AssertionNamespaceNameX, Saml2Constants.AssertionNamespaceX); + + if (RequesterID != null) + { + foreach (var item in RequesterID) + { + yield return new XAttribute(Saml2Constants.Message.RequesterID, item); + } + } + + if (IDPList != null) + { + yield return IDPList.ToXElement(); + } + } + } +} \ No newline at end of file diff --git a/src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs b/src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs index 64b77e7..ebad24a 100644 --- a/src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs +++ b/src/ITfoxtec.Identity.Saml2/Tokens/Saml2ResponseSecurityTokenHandler.cs @@ -94,6 +94,14 @@ public ReadOnlyCollection ValidateToken(SecurityToken token, str { identity.AddClaim(new Claim(Saml2ClaimTypes.NameIdFormat, saml2Response.NameId.Format.OriginalString)); } + if (saml2Response.NameId.NameQualifier != null) + { + identity.AddClaim(new Claim(Saml2ClaimTypes.NameQualifier, saml2Response.NameId.NameQualifier)); + } + if (saml2Response.NameId.SPNameQualifier != null) + { + identity.AddClaim(new Claim(Saml2ClaimTypes.SPNameQualifier, saml2Response.NameId.SPNameQualifier)); + } } var sessionIndex = (saml2SecurityToken.Assertion.Statements.Where(s => s is Saml2AuthenticationStatement).FirstOrDefault() as Saml2AuthenticationStatement)?.SessionIndex; diff --git a/test/TestIdPCore/Controllers/AuthController.cs b/test/TestIdPCore/Controllers/AuthController.cs index db968fd..a2e1107 100644 --- a/test/TestIdPCore/Controllers/AuthController.cs +++ b/test/TestIdPCore/Controllers/AuthController.cs @@ -167,8 +167,13 @@ private IActionResult LoginPostResponse(Saml2Id inResponseTo, Saml2StatusCodes s saml2AuthnResponse.SessionIndex = sessionIndex; var claimsIdentity = new ClaimsIdentity(claims); - saml2AuthnResponse.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single(), NameIdentifierFormats.Persistent); //saml2AuthnResponse.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single()); + saml2AuthnResponse.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single(), NameIdentifierFormats.Persistent); + //saml2AuthnResponse.NameId = new Saml2NameIdentifier(claimsIdentity.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).Select(c => c.Value).Single(), NameIdentifierFormats.Persistent) + //{ + // NameQualifier = "somedomain.com", + // SPNameQualifier = "sub.somedomain.com" + //}; saml2AuthnResponse.ClaimsIdentity = claimsIdentity; diff --git a/test/TestWebApp/Identity/DefaultClaimsAuthenticationManager.cs b/test/TestWebApp/Identity/DefaultClaimsAuthenticationManager.cs index 5808126..2a3b064 100644 --- a/test/TestWebApp/Identity/DefaultClaimsAuthenticationManager.cs +++ b/test/TestWebApp/Identity/DefaultClaimsAuthenticationManager.cs @@ -38,6 +38,8 @@ private IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal) { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCore/Identity/ClaimsTransform.cs b/test/TestWebAppCore/Identity/ClaimsTransform.cs index cb07667..dea43ff 100644 --- a/test/TestWebAppCore/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCore/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCoreAngularApi/Identity/ClaimsTransform.cs b/test/TestWebAppCoreAngularApi/Identity/ClaimsTransform.cs index 471d924..bf59523 100644 --- a/test/TestWebAppCoreAngularApi/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCoreAngularApi/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCoreArtifact/Identity/ClaimsTransform.cs b/test/TestWebAppCoreArtifact/Identity/ClaimsTransform.cs index 5656c66..48efebc 100644 --- a/test/TestWebAppCoreArtifact/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCoreArtifact/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCoreAzureKeyVault/Identity/ClaimsTransform.cs b/test/TestWebAppCoreAzureKeyVault/Identity/ClaimsTransform.cs index 97c0af4..6b12557 100644 --- a/test/TestWebAppCoreAzureKeyVault/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCoreAzureKeyVault/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCoreFramework/Identity/ClaimsTransform.cs b/test/TestWebAppCoreFramework/Identity/ClaimsTransform.cs index b8b188b..191c2fd 100644 --- a/test/TestWebAppCoreFramework/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCoreFramework/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); } diff --git a/test/TestWebAppCoreNemLogin3Sp/Identity/ClaimsTransform.cs b/test/TestWebAppCoreNemLogin3Sp/Identity/ClaimsTransform.cs index ca370d9..8a35f4e 100644 --- a/test/TestWebAppCoreNemLogin3Sp/Identity/ClaimsTransform.cs +++ b/test/TestWebAppCoreNemLogin3Sp/Identity/ClaimsTransform.cs @@ -38,6 +38,8 @@ private static IEnumerable GetSaml2LogoutClaims(ClaimsPrincipal principal { yield return GetClaim(principal, Saml2ClaimTypes.NameId); yield return GetClaim(principal, Saml2ClaimTypes.NameIdFormat); + yield return GetClaim(principal, Saml2ClaimTypes.NameQualifier); + yield return GetClaim(principal, Saml2ClaimTypes.SPNameQualifier); yield return GetClaim(principal, Saml2ClaimTypes.SessionIndex); }