You've already forked linux-packaging-mono
Rewrite with hard-coded offsets into the PE file format to discern if a binary is PE32 or PE32+, and then to determine if it contains a "CLR Data Directory" entry that looks valid. Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native binaries, and a random assortment of garbage files. Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
3720 lines
168 KiB
C#
3720 lines
168 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
|
|
namespace System.IdentityModel.Tokens
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Globalization;
|
|
using System.IdentityModel.Configuration;
|
|
using System.IdentityModel.Diagnostics;
|
|
using System.IdentityModel.Protocols.WSTrust;
|
|
using System.IdentityModel.Selectors;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime;
|
|
using System.Security.Claims;
|
|
using System.Security.Cryptography;
|
|
using System.Security.Principal;
|
|
using System.Text;
|
|
using System.Xml;
|
|
using System.Xml.Schema;
|
|
using Claim = System.Security.Claims.Claim;
|
|
using ClaimTypes = System.Security.Claims.ClaimTypes;
|
|
|
|
/// <summary>
|
|
/// This class implements a SecurityTokenHandler for a Saml11 token. It contains functionality for: Creating, Serializing and Validating
|
|
/// a Saml 11 Token.
|
|
/// </summary>
|
|
public class SamlSecurityTokenHandler : SecurityTokenHandler
|
|
{
|
|
#pragma warning disable 1591
|
|
public const string Namespace = "urn:oasis:names:tc:SAML:1.0";
|
|
public const string BearerConfirmationMethod = Namespace + ":cm:bearer";
|
|
public const string UnspecifiedAuthenticationMethod = Namespace + ":am:unspecified";
|
|
public const string Assertion = Namespace + ":assertion";
|
|
#pragma warning restore 1591
|
|
|
|
const string Attribute = "saml:Attribute";
|
|
const string Actor = "Actor";
|
|
const string ClaimType2009Namespace = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims";
|
|
|
|
// Below are WCF DateTime values for Min and Max. SamlConditions when new'ed up will
|
|
// have these values as default. To maintin compatability with WCF behavior we will
|
|
// not write out SamlConditions NotBefore and NotOnOrAfter times which match the below
|
|
// values.
|
|
static DateTime WCFMinValue = new DateTime(DateTime.MinValue.Ticks + TimeSpan.TicksPerDay, DateTimeKind.Utc);
|
|
static DateTime WCFMaxValue = new DateTime(DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay, DateTimeKind.Utc);
|
|
|
|
static string[] _tokenTypeIdentifiers = new string[] { SecurityTokenTypes.SamlTokenProfile11, SecurityTokenTypes.OasisWssSamlTokenProfile11 };
|
|
|
|
SamlSecurityTokenRequirement _samlSecurityTokenRequirement;
|
|
|
|
SecurityTokenSerializer _keyInfoSerializer;
|
|
|
|
object _syncObject = new object();
|
|
|
|
/// <summary>
|
|
/// Initializes an instance of <see cref="SamlSecurityTokenHandler"/>
|
|
/// </summary>
|
|
public SamlSecurityTokenHandler()
|
|
: this(new SamlSecurityTokenRequirement())
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes an instance of <see cref="SamlSecurityTokenHandler"/>
|
|
/// </summary>
|
|
/// <param name="samlSecurityTokenRequirement">The SamlSecurityTokenRequirement to be used by the Saml11SecurityTokenHandler instance when validating tokens.</param>
|
|
public SamlSecurityTokenHandler(SamlSecurityTokenRequirement samlSecurityTokenRequirement)
|
|
{
|
|
if (samlSecurityTokenRequirement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityTokenRequirement");
|
|
}
|
|
_samlSecurityTokenRequirement = samlSecurityTokenRequirement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load custom configuration from Xml
|
|
/// </summary>
|
|
/// <param name="customConfigElements">Custom configuration that describes SamlSecurityTokenRequirement.</param>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'customConfigElements' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Custom configuration specified was invalid.</exception>
|
|
public override void LoadCustomConfiguration(XmlNodeList customConfigElements)
|
|
{
|
|
if (customConfigElements == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("customConfigElements");
|
|
}
|
|
|
|
List<XmlElement> configNodes = XmlUtil.GetXmlElements(customConfigElements);
|
|
|
|
bool foundValidConfig = false;
|
|
|
|
foreach (XmlElement configElement in configNodes)
|
|
{
|
|
if (configElement.LocalName != ConfigurationStrings.SamlSecurityTokenRequirement)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (foundValidConfig)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID7026, ConfigurationStrings.SamlSecurityTokenRequirement));
|
|
}
|
|
|
|
_samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(configElement);
|
|
|
|
foundValidConfig = true;
|
|
}
|
|
|
|
if (!foundValidConfig)
|
|
{
|
|
_samlSecurityTokenRequirement = new SamlSecurityTokenRequirement();
|
|
}
|
|
}
|
|
|
|
#region TokenCreation
|
|
|
|
/// <summary>
|
|
/// Creates the security token based on the tokenDescriptor passed in.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The security token descriptor that contains the information to build a token.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'tokenDescriptor' is null.</exception>
|
|
public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (tokenDescriptor == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
IEnumerable<SamlStatement> statements = CreateStatements(tokenDescriptor);
|
|
|
|
// - NotBefore / NotAfter
|
|
// - Audience Restriction
|
|
SamlConditions conditions = CreateConditions(tokenDescriptor.Lifetime, tokenDescriptor.AppliesToAddress, tokenDescriptor);
|
|
|
|
SamlAdvice advice = CreateAdvice(tokenDescriptor);
|
|
|
|
string issuerName = tokenDescriptor.TokenIssuerName;
|
|
|
|
SamlAssertion assertion = CreateAssertion(issuerName, conditions, advice, statements);
|
|
if (assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4013)));
|
|
}
|
|
|
|
assertion.SigningCredentials = GetSigningCredentials(tokenDescriptor);
|
|
|
|
SecurityToken token = new SamlSecurityToken(assertion);
|
|
|
|
//
|
|
// Encrypt the token if encrypting credentials are set
|
|
//
|
|
|
|
EncryptingCredentials encryptingCredentials = GetEncryptingCredentials(tokenDescriptor);
|
|
if (encryptingCredentials != null)
|
|
{
|
|
token = new EncryptedSecurityToken(token, encryptingCredentials);
|
|
}
|
|
|
|
return token;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the credentials for encrypting the token. Override this method to provide custom encrypting credentials.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The Scope property provides access to the encrypting credentials.</param>
|
|
/// <returns>The token encrypting credentials.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'tokenDescriptor' is null.</exception>
|
|
/// <remarks>The default behavior is to return the SecurityTokenDescriptor.Scope.EncryptingCredentials
|
|
/// If this key is ----ymmetric, a symmetric key will be generated and wrapped with the asymmetric key.</remarks>
|
|
protected virtual EncryptingCredentials GetEncryptingCredentials(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
EncryptingCredentials encryptingCredentials = null;
|
|
|
|
if (null != tokenDescriptor.EncryptingCredentials)
|
|
{
|
|
encryptingCredentials = tokenDescriptor.EncryptingCredentials;
|
|
|
|
if (encryptingCredentials.SecurityKey is AsymmetricSecurityKey)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenException(SR.GetString(SR.ID4178)));
|
|
}
|
|
}
|
|
|
|
return encryptingCredentials;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the credentials for the signing the assertion. Override this method to provide custom signing credentials.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The Scope property provides access to the signing credentials.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'tokenDescriptor' is null.</exception>
|
|
/// <returns>The assertion signing credentials.</returns>
|
|
/// <remarks>The default behavior is to return the SecurityTokenDescriptor.Scope.SigningCredentials.</remarks>
|
|
protected virtual SigningCredentials GetSigningCredentials(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
return tokenDescriptor.SigningCredentials;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this method to provide a SamlAdvice to place in the Samltoken.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">Contains informaiton about the token.</param>
|
|
/// <returns>SamlAdvice, default is null.</returns>
|
|
protected virtual SamlAdvice CreateAdvice(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this method to customize the parameters to create a SamlAssertion.
|
|
/// </summary>
|
|
/// <param name="issuer">The Issuer of the Assertion.</param>
|
|
/// <param name="conditions">The SamlConditions to add.</param>
|
|
/// <param name="advice">The SamlAdvice to add.</param>
|
|
/// <param name="statements">The SamlStatements to add.</param>
|
|
/// <returns>A SamlAssertion.</returns>
|
|
/// <remarks>A unique random id is created for the assertion
|
|
/// IssueInstance is set to DateTime.UtcNow.</remarks>
|
|
protected virtual SamlAssertion CreateAssertion(string issuer, SamlConditions conditions, SamlAdvice advice, IEnumerable<SamlStatement> statements)
|
|
{
|
|
return new SamlAssertion(System.IdentityModel.UniqueId.CreateRandomId(), issuer, DateTime.UtcNow, conditions, advice, statements);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the security token reference when the token is not attached to the message.
|
|
/// </summary>
|
|
/// <param name="token">The saml token.</param>
|
|
/// <param name="attached">Boolean that indicates if a attached or unattached
|
|
/// reference needs to be created.</param>
|
|
/// <returns>A SamlAssertionKeyIdentifierClause.</returns>
|
|
public override SecurityKeyIdentifierClause CreateSecurityTokenReference(SecurityToken token, bool attached)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
return token.CreateKeyIdentifierClause<SamlAssertionKeyIdentifierClause>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates all the conditions for saml
|
|
///
|
|
/// 1. Lifetime condition
|
|
/// 2. AudienceRestriction condition
|
|
///
|
|
/// </summary>
|
|
/// <param name="tokenLifetime">Lifetime of the Token.</param>
|
|
/// <param name="relyingPartyAddress">The endpoint address to who the token is created. The address
|
|
/// is modelled as an AudienceRestriction condition.</param>
|
|
/// <param name="tokenDescriptor">Contains all the other information that is used in token issuance.</param>
|
|
/// <returns>SamlConditions</returns>
|
|
protected virtual SamlConditions CreateConditions(Lifetime tokenLifetime, string relyingPartyAddress, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
SamlConditions conditions = new SamlConditions();
|
|
if (tokenLifetime != null)
|
|
{
|
|
if (tokenLifetime.Created != null)
|
|
{
|
|
conditions.NotBefore = tokenLifetime.Created.Value;
|
|
}
|
|
|
|
if (tokenLifetime.Expires != null)
|
|
{
|
|
conditions.NotOnOrAfter = tokenLifetime.Expires.Value;
|
|
}
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(relyingPartyAddress))
|
|
{
|
|
conditions.Conditions.Add(new SamlAudienceRestrictionCondition(new Uri[] { new Uri(relyingPartyAddress) }));
|
|
}
|
|
|
|
return conditions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates an enumeration of SamlStatements from a SecurityTokenDescriptor.
|
|
/// Only SamlAttributeStatements and SamlAuthenticationStatements are generated.
|
|
/// Overwrite this method to customize the creation of statements.
|
|
/// <para>
|
|
/// Calls in order (all are virtual):
|
|
/// 1. CreateSamlSubject
|
|
/// 2. CreateAttributeStatements
|
|
/// 3. CreateAuthenticationStatements
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The SecurityTokenDescriptor to use to build the statements.</param>
|
|
/// <returns>An enumeration of SamlStatement.</returns>
|
|
protected virtual IEnumerable<SamlStatement> CreateStatements(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
Collection<SamlStatement> statements = new Collection<SamlStatement>();
|
|
|
|
SamlSubject subject = CreateSamlSubject(tokenDescriptor);
|
|
SamlAttributeStatement attributeStatement = CreateAttributeStatement(subject, tokenDescriptor.Subject, tokenDescriptor);
|
|
if (attributeStatement != null)
|
|
{
|
|
statements.Add(attributeStatement);
|
|
}
|
|
|
|
SamlAuthenticationStatement authnStatement = CreateAuthenticationStatement(subject, tokenDescriptor.AuthenticationInfo, tokenDescriptor);
|
|
if (authnStatement != null)
|
|
{
|
|
statements.Add(authnStatement);
|
|
}
|
|
|
|
return statements;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a SamlAuthenticationStatement for each AuthenticationInformation found in AuthenticationInformation.
|
|
/// Override this method to provide a custom implementation.
|
|
/// </summary>
|
|
/// <param name="samlSubject">The SamlSubject of the Statement.</param>
|
|
/// <param name="authInfo">AuthenticationInformation from which to generate the SAML Authentication statement.</param>
|
|
/// <param name="tokenDescriptor">Contains all the other information that is used in token issuance.</param>
|
|
/// <returns>SamlAuthenticationStatement</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'samlSubject' or 'authInfo' is null.</exception>
|
|
protected virtual SamlAuthenticationStatement CreateAuthenticationStatement(
|
|
SamlSubject samlSubject,
|
|
AuthenticationInformation authInfo,
|
|
SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (samlSubject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject");
|
|
}
|
|
|
|
if (tokenDescriptor == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
if (tokenDescriptor.Subject == null)
|
|
{
|
|
return null;
|
|
}
|
|
string authenticationMethod = null;
|
|
string authenticationInstant = null;
|
|
|
|
// Search for an Authentication Claim.
|
|
IEnumerable<Claim> claimCollection = (from c in tokenDescriptor.Subject.Claims
|
|
where c.Type == ClaimTypes.AuthenticationMethod
|
|
select c);
|
|
if (claimCollection.Count<Claim>() > 0)
|
|
{
|
|
// We support only one authentication statement and hence we just pick the first authentication type
|
|
// claim found in the claim collection. Since the spec allows multiple Auth Statements
|
|
// we do not throw an error.
|
|
authenticationMethod = claimCollection.First<Claim>().Value;
|
|
}
|
|
|
|
claimCollection = (from c in tokenDescriptor.Subject.Claims
|
|
where c.Type == ClaimTypes.AuthenticationInstant
|
|
select c);
|
|
if (claimCollection.Count<Claim>() > 0)
|
|
{
|
|
authenticationInstant = claimCollection.First<Claim>().Value;
|
|
}
|
|
|
|
if (authenticationMethod == null && authenticationInstant == null)
|
|
{
|
|
return null;
|
|
}
|
|
else if (authenticationMethod == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationMethod", "SAML11"));
|
|
}
|
|
else if (authenticationInstant == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationInstant", "SAML11"));
|
|
}
|
|
|
|
DateTime authInstantTime = DateTime.ParseExact(authenticationInstant,
|
|
DateTimeFormats.Accepted,
|
|
DateTimeFormatInfo.InvariantInfo,
|
|
DateTimeStyles.None).ToUniversalTime();
|
|
if (authInfo == null)
|
|
{
|
|
return new SamlAuthenticationStatement(samlSubject, DenormalizeAuthenticationType(authenticationMethod), authInstantTime, null, null, null);
|
|
}
|
|
else
|
|
{
|
|
return new SamlAuthenticationStatement(samlSubject, DenormalizeAuthenticationType(authenticationMethod), authInstantTime, authInfo.DnsName, authInfo.Address, null);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates SamlAttributeStatements and adds them to a collection.
|
|
/// Override this method to provide a custom implementation.
|
|
/// <para>
|
|
/// Default behavior is to create a new SamlAttributeStatement for each Subject in the tokenDescriptor.Subjects collection.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="samlSubject">The SamlSubject to use in the SamlAttributeStatement that are created.</param>
|
|
/// <param name="subject">The ClaimsIdentity that contains claims which will be converted to SAML Attributes.</param>
|
|
/// <param name="tokenDescriptor">Contains all the other information that is used in token issuance.</param>
|
|
/// <returns>SamlAttributeStatement</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'samlSubject' is null.</exception>
|
|
protected virtual SamlAttributeStatement CreateAttributeStatement(
|
|
SamlSubject samlSubject,
|
|
ClaimsIdentity subject,
|
|
SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (subject == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (samlSubject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject");
|
|
}
|
|
|
|
if (subject.Claims != null)
|
|
{
|
|
|
|
List<SamlAttribute> attributes = new List<SamlAttribute>();
|
|
foreach (Claim claim in subject.Claims)
|
|
{
|
|
if (claim != null && claim.Type != ClaimTypes.NameIdentifier)
|
|
{
|
|
//
|
|
// NameIdentifier claim is already processed while creating the samlsubject
|
|
// AuthenticationInstant and AuthenticationType are not converted to Claims
|
|
//
|
|
switch (claim.Type)
|
|
{
|
|
case ClaimTypes.AuthenticationInstant:
|
|
case ClaimTypes.AuthenticationMethod:
|
|
break;
|
|
default:
|
|
attributes.Add(CreateAttribute(claim, tokenDescriptor));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
AddDelegateToAttributes(subject, attributes, tokenDescriptor);
|
|
|
|
ICollection<SamlAttribute> collectedAttributes = CollectAttributeValues(attributes);
|
|
if (collectedAttributes.Count > 0)
|
|
{
|
|
return new SamlAttributeStatement(samlSubject, collectedAttributes);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Collects attributes with a common claim type, claim value type, and original issuer into a
|
|
/// single attribute with multiple values.
|
|
/// </summary>
|
|
/// <param name="attributes">List of attributes generated from claims.</param>
|
|
/// <returns>List of attribute values with common attributes collected into value lists.</returns>
|
|
protected virtual ICollection<SamlAttribute> CollectAttributeValues(ICollection<SamlAttribute> attributes)
|
|
{
|
|
Dictionary<SamlAttributeKeyComparer.AttributeKey, SamlAttribute> distinctAttributes = new Dictionary<SamlAttributeKeyComparer.AttributeKey, SamlAttribute>(attributes.Count, new SamlAttributeKeyComparer());
|
|
|
|
foreach (SamlAttribute attribute in attributes)
|
|
{
|
|
SamlAttribute SamlAttribute = attribute as SamlAttribute;
|
|
if (SamlAttribute != null)
|
|
{
|
|
// Use unique attribute if name, value type, or issuer differ
|
|
SamlAttributeKeyComparer.AttributeKey attributeKey = new SamlAttributeKeyComparer.AttributeKey(SamlAttribute);
|
|
|
|
if (distinctAttributes.ContainsKey(attributeKey))
|
|
{
|
|
foreach (string attributeValue in SamlAttribute.AttributeValues)
|
|
{
|
|
distinctAttributes[attributeKey].AttributeValues.Add(attributeValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
distinctAttributes.Add(attributeKey, SamlAttribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
return distinctAttributes.Values;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds all the delegates associated with the ActAs subject into the attribute collection.
|
|
/// </summary>
|
|
/// <param name="subject">The delegate of this ClaimsIdentity will be serialized into a SamlAttribute.</param>
|
|
/// <param name="attributes">Attribute collection to which the ActAs token will be serialized.</param>
|
|
/// <param name="tokenDescriptor">Contains all the information that is used in token issuance.</param>
|
|
protected virtual void AddDelegateToAttributes(
|
|
ClaimsIdentity subject,
|
|
ICollection<SamlAttribute> attributes,
|
|
SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
if (tokenDescriptor == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
if (subject.Actor == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<SamlAttribute> actingAsAttributes = new List<SamlAttribute>();
|
|
|
|
foreach (Claim claim in subject.Actor.Claims)
|
|
{
|
|
if (claim != null)
|
|
{
|
|
actingAsAttributes.Add(CreateAttribute(claim, tokenDescriptor));
|
|
}
|
|
}
|
|
|
|
// perform depth first recursion
|
|
AddDelegateToAttributes(subject.Actor, actingAsAttributes, tokenDescriptor);
|
|
|
|
ICollection<SamlAttribute> collectedAttributes = CollectAttributeValues(actingAsAttributes);
|
|
attributes.Add(CreateAttribute(new Claim(ClaimTypes.Actor, CreateXmlStringFromAttributes(collectedAttributes), ClaimValueTypes.String), tokenDescriptor));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the SamlSubject to use for all the statements that will be created.
|
|
/// Overwrite this method to customize the creation of the SamlSubject.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">Contains all the information that is used in token issuance.</param>
|
|
/// <returns>A SamlSubject created from the first subject found in the tokenDescriptor as follows:
|
|
/// <para>
|
|
/// 1. Claim of Type NameIdentifier is searched. If found, SamlSubject.Name is set to claim.Value.
|
|
/// 2. If a non-null tokenDescriptor.proof is found then SamlSubject.KeyIdentifier = tokenDescriptor.Proof.KeyIdentifier AND SamlSubject.ConfirmationMethod is set to 'HolderOfKey'.
|
|
/// 3. If a null tokenDescriptor.proof is found then SamlSubject.ConfirmationMethod is set to 'BearerKey'.
|
|
/// </para>
|
|
/// </returns>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'tokenDescriptor' is null.</exception>
|
|
protected virtual SamlSubject CreateSamlSubject(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (tokenDescriptor == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
SamlSubject samlSubject = new SamlSubject();
|
|
|
|
Claim identityClaim = null;
|
|
if (tokenDescriptor.Subject != null && tokenDescriptor.Subject.Claims != null)
|
|
{
|
|
foreach (Claim claim in tokenDescriptor.Subject.Claims)
|
|
{
|
|
if (claim.Type == ClaimTypes.NameIdentifier)
|
|
{
|
|
// Do not allow multiple name identifier claim.
|
|
if (null != identityClaim)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4139)));
|
|
}
|
|
identityClaim = claim;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (identityClaim != null)
|
|
{
|
|
samlSubject.Name = identityClaim.Value;
|
|
|
|
if (identityClaim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierFormat))
|
|
{
|
|
samlSubject.NameFormat = identityClaim.Properties[ClaimProperties.SamlNameIdentifierFormat];
|
|
}
|
|
|
|
if (identityClaim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierNameQualifier))
|
|
{
|
|
samlSubject.NameQualifier = identityClaim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier];
|
|
}
|
|
}
|
|
|
|
if (tokenDescriptor.Proof != null)
|
|
{
|
|
//
|
|
// Add the key and the Holder-Of-Key confirmation method
|
|
// for both symmetric and asymmetric key case
|
|
//
|
|
samlSubject.KeyIdentifier = tokenDescriptor.Proof.KeyIdentifier;
|
|
samlSubject.ConfirmationMethods.Add(SamlConstants.HolderOfKey);
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// This is a bearer token
|
|
//
|
|
samlSubject.ConfirmationMethods.Add(BearerConfirmationMethod);
|
|
}
|
|
|
|
return samlSubject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds an XML formated string from a collection of saml attributes that represend the Actor.
|
|
/// </summary>
|
|
/// <param name="attributes">An enumeration of Saml Attributes.</param>
|
|
/// <returns>A well formed XML string.</returns>
|
|
/// <remarks>The string is of the form "<Actor><SamlAttribute name, ns><SamlAttributeValue>...</SamlAttributeValue>, ...</SamlAttribute>...</Actor>"</remarks>
|
|
protected virtual string CreateXmlStringFromAttributes(IEnumerable<SamlAttribute> attributes)
|
|
{
|
|
bool actorElementWritten = false;
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
using (XmlDictionaryWriter dicWriter = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false))
|
|
{
|
|
foreach (SamlAttribute samlAttribute in attributes)
|
|
{
|
|
if (samlAttribute != null)
|
|
{
|
|
if (!actorElementWritten)
|
|
{
|
|
dicWriter.WriteStartElement(Actor);
|
|
actorElementWritten = true;
|
|
}
|
|
WriteAttribute(dicWriter, samlAttribute);
|
|
}
|
|
}
|
|
|
|
if (actorElementWritten)
|
|
{
|
|
dicWriter.WriteEndElement();
|
|
}
|
|
|
|
dicWriter.Flush();
|
|
}
|
|
return Encoding.UTF8.GetString(ms.ToArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a SamlAttribute from a claim.
|
|
/// </summary>
|
|
/// <param name="claim">Claim from which to generate a SamlAttribute.</param>
|
|
/// <param name="tokenDescriptor">Contains all the information that is used in token issuance.</param>
|
|
/// <returns>The SamlAttribute.</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'claim' is null.</exception>
|
|
protected virtual SamlAttribute CreateAttribute(Claim claim, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (claim == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("claim");
|
|
}
|
|
|
|
int lastSlashIndex = claim.Type.LastIndexOf('/');
|
|
string attributeNamespace = null;
|
|
string attributeName = null;
|
|
|
|
if ((lastSlashIndex == 0) || (lastSlashIndex == -1))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claimType", SR.GetString(SR.ID4216, claim.Type));
|
|
}
|
|
else if (lastSlashIndex == claim.Type.Length - 1)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claimType", SR.GetString(SR.ID4216, claim.Type));
|
|
}
|
|
else
|
|
{
|
|
attributeNamespace = claim.Type.Substring(0, lastSlashIndex);
|
|
//
|
|
// The WCF SamlAttribute requires that the attributeNamespace and attributeName are both non-null and non-empty.
|
|
// Furthermore, on deserialization / construction it considers the claimType associated with the SamlAttribute to be attributeNamespace + "/" + attributeName.
|
|
//
|
|
// IDFX extends the WCF SamlAttribute and hence has to work with an attributeNamespace and attributeName that are both non-null and non-empty.
|
|
// On serialization, we identify the last slash in the claimtype, and treat everything before the slash as the attributeNamespace and everything after the slash as the attributeName.
|
|
// On deserialization, we don't always insert a "/" between the attributeNamespace and attributeName (like WCF does); we only do so if the attributeNamespace doesn't have a trailing slash.
|
|
//
|
|
// Send Receive Behavior
|
|
// =============================
|
|
// WCF WCF Works as expected
|
|
//
|
|
// WCF IDFX In the common case (http://www.claimtypes.com/foo), WCF will not send a trailing slash in the attributeNamespace. IDFX will add one upon deserialization.
|
|
// In the edge case (http://www.claimtypes.com//foo), WCF will send a trailing slash in the attributeNamespace. IDFX will not add one upon deserialization.
|
|
//
|
|
// IDFX WCF In the common case (http://www.claimtypes.com/foo), IDFX will not send a trailing slash. WCF will add one upon deserialization.
|
|
// In the edge case (http://www.claimtypes.com//foo), IDFX will throw (which is what the fix for FIP 6301 is about).
|
|
//
|
|
// IDFX IDFX Works as expected
|
|
//
|
|
if (attributeNamespace.EndsWith("/", StringComparison.Ordinal))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("claim", SR.GetString(SR.ID4213, claim.Type));
|
|
}
|
|
attributeName = claim.Type.Substring(lastSlashIndex + 1, claim.Type.Length - (lastSlashIndex + 1));
|
|
}
|
|
|
|
SamlAttribute attribute = new SamlAttribute(attributeNamespace, attributeName, new string[] { claim.Value });
|
|
if (!StringComparer.Ordinal.Equals(ClaimsIdentity.DefaultIssuer, claim.OriginalIssuer))
|
|
{
|
|
attribute.OriginalIssuer = claim.OriginalIssuer;
|
|
}
|
|
attribute.AttributeValueXsiType = claim.ValueType;
|
|
|
|
return attribute;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TokenValidation
|
|
|
|
/// <summary>
|
|
/// Returns value indicates if this handler can validate tokens of type
|
|
/// SamlSecurityToken.
|
|
/// </summary>
|
|
public override bool CanValidateToken
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the X509CeritificateValidator that is used by the current instance.
|
|
/// </summary>
|
|
public X509CertificateValidator CertificateValidator
|
|
{
|
|
get
|
|
{
|
|
if (_samlSecurityTokenRequirement.CertificateValidator == null)
|
|
{
|
|
if (Configuration != null)
|
|
{
|
|
return Configuration.CertificateValidator;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return _samlSecurityTokenRequirement.CertificateValidator;
|
|
}
|
|
}
|
|
set
|
|
{
|
|
_samlSecurityTokenRequirement.CertificateValidator = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Throws if a token is detected as being replayed. If the token is not found it is added to the <see cref="TokenReplayCache" />.
|
|
/// </summary>
|
|
/// <exception cref="ArgumentNullException">The input argument 'token' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Configuration or Configuration.TokenReplayCache property is null.</exception>
|
|
/// <exception cref="ArgumentException">The input argument 'token' is not a SamlSecurityToken.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">SamlSecurityToken.Assertion.Id is null or empty.</exception>
|
|
/// <exception cref="SecurityTokenReplayDetectedException">If the token is found in the <see cref="TokenReplayCache" />.</exception>
|
|
/// <remarks>The default behavior is to only check tokens bearer tokens (tokens that do not have keys).</remarks>
|
|
protected override void DetectReplayedToken(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
SamlSecurityToken samlToken = token as SamlSecurityToken;
|
|
if (null == samlToken)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1067, token.GetType().ToString()));
|
|
}
|
|
|
|
//
|
|
// by default we only check bearer tokens.
|
|
//
|
|
|
|
if (samlToken.SecurityKeys.Count != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (Configuration.Caches.TokenReplayCache == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4278));
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(samlToken.Assertion.AssertionId))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID1063)));
|
|
}
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
|
|
string key;
|
|
|
|
using (HashAlgorithm hashAlgorithm = CryptoHelper.NewSha256HashAlgorithm())
|
|
{
|
|
if (string.IsNullOrEmpty(samlToken.Assertion.Issuer))
|
|
{
|
|
stringBuilder.AppendFormat("{0}{1}", samlToken.Assertion.AssertionId, _tokenTypeIdentifiers[0]);
|
|
}
|
|
else
|
|
{
|
|
stringBuilder.AppendFormat("{0}{1}{2}", samlToken.Assertion.AssertionId, samlToken.Assertion.Issuer, _tokenTypeIdentifiers[0]);
|
|
}
|
|
|
|
key = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString())));
|
|
}
|
|
|
|
if (Configuration.Caches.TokenReplayCache.Contains(key))
|
|
{
|
|
if (string.IsNullOrEmpty(samlToken.Assertion.Issuer))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1062, typeof(SamlSecurityToken).ToString(), samlToken.Assertion.AssertionId, "")));
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1062, typeof(SamlSecurityToken).ToString(), samlToken.Assertion.AssertionId, samlToken.Assertion.Issuer)));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Configuration.Caches.TokenReplayCache.AddOrUpdate(key, token, DateTimeUtil.Add(GetTokenReplayCacheEntryExpirationTime(samlToken), Configuration.MaxClockSkew));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the time until which the token should be held in the token replay cache.
|
|
/// </summary>
|
|
/// <param name="token">The token to return an expiration time for.</param>
|
|
/// <exception cref="ArgumentNullException">The input argument 'token' is null.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">The SamlSecurityToken's validity period is greater than the expiration period set to TokenReplayCache.</exception>
|
|
/// <returns>A DateTime representing the expiration time.</returns>
|
|
protected virtual DateTime GetTokenReplayCacheEntryExpirationTime(SamlSecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
//
|
|
// DateTimeUtil handles overflows
|
|
//
|
|
DateTime maximumExpirationTime = DateTimeUtil.Add(DateTime.UtcNow, Configuration.TokenReplayCacheExpirationPeriod);
|
|
|
|
// If the token validity period is greater than the TokenReplayCacheExpirationPeriod, throw
|
|
if (DateTime.Compare(maximumExpirationTime, token.ValidTo) < 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenValidationException(SR.GetString(SR.ID1069, token.ValidTo.ToString(), Configuration.TokenReplayCacheExpirationPeriod.ToString())));
|
|
}
|
|
|
|
return token.ValidTo;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rejects tokens that are not valid.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The token may be invalid for a number of reasons. For example, the
|
|
/// current time may not be within the token's validity period, the
|
|
/// token may contain invalid or contradictory data, or the token
|
|
/// may contain unsupported SAML elements.
|
|
/// </remarks>
|
|
/// <param name="conditions">SAML condition to be validated.</param>
|
|
/// <param name="enforceAudienceRestriction">True to check for Audience Restriction condition.</param>
|
|
protected virtual void ValidateConditions(SamlConditions conditions, bool enforceAudienceRestriction)
|
|
{
|
|
if (null != conditions)
|
|
{
|
|
DateTime now = DateTime.UtcNow;
|
|
|
|
if (null != conditions.NotBefore
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew) < conditions.NotBefore)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenNotYetValidException(SR.GetString(SR.ID4222, conditions.NotBefore, now)));
|
|
}
|
|
|
|
if (null != conditions.NotOnOrAfter
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= conditions.NotOnOrAfter)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenExpiredException(SR.GetString(SR.ID4223, conditions.NotOnOrAfter, now)));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Enforce the audience restriction
|
|
//
|
|
if (enforceAudienceRestriction)
|
|
{
|
|
if (this.Configuration == null || this.Configuration.AudienceRestriction.AllowedAudienceUris.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID1032)));
|
|
}
|
|
|
|
//
|
|
// Process each condition, enforcing the AudienceRestrictionConditions
|
|
//
|
|
bool foundAudienceRestriction = false;
|
|
|
|
if (null != conditions && null != conditions.Conditions)
|
|
{
|
|
foreach (SamlCondition condition in conditions.Conditions)
|
|
{
|
|
SamlAudienceRestrictionCondition audienceRestriction = condition as SamlAudienceRestrictionCondition;
|
|
if (null == audienceRestriction)
|
|
{
|
|
// Skip other conditions
|
|
continue;
|
|
}
|
|
|
|
_samlSecurityTokenRequirement.ValidateAudienceRestriction(this.Configuration.AudienceRestriction.AllowedAudienceUris, audienceRestriction.Audiences);
|
|
foundAudienceRestriction = true;
|
|
}
|
|
}
|
|
|
|
if (!foundAudienceRestriction)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(SR.GetString(SR.ID1035)));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a <see cref="SamlSecurityToken"/>.
|
|
/// </summary>
|
|
/// <param name="token">The <see cref="SamlSecurityToken"/> to validate.</param>
|
|
/// <returns>The <see cref="ReadOnlyCollection{T}"/> of <see cref="ClaimsIdentity"/> representing the identities contained in the token.</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'token' is null.</exception>
|
|
/// <exception cref="ArgumentException">The token is not assignable from <see cref="SamlSecurityToken"/>.</exception>
|
|
/// <exception cref="InvalidOperationException">Configuration <see cref="SecurityTokenHandlerConfiguration"/>is null.</exception>
|
|
/// <exception cref="ArgumentException">SamlSecurityToken.Assertion is null.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">Thrown if SamlSecurityToken.Assertion.SigningToken is null.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">Thrown if the certificate associated with the token issuer does not pass validation.</exception>
|
|
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
SamlSecurityToken samlToken = token as SamlSecurityToken;
|
|
if (samlToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1033, token.GetType().ToString()));
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
try
|
|
{
|
|
if (samlToken.Assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1034));
|
|
}
|
|
|
|
TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.Diagnostics, SR.GetString(SR.TraceValidateToken), new SecurityTraceRecordHelper.TokenTraceRecord(token), null, null);
|
|
|
|
// Ensure token was signed and verified at some point
|
|
if (samlToken.Assertion.SigningToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4220)));
|
|
}
|
|
|
|
this.ValidateConditions(samlToken.Assertion.Conditions, _samlSecurityTokenRequirement.ShouldEnforceAudienceRestriction(this.Configuration.AudienceRestriction.AudienceMode, samlToken));
|
|
|
|
// We need something like AudienceUriMode and have a setting on Configuration to allow extensibility and custom settings
|
|
// By default we only check bearer tokens
|
|
if (this.Configuration.DetectReplayedTokens)
|
|
{
|
|
this.DetectReplayedToken(samlToken);
|
|
}
|
|
|
|
//
|
|
// If the backing token is x509, validate trust
|
|
//
|
|
X509SecurityToken x509IssuerToken = samlToken.Assertion.SigningToken as X509SecurityToken;
|
|
if (x509IssuerToken != null)
|
|
{
|
|
try
|
|
{
|
|
CertificateValidator.Validate(x509IssuerToken.Certificate);
|
|
}
|
|
catch (SecurityTokenValidationException e)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4257,
|
|
X509Util.GetCertificateId(x509IssuerToken.Certificate)), e));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create the claims
|
|
//
|
|
ClaimsIdentity claimsIdentity = CreateClaims(samlToken);
|
|
|
|
if (_samlSecurityTokenRequirement.MapToWindows)
|
|
{
|
|
// TFS: 153865, Microsoft WindowsIdentity does not set Authtype. I don't think that authtype should be set here anyway.
|
|
// The authtype will be S4U (kerberos) it doesn't really matter that the upn arrived in a SAML token.
|
|
WindowsIdentity windowsIdentity = CreateWindowsIdentity(FindUpn(claimsIdentity));
|
|
|
|
// PARTIAL TRUST: will fail when adding claims, AddClaims is SecurityCritical.
|
|
windowsIdentity.AddClaims(claimsIdentity.Claims);
|
|
claimsIdentity = windowsIdentity;
|
|
}
|
|
|
|
if (this.Configuration.SaveBootstrapContext)
|
|
{
|
|
claimsIdentity.BootstrapContext = new BootstrapContext(token, this);
|
|
}
|
|
|
|
this.TraceTokenValidationSuccess(token);
|
|
|
|
List<ClaimsIdentity> identities = new List<ClaimsIdentity>(1);
|
|
identities.Add(claimsIdentity);
|
|
return identities.AsReadOnly();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
this.TraceTokenValidationFailure(token, e.Message);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="WindowsIdentity"/> object using the <paramref name="upn"/> value.
|
|
/// </summary>
|
|
/// <param name="upn">The upn name.</param>
|
|
/// <returns>A <see cref="WindowsIdentity"/> object.</returns>
|
|
/// <exception cref="ArgumentException">If <paramref name="upn"/> is null or empty.</exception>
|
|
protected virtual WindowsIdentity CreateWindowsIdentity(string upn)
|
|
{
|
|
if (string.IsNullOrEmpty(upn))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("upn");
|
|
}
|
|
|
|
WindowsIdentity wi = new WindowsIdentity(upn);
|
|
|
|
return new WindowsIdentity(wi.Token, AuthenticationTypes.Federation, WindowsAccountType.Normal, true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finds the UPN claim value in the provided <see cref="ClaimsIdentity" /> object for the purpose
|
|
/// of mapping the identity to a <see cref="WindowsIdentity" /> object.
|
|
/// </summary>
|
|
/// <param name="claimsIdentity">The claims identity object containing the desired UPN claim.</param>
|
|
/// <returns>The UPN claim value found.</returns>
|
|
/// <exception cref="InvalidOperationException">If more than one UPN claim is contained in
|
|
/// <paramref name="claimsIdentity"/></exception>
|
|
protected virtual string FindUpn(ClaimsIdentity claimsIdentity)
|
|
{
|
|
return ClaimsHelper.FindUpn(claimsIdentity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates SubjectCollection that represents a SamlToken.
|
|
/// Only SamlAttributeStatements processed.
|
|
/// Overwrite this method to customize the creation of statements.
|
|
/// <para>
|
|
/// Calls:
|
|
/// 1. ProcessAttributeStatement for SamlAttributeStatements.
|
|
/// 2. ProcessAuthenticationStatement for SamlAuthenticationStatements.
|
|
/// 3. ProcessAuthorizationDecisionStatement for SamlAuthorizationDecisionStatements.
|
|
/// 4. ProcessCustomStatement for other SamlStatements.
|
|
/// </para>
|
|
/// </summary>
|
|
/// <param name="samlSecurityToken">The token used to generate the SubjectCollection.</param>
|
|
/// <returns>ClaimsIdentity representing the subject of the SamlToken.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'samlSecurityToken' is null.</exception>
|
|
protected virtual ClaimsIdentity CreateClaims(SamlSecurityToken samlSecurityToken)
|
|
{
|
|
if (samlSecurityToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityToken");
|
|
}
|
|
|
|
if (samlSecurityToken.Assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("samlSecurityToken", SR.GetString(SR.ID1034));
|
|
}
|
|
|
|
//
|
|
// Construct the subject and issuer identities.
|
|
// Use claim types specified in the security token requirements used for IPrincipal.Role and IIdentity.Name
|
|
//
|
|
ClaimsIdentity subject = new ClaimsIdentity(AuthenticationTypes.Federation,
|
|
_samlSecurityTokenRequirement.NameClaimType,
|
|
_samlSecurityTokenRequirement.RoleClaimType);
|
|
|
|
string issuer = null;
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (this.Configuration.IssuerNameRegistry == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4277));
|
|
}
|
|
|
|
// SamlAssertion. The SigningToken may or may not be null.
|
|
// The default IssuerNameRegistry will throw if null.
|
|
// This callout is provided for extensibility scenarios with custom IssuerNameRegistry.
|
|
issuer = this.Configuration.IssuerNameRegistry.GetIssuerName(samlSecurityToken.Assertion.SigningToken, samlSecurityToken.Assertion.Issuer);
|
|
|
|
if (string.IsNullOrEmpty(issuer))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4175)));
|
|
}
|
|
|
|
ProcessStatement(samlSecurityToken.Assertion.Statements, subject, issuer);
|
|
return subject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the Saml11 AuthenticationMethod matching a normalized value.
|
|
/// </summary>
|
|
/// <param name="normalizedAuthenticationType">Normalized value.</param>
|
|
/// <returns><see cref="SamlConstants.AuthenticationMethods"/></returns>
|
|
protected virtual string DenormalizeAuthenticationType(string normalizedAuthenticationType)
|
|
{
|
|
return AuthenticationTypeMaps.Denormalize(normalizedAuthenticationType, AuthenticationTypeMaps.Saml);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the normalized value matching a Saml11 AuthenticationMethod.
|
|
/// </summary>
|
|
/// <param name="saml11AuthenticationMethod"><see cref="SamlConstants.AuthenticationMethods"/></param>
|
|
/// <returns>Normalized value.</returns>
|
|
protected virtual string NormalizeAuthenticationType(string saml11AuthenticationMethod)
|
|
{
|
|
return AuthenticationTypeMaps.Normalize(saml11AuthenticationMethod, AuthenticationTypeMaps.Saml);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Processes all statements to generate claims.
|
|
/// </summary>
|
|
/// <param name="statements">A collection of Saml2Statement.</param>
|
|
/// <param name="subject">The subject.</param>
|
|
/// <param name="issuer">The issuer.</param>
|
|
protected virtual void ProcessStatement(IList<SamlStatement> statements, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (statements == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statements");
|
|
}
|
|
|
|
Collection<SamlAuthenticationStatement> authStatementCollection = new Collection<SamlAuthenticationStatement>();
|
|
|
|
//
|
|
// Validate that the Saml subjects in all the statements are the same.
|
|
//
|
|
ValidateStatements(statements);
|
|
|
|
foreach (SamlStatement samlStatement in statements)
|
|
{
|
|
SamlAttributeStatement attrStatement = samlStatement as SamlAttributeStatement;
|
|
if (attrStatement != null)
|
|
{
|
|
ProcessAttributeStatement(attrStatement, subject, issuer);
|
|
}
|
|
else
|
|
{
|
|
SamlAuthenticationStatement authenStatement = samlStatement as SamlAuthenticationStatement;
|
|
if (authenStatement != null)
|
|
{
|
|
authStatementCollection.Add(authenStatement);
|
|
}
|
|
else
|
|
{
|
|
SamlAuthorizationDecisionStatement decisionStatement = samlStatement as SamlAuthorizationDecisionStatement;
|
|
if (decisionStatement != null)
|
|
{
|
|
ProcessAuthorizationDecisionStatement(decisionStatement, subject, issuer);
|
|
}
|
|
else
|
|
{
|
|
// We don't process custom statements. Just fall through.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Processing Authentication statement(s) should be done at the last phase to add the authentication
|
|
// information as claims to the ClaimsIdentity
|
|
foreach (SamlAuthenticationStatement authStatement in authStatementCollection)
|
|
{
|
|
if (authStatement != null)
|
|
{
|
|
ProcessAuthenticationStatement(authStatement, subject, issuer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this virtual to provide custom processing of SamlAttributeStatements.
|
|
/// </summary>
|
|
/// <param name="samlStatement">The SamlAttributeStatement to process.</param>
|
|
/// <param name="subject">The identity that should be modified to reflect the statement.</param>
|
|
/// <param name="issuer">The subject that identifies the issuer.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'samlStatement' or 'subject' is null.</exception>
|
|
protected virtual void ProcessAttributeStatement(SamlAttributeStatement samlStatement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (samlStatement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlStatement");
|
|
}
|
|
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
// We will be adding the nameid claim only once for multiple attribute and/or authn statements.
|
|
// As of now, we put the nameId claim both inside the saml subject and the saml attribute statement as assertion.
|
|
// When generating claims, we will only pick up the saml subject of a saml statement, not the attribute statement value.
|
|
ProcessSamlSubject(samlStatement.SamlSubject, subject, issuer);
|
|
|
|
foreach (SamlAttribute attr in samlStatement.Attributes)
|
|
{
|
|
string claimType = null;
|
|
if (string.IsNullOrEmpty(attr.Namespace))
|
|
{
|
|
claimType = attr.Name;
|
|
}
|
|
else if (StringComparer.Ordinal.Equals(attr.Name, SamlConstants.ElementNames.NameIdentifier))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException(SR.GetString(SR.ID4094)));
|
|
}
|
|
else
|
|
{
|
|
// check if Namespace end with slash don't add it
|
|
// If no slash or last char is not a slash, add it.
|
|
int lastSlashIndex = attr.Namespace.LastIndexOf('/');
|
|
if ((lastSlashIndex == -1) || (!(lastSlashIndex == attr.Namespace.Length - 1)))
|
|
{
|
|
claimType = attr.Namespace + "/" + attr.Name;
|
|
}
|
|
else
|
|
{
|
|
claimType = attr.Namespace + attr.Name;
|
|
}
|
|
|
|
}
|
|
|
|
if (claimType == ClaimTypes.Actor)
|
|
{
|
|
if (subject.Actor != null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4034));
|
|
}
|
|
|
|
SetDelegateFromAttribute(attr, subject, issuer);
|
|
}
|
|
else
|
|
{
|
|
for (int k = 0; k < attr.AttributeValues.Count; ++k)
|
|
{
|
|
// Check if we already have a nameId claim.
|
|
if (StringComparer.Ordinal.Equals(ClaimTypes.NameIdentifier, claimType) && GetClaim(subject, ClaimTypes.NameIdentifier) != null)
|
|
{
|
|
continue;
|
|
}
|
|
string originalIssuer = issuer;
|
|
SamlAttribute SamlAttribute = attr as SamlAttribute;
|
|
if ((SamlAttribute != null) && (SamlAttribute.OriginalIssuer != null))
|
|
{
|
|
originalIssuer = SamlAttribute.OriginalIssuer;
|
|
}
|
|
string claimValueType = ClaimValueTypes.String;
|
|
if (SamlAttribute != null)
|
|
{
|
|
claimValueType = SamlAttribute.AttributeValueXsiType;
|
|
}
|
|
subject.AddClaim(new Claim(claimType, attr.AttributeValues[k], claimValueType, issuer, originalIssuer));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a specific claim of type claimType from the subject's claims collection.
|
|
/// </summary>
|
|
/// <param name="subject">The subject.</param>
|
|
/// <param name="claimType">The type of the claim.</param>
|
|
/// <returns>The claim of type claimType if present, else null.</returns>
|
|
private static Claim GetClaim(ClaimsIdentity subject, string claimType)
|
|
{
|
|
foreach (Claim claim in subject.Claims)
|
|
{
|
|
if (StringComparer.Ordinal.Equals(claimType, claim.Type))
|
|
{
|
|
return claim;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// For each saml statement (attribute/authentication/authz/custom), we will check if we need to create
|
|
/// a nameid claim or a key identifier claim out of its SamlSubject.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To make sure that the saml subject within each saml statement are the same, this method does the following comparisons.
|
|
/// 1. All the saml subjects' contents are the same.
|
|
/// 2. The name identifiers (if present) are the same. The name identifier comparison is done for the name identifier value,
|
|
/// name identifier format (if present), and name identifier qualifier (if present).
|
|
/// 3. The key identifiers (if present) are the same.
|
|
/// </remarks>
|
|
/// <param name="samlSubject">The SamlSubject to extract claims from.</param>
|
|
/// <param name="subject">The identity that should be modified to reflect the SamlSubject.</param>
|
|
/// <param name="issuer">The Issuer claims of the SAML token.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'samlSubject' is null.</exception>
|
|
protected virtual void ProcessSamlSubject(SamlSubject samlSubject, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (samlSubject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSubject");
|
|
}
|
|
|
|
Claim nameIdentifierClaim = GetClaim(subject, ClaimTypes.NameIdentifier);
|
|
|
|
if (nameIdentifierClaim == null)
|
|
{
|
|
// first saml subject. so we will create claims for this subject.
|
|
// subsequent subjects must have the same content.
|
|
|
|
// add name identifier claim if present.
|
|
if (!string.IsNullOrEmpty(samlSubject.Name))
|
|
{
|
|
Claim claim = new Claim(ClaimTypes.NameIdentifier, samlSubject.Name, ClaimValueTypes.String, issuer);
|
|
|
|
if (samlSubject.NameFormat != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierFormat] = samlSubject.NameFormat;
|
|
}
|
|
|
|
if (samlSubject.NameQualifier != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier] = samlSubject.NameQualifier;
|
|
}
|
|
|
|
subject.AddClaim(claim);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this virtual to provide custom processing of the SamlAuthenticationStatement.
|
|
/// By default it adds authentication type and instant to each claim.
|
|
/// </summary>
|
|
/// <param name="samlStatement">The SamlAuthenticationStatement to process</param>
|
|
/// <param name="subject">The identity that should be modified to reflect the statement</param>
|
|
/// <param name="issuer">issuer Identity.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'samlSubject' or 'subject' is null.</exception>
|
|
protected virtual void ProcessAuthenticationStatement(SamlAuthenticationStatement samlStatement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (samlStatement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlStatement");
|
|
}
|
|
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
// When there is only a authentication statement present inside a saml assertion, we need to generate
|
|
// a nameId claim. See FIP 4848. We do not support any saml assertion without a attribute statement, but
|
|
// we might receive a saml assertion with only a authentication statement.
|
|
ProcessSamlSubject(samlStatement.SamlSubject, subject, issuer);
|
|
|
|
subject.AddClaim(new Claim(ClaimTypes.AuthenticationMethod, NormalizeAuthenticationType(samlStatement.AuthenticationMethod), ClaimValueTypes.String, issuer));
|
|
subject.AddClaim(new Claim(ClaimTypes.AuthenticationInstant, XmlConvert.ToString(samlStatement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated), ClaimValueTypes.DateTime, issuer));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this virtual to provide custom processing of SamlAuthorizationDecisionStatement.
|
|
/// By default no processing is performed, you will need to access the token for SamlAuthorizationDecisionStatement information.
|
|
/// </summary>
|
|
/// <param name="samlStatement">The SamlAuthorizationDecisionStatement to process.</param>
|
|
/// <param name="subject">The identity that should be modified to reflect the statement.</param>
|
|
/// <param name="issuer">The subject that identifies the issuer.</param>
|
|
protected virtual void ProcessAuthorizationDecisionStatement(SamlAuthorizationDecisionStatement samlStatement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method gets called when a special type of SamlAttribute is detected. The SamlAttribute passed in wraps a SamlAttribute
|
|
/// that contains a collection of AttributeValues, each of which are mapped to a claim. All of the claims will be returned
|
|
/// in an ClaimsIdentity with the specified issuer.
|
|
/// </summary>
|
|
/// <param name="attribute">The SamlAttribute to be processed.</param>
|
|
/// <param name="subject">The identity that should be modified to reflect the SamlAttribute.</param>
|
|
/// <param name="issuer">Issuer Identity.</param>
|
|
/// <exception cref="InvalidOperationException">Will be thrown if the SamlAttribute does not contain any valid SamlAttributeValues.</exception>
|
|
protected virtual void SetDelegateFromAttribute(SamlAttribute attribute, ClaimsIdentity subject, string issuer)
|
|
{
|
|
// bail here nothing to add.
|
|
if (subject == null || attribute == null || attribute.AttributeValues == null || attribute.AttributeValues.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Collection<Claim> claims = new Collection<Claim>();
|
|
SamlAttribute actingAsAttribute = null;
|
|
|
|
foreach (string attributeValue in attribute.AttributeValues)
|
|
{
|
|
if (attributeValue != null && attributeValue.Length > 0)
|
|
{
|
|
|
|
using (XmlDictionaryReader xmlReader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(attributeValue), XmlDictionaryReaderQuotas.Max))
|
|
{
|
|
xmlReader.MoveToContent();
|
|
xmlReader.ReadStartElement(Actor);
|
|
|
|
while (xmlReader.IsStartElement(Attribute))
|
|
{
|
|
SamlAttribute innerAttribute = ReadAttribute(xmlReader);
|
|
if (innerAttribute != null)
|
|
{
|
|
string claimType = string.IsNullOrEmpty(innerAttribute.Namespace) ? innerAttribute.Name : innerAttribute.Namespace + "/" + innerAttribute.Name;
|
|
if (claimType == ClaimTypes.Actor)
|
|
{
|
|
// In this case we have two delegates acting as an identity, we do not allow this
|
|
if (actingAsAttribute != null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4034));
|
|
}
|
|
|
|
actingAsAttribute = innerAttribute;
|
|
}
|
|
else
|
|
{
|
|
string claimValueType = ClaimValueTypes.String;
|
|
string originalIssuer = null;
|
|
SamlAttribute SamlAttribute = innerAttribute as SamlAttribute;
|
|
if (SamlAttribute != null)
|
|
{
|
|
claimValueType = SamlAttribute.AttributeValueXsiType;
|
|
originalIssuer = SamlAttribute.OriginalIssuer;
|
|
}
|
|
for (int k = 0; k < innerAttribute.AttributeValues.Count; ++k)
|
|
{
|
|
Claim claim = null;
|
|
if (string.IsNullOrEmpty(originalIssuer))
|
|
{
|
|
claim = new Claim(claimType, innerAttribute.AttributeValues[k], claimValueType, issuer);
|
|
}
|
|
else
|
|
{
|
|
claim = new Claim(claimType, innerAttribute.AttributeValues[k], claimValueType, issuer, originalIssuer);
|
|
}
|
|
claims.Add(claim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
xmlReader.ReadEndElement(); // Actor
|
|
}
|
|
}
|
|
}
|
|
|
|
subject.Actor = new ClaimsIdentity(claims, AuthenticationTypes.Federation);
|
|
|
|
SetDelegateFromAttribute(actingAsAttribute, subject.Actor, issuer);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region TokenSerialization
|
|
|
|
/// <summary>
|
|
/// Indicates whether the current XML element can be read as a token
|
|
/// of the type handled by this instance.
|
|
/// </summary>
|
|
/// <param name="reader">An XML reader positioned at a start
|
|
/// element. The reader should not be advanced.</param>
|
|
/// <returns>'True' if the ReadToken method can the element.</returns>
|
|
public override bool CanReadToken(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes from XML a token of the type handled by this instance.
|
|
/// </summary>
|
|
/// <param name="reader">An XML reader positioned at the token's start
|
|
/// element.</param>
|
|
/// <returns>An instance of <see cref="SamlSecurityToken"/>.</returns>
|
|
/// <exception cref="InvalidOperationException">Is thrown if 'Configuration' or 'Configruation.IssuerTokenResolver' is null.</exception>
|
|
public override SecurityToken ReadToken(XmlReader reader)
|
|
{
|
|
if (Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (Configuration.IssuerTokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4275));
|
|
}
|
|
|
|
SamlAssertion assertion = ReadAssertion(reader);
|
|
//
|
|
// Resolve signing token if one is present. It may be deferred and signed by reference.
|
|
//
|
|
SecurityToken token;
|
|
|
|
TryResolveIssuerToken(assertion, Configuration.IssuerTokenResolver, out token);
|
|
|
|
assertion.SigningToken = token;
|
|
|
|
return new SamlSecurityToken(assertion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:Action element.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at saml:Action element.</param>
|
|
/// <returns>SamlAction</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The saml:Action element contains unknown elements.</exception>
|
|
protected virtual SamlAction ReadAction(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Action, SamlConstants.Namespace))
|
|
{
|
|
// The Namespace attribute is optional.
|
|
string ns = reader.GetAttribute(SamlConstants.AttributeNames.Namespace, null);
|
|
|
|
reader.MoveToContent();
|
|
string action = reader.ReadString();
|
|
if (string.IsNullOrEmpty(action))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4073)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return new SamlAction(action, ns);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Action, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the given SamlAction to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlAction into.</param>
|
|
/// <param name="action">SamlAction to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'writer' or 'action' is null.</exception>
|
|
protected virtual void WriteAction(XmlWriter writer, SamlAction action)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (action == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("action");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Action, SamlConstants.Namespace);
|
|
if (!string.IsNullOrEmpty(action.Namespace))
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Namespace, null, action.Namespace);
|
|
}
|
|
writer.WriteString(action.Action);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:Advice element from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a SAML Advice element.</param>
|
|
/// <returns>SamlAdvice</returns>
|
|
/// <exception cref="ArgumentNullException">Parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The reder is not positioned at a saml:Advice element.</exception>
|
|
protected virtual SamlAdvice ReadAdvice(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.Advice, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Advice, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
// SAML Advice is an optional element and all its child elements are optional
|
|
// too. So we may have an empty saml:Advice element in the saml token.
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
// Just issue a read for the empty element.
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
return new SamlAdvice();
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
Collection<string> assertionIdReferences = new Collection<string>();
|
|
Collection<SamlAssertion> assertions = new Collection<SamlAssertion>();
|
|
while (reader.IsStartElement())
|
|
{
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace))
|
|
{
|
|
assertionIdReferences.Add(reader.ReadString());
|
|
reader.ReadEndElement();
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace))
|
|
{
|
|
SamlAssertion assertion = ReadAssertion(reader);
|
|
assertions.Add(assertion);
|
|
}
|
|
else
|
|
{
|
|
TraceUtility.TraceString(TraceEventType.Warning, SR.GetString(SR.ID8005, reader.LocalName, reader.NamespaceURI));
|
|
reader.Skip();
|
|
}
|
|
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return new SamlAdvice(assertionIdReferences, assertions);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Serialize the given SamlAdvice to the given XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlAdvice.</param>
|
|
/// <param name="advice">SamlAdvice to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'advice' is null.</exception>
|
|
protected virtual void WriteAdvice(XmlWriter writer, SamlAdvice advice)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (advice == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("advice");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Advice, SamlConstants.Namespace);
|
|
if (advice.AssertionIdReferences.Count > 0)
|
|
{
|
|
foreach (string assertionIdReference in advice.AssertionIdReferences)
|
|
{
|
|
if (string.IsNullOrEmpty(assertionIdReference))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4079)));
|
|
}
|
|
writer.WriteElementString(SamlConstants.Prefix, SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace, assertionIdReference);
|
|
}
|
|
}
|
|
|
|
if (advice.Assertions.Count > 0)
|
|
{
|
|
foreach (SamlAssertion assertion in advice.Assertions)
|
|
{
|
|
WriteAssertion(writer, assertion);
|
|
}
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:Assertion element from the given reader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader to deserialize the Assertion from.</param>
|
|
/// <returns>SamlAssertion</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at a saml:Assertion element or the Assertion
|
|
/// contains unknown child elements.</exception>
|
|
protected virtual SamlAssertion ReadAssertion(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (this.Configuration.IssuerTokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4275));
|
|
}
|
|
|
|
SamlAssertion assertion = new SamlAssertion();
|
|
|
|
EnvelopedSignatureReader wrappedReader = new EnvelopedSignatureReader(reader, new WrappedSerializer(this, assertion), this.Configuration.IssuerTokenResolver, false, true, false);
|
|
|
|
|
|
if (!wrappedReader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4065, SamlConstants.ElementNames.Assertion, SamlConstants.Namespace, wrappedReader.LocalName, wrappedReader.NamespaceURI)));
|
|
}
|
|
|
|
string attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.MajorVersion, null);
|
|
if (string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.MajorVersion)));
|
|
}
|
|
|
|
int majorVersion = XmlConvert.ToInt32(attributeValue);
|
|
|
|
attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.MinorVersion, null);
|
|
if (string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.MinorVersion)));
|
|
}
|
|
|
|
int minorVersion = XmlConvert.ToInt32(attributeValue);
|
|
|
|
if ((majorVersion != SamlConstants.MajorVersionValue) || (minorVersion != SamlConstants.MinorVersionValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4076, majorVersion, minorVersion, SamlConstants.MajorVersionValue, SamlConstants.MinorVersionValue)));
|
|
}
|
|
|
|
attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.AssertionId, null);
|
|
if (string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.AssertionId)));
|
|
}
|
|
|
|
if (!XmlUtil.IsValidXmlIDValue(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4077, attributeValue)));
|
|
}
|
|
|
|
assertion.AssertionId = attributeValue;
|
|
|
|
attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.Issuer, null);
|
|
if (string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4075, SamlConstants.AttributeNames.Issuer)));
|
|
}
|
|
|
|
assertion.Issuer = attributeValue;
|
|
|
|
attributeValue = wrappedReader.GetAttribute(SamlConstants.AttributeNames.IssueInstant, null);
|
|
if (!string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
assertion.IssueInstant = DateTime.ParseExact(
|
|
attributeValue, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime();
|
|
}
|
|
|
|
wrappedReader.MoveToContent();
|
|
wrappedReader.Read();
|
|
|
|
if (wrappedReader.IsStartElement(SamlConstants.ElementNames.Conditions, SamlConstants.Namespace))
|
|
{
|
|
assertion.Conditions = ReadConditions(wrappedReader);
|
|
}
|
|
|
|
if (wrappedReader.IsStartElement(SamlConstants.ElementNames.Advice, SamlConstants.Namespace))
|
|
{
|
|
assertion.Advice = ReadAdvice(wrappedReader);
|
|
}
|
|
|
|
while (wrappedReader.IsStartElement())
|
|
{
|
|
assertion.Statements.Add(ReadStatement(wrappedReader));
|
|
}
|
|
|
|
if (assertion.Statements.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4078)));
|
|
}
|
|
|
|
wrappedReader.MoveToContent();
|
|
wrappedReader.ReadEndElement();
|
|
|
|
// Reading the end element will complete the signature;
|
|
// capture the signing creds
|
|
assertion.SigningCredentials = wrappedReader.SigningCredentials;
|
|
|
|
// Save the captured on-the-wire data, which can then be used
|
|
// to re-emit this assertion, preserving the same signature.
|
|
assertion.CaptureSourceData(wrappedReader);
|
|
|
|
return assertion;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes a given SamlAssertion to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to use for the serialization.</param>
|
|
/// <param name="assertion">Assertion to be serialized into the XmlWriter.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'assertion' is null.</exception>
|
|
protected virtual void WriteAssertion(XmlWriter writer, SamlAssertion assertion)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
SamlAssertion SamlAssertion = assertion as SamlAssertion;
|
|
if (SamlAssertion != null)
|
|
{
|
|
if (SamlAssertion.CanWriteSourceData)
|
|
{
|
|
SamlAssertion.WriteSourceData(writer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (assertion.SigningCredentials != null)
|
|
{
|
|
writer = new EnvelopedSignatureWriter(writer, assertion.SigningCredentials, assertion.AssertionId, new WrappedSerializer(this, assertion));
|
|
}
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Assertion, SamlConstants.Namespace);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.MajorVersion, null, Convert.ToString(SamlConstants.MajorVersionValue, CultureInfo.InvariantCulture));
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.MinorVersion, null, Convert.ToString(SamlConstants.MinorVersionValue, CultureInfo.InvariantCulture));
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.AssertionId, null, assertion.AssertionId);
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Issuer, null, assertion.Issuer);
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.IssueInstant, null, assertion.IssueInstant.ToUniversalTime().ToString(DateTimeFormats.Generated, CultureInfo.InvariantCulture));
|
|
|
|
// Write out conditions
|
|
if (assertion.Conditions != null)
|
|
{
|
|
WriteConditions(writer, assertion.Conditions);
|
|
}
|
|
|
|
// Write out advice if there is one
|
|
if (assertion.Advice != null)
|
|
{
|
|
WriteAdvice(writer, assertion.Advice);
|
|
}
|
|
|
|
// Write statements.
|
|
for (int i = 0; i < assertion.Statements.Count; i++)
|
|
{
|
|
WriteStatement(writer, assertion.Statements[i]);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:Conditions from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader to read the SAML conditions from.</param>
|
|
/// <returns>SamlConditions</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The reader is not positioned at saml:Conditions element or contains
|
|
/// elements that are not recognized.</exception>
|
|
protected virtual SamlConditions ReadConditions(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
SamlConditions conditions = new SamlConditions();
|
|
string time = reader.GetAttribute(SamlConstants.AttributeNames.NotBefore, null);
|
|
if (!string.IsNullOrEmpty(time))
|
|
{
|
|
conditions.NotBefore = DateTime.ParseExact(
|
|
time, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime();
|
|
}
|
|
|
|
time = reader.GetAttribute(SamlConstants.AttributeNames.NotOnOrAfter, null);
|
|
if (!string.IsNullOrEmpty(time))
|
|
{
|
|
conditions.NotOnOrAfter = DateTime.ParseExact(
|
|
time, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime();
|
|
}
|
|
|
|
// Saml Conditions element is an optional element and all its child element
|
|
// are optional as well. So we can have a empty <saml:Conditions /> element
|
|
// in a valid Saml token.
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
// Just issue a read to read the Empty element.
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
return conditions;
|
|
}
|
|
|
|
reader.ReadStartElement();
|
|
|
|
while (reader.IsStartElement())
|
|
{
|
|
conditions.Conditions.Add(ReadCondition(reader));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return conditions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize SamlConditions to the given XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to which the SamlConditions is serialized.</param>
|
|
/// <param name="conditions">SamlConditions to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'writer' or 'conditions' is null.</exception>
|
|
protected virtual void WriteConditions(XmlWriter writer, SamlConditions conditions)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (conditions == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("conditions");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Conditions, SamlConstants.Namespace);
|
|
|
|
// SamlConditions when new'ed up will have the min and max values defined in WCF
|
|
// which is different than our defaults. To maintin compatability with WCF behavior we will
|
|
// not write out SamlConditions NotBefore and NotOnOrAfter times which match the WCF
|
|
// min and max default values as well.
|
|
if (conditions.NotBefore != DateTimeUtil.GetMinValue(DateTimeKind.Utc) &&
|
|
conditions.NotBefore != WCFMinValue)
|
|
{
|
|
writer.WriteAttributeString(
|
|
SamlConstants.AttributeNames.NotBefore,
|
|
null,
|
|
conditions.NotBefore.ToUniversalTime().ToString(DateTimeFormats.Generated, DateTimeFormatInfo.InvariantInfo));
|
|
}
|
|
|
|
if (conditions.NotOnOrAfter != DateTimeUtil.GetMaxValue(DateTimeKind.Utc) &&
|
|
conditions.NotOnOrAfter != WCFMaxValue)
|
|
{
|
|
writer.WriteAttributeString(
|
|
SamlConstants.AttributeNames.NotOnOrAfter,
|
|
null,
|
|
conditions.NotOnOrAfter.ToUniversalTime().ToString(DateTimeFormats.Generated, DateTimeFormatInfo.InvariantInfo));
|
|
}
|
|
|
|
for (int i = 0; i < conditions.Conditions.Count; i++)
|
|
{
|
|
WriteCondition(writer, conditions.Conditions[i]);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:AudienceRestrictionCondition or saml:DoNotCacheCondition from the given reader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader to read the SamlCondition from.</param>
|
|
/// <returns>SamlCondition</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">XmlReader is positioned at an unknown element.</exception>
|
|
protected virtual SamlCondition ReadCondition(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace))
|
|
{
|
|
return ReadAudienceRestrictionCondition(reader);
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace))
|
|
{
|
|
return ReadDoNotCacheCondition(reader);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4080, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given SamlCondition to the given XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the condition.</param>
|
|
/// <param name="condition">SamlConditon to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'condition' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">The given condition is unknown. By default only SamlAudienceRestrictionCondition
|
|
/// and SamlDoNotCacheCondition are serialized.</exception>
|
|
protected virtual void WriteCondition(XmlWriter writer, SamlCondition condition)
|
|
{
|
|
if (condition == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition");
|
|
}
|
|
|
|
SamlAudienceRestrictionCondition audienceRestrictionCondition = condition as SamlAudienceRestrictionCondition;
|
|
if (audienceRestrictionCondition != null)
|
|
{
|
|
WriteAudienceRestrictionCondition(writer, audienceRestrictionCondition);
|
|
return;
|
|
}
|
|
|
|
SamlDoNotCacheCondition doNotCacheCondition = condition as SamlDoNotCacheCondition;
|
|
if (doNotCacheCondition != null)
|
|
{
|
|
WriteDoNotCacheCondition(writer, doNotCacheCondition);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4081, condition.GetType())));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:AudienceRestrictionCondition from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a saml:AudienceRestrictionCondition.</param>
|
|
/// <returns>SamlAudienceRestrictionCondition</returns>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at saml:AudienceRestrictionCondition.</exception>
|
|
protected virtual SamlAudienceRestrictionCondition ReadAudienceRestrictionCondition(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
reader.ReadStartElement();
|
|
|
|
SamlAudienceRestrictionCondition audienceRestrictionCondition = new SamlAudienceRestrictionCondition();
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Audience, SamlConstants.Namespace))
|
|
{
|
|
string audience = reader.ReadString();
|
|
if (string.IsNullOrEmpty(audience))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4083)));
|
|
}
|
|
|
|
audienceRestrictionCondition.Audiences.Add(new Uri(audience, UriKind.RelativeOrAbsolute));
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Audience, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
if (audienceRestrictionCondition.Audiences.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4084)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return audienceRestrictionCondition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize SamlAudienceRestrictionCondition to a XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlAudienceRestrictionCondition.</param>
|
|
/// <param name="condition">SamlAudienceRestrictionCondition to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'writer' or 'condition' is null.</exception>
|
|
protected virtual void WriteAudienceRestrictionCondition(XmlWriter writer, SamlAudienceRestrictionCondition condition)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (condition == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition");
|
|
}
|
|
|
|
// Schema requires at least one audience.
|
|
if (condition.Audiences == null || condition.Audiences.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4269)));
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AudienceRestrictionCondition, SamlConstants.Namespace);
|
|
|
|
for (int i = 0; i < condition.Audiences.Count; i++)
|
|
{
|
|
// When writing out the audience uri we use the OriginalString property to preserve the value that was initially passed down during token creation as-is.
|
|
writer.WriteElementString(SamlConstants.ElementNames.Audience, SamlConstants.Namespace, condition.Audiences[i].OriginalString);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:DoNotCacheCondition from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a saml:DoNotCacheCondition element.</param>
|
|
/// <returns>SamlDoNotCacheCondition</returns>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at saml:DoNotCacheCondition.</exception>
|
|
protected virtual SamlDoNotCacheCondition ReadDoNotCacheCondition(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
SamlDoNotCacheCondition doNotCacheCondition = new SamlDoNotCacheCondition();
|
|
// saml:DoNotCacheCondition is a empty element. So just issue a read for
|
|
// the empty element.
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
return doNotCacheCondition;
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadStartElement();
|
|
reader.ReadEndElement();
|
|
|
|
return doNotCacheCondition;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize SamlDoNotCacheCondition to a XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlDoNotCacheCondition.</param>
|
|
/// <param name="condition">SamlDoNotCacheCondition to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'writer' or 'condition' is null.</exception>
|
|
protected virtual void WriteDoNotCacheCondition(XmlWriter writer, SamlDoNotCacheCondition condition)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (condition == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("condition");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.DoNotCacheCondition, SamlConstants.Namespace);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read a SamlStatement from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a SamlStatement.</param>
|
|
/// <returns>SamlStatement</returns>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at recognized SamlStatement. By default,
|
|
/// only saml:AuthenticationStatement, saml:AttributeStatement and saml:AuthorizationDecisionStatement.</exception>
|
|
protected virtual SamlStatement ReadStatement(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace))
|
|
{
|
|
return ReadAuthenticationStatement(reader);
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace))
|
|
{
|
|
return ReadAttributeStatement(reader);
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace))
|
|
{
|
|
return ReadAuthorizationDecisionStatement(reader);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4085, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize the SamlStatement to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlStatement.</param>
|
|
/// <param name="statement">The SamlStatement to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The parameter 'writer' or 'statement' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">The SamlStatement is not recognized. Only SamlAuthenticationStatement,
|
|
/// SamlAuthorizationStatement and SamlAttributeStatement are recognized.</exception>
|
|
protected virtual void WriteStatement(XmlWriter writer, SamlStatement statement)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (statement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
SamlAuthenticationStatement authnStatement = statement as SamlAuthenticationStatement;
|
|
if (authnStatement != null)
|
|
{
|
|
WriteAuthenticationStatement(writer, authnStatement);
|
|
return;
|
|
}
|
|
|
|
SamlAuthorizationDecisionStatement authzStatement = statement as SamlAuthorizationDecisionStatement;
|
|
if (authzStatement != null)
|
|
{
|
|
WriteAuthorizationDecisionStatement(writer, authzStatement);
|
|
return;
|
|
}
|
|
|
|
SamlAttributeStatement attributeStatement = statement as SamlAttributeStatement;
|
|
if (attributeStatement != null)
|
|
{
|
|
WriteAttributeStatement(writer, attributeStatement);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4086, statement.GetType())));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the SamlSubject from the XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader to read the SamlSubject from.</param>
|
|
/// <returns>SamlSubject</returns>
|
|
/// <exception cref="ArgumentNullException">The input argument 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The reader is not positioned at a SamlSubject.</exception>
|
|
protected virtual SamlSubject ReadSubject(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Subject, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
SamlSubject subject = new SamlSubject();
|
|
|
|
reader.ReadStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace);
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.NameIdentifier, SamlConstants.Namespace))
|
|
{
|
|
subject.NameFormat = reader.GetAttribute(SamlConstants.AttributeNames.NameIdentifierFormat, null);
|
|
subject.NameQualifier = reader.GetAttribute(SamlConstants.AttributeNames.NameIdentifierNameQualifier, null);
|
|
|
|
reader.MoveToContent();
|
|
subject.Name = reader.ReadElementString();
|
|
|
|
if (string.IsNullOrEmpty(subject.Name))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4087)));
|
|
}
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmation, SamlConstants.Namespace))
|
|
{
|
|
reader.ReadStartElement();
|
|
|
|
while (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmationMethod, SamlConstants.Namespace))
|
|
{
|
|
string method = reader.ReadElementString();
|
|
if (string.IsNullOrEmpty(method))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4088)));
|
|
}
|
|
|
|
subject.ConfirmationMethods.Add(method);
|
|
}
|
|
|
|
if (subject.ConfirmationMethods.Count == 0)
|
|
{
|
|
// A SubjectConfirmaton clause should specify at least one
|
|
// ConfirmationMethod.
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4088)));
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.SubjectConfirmationData, SamlConstants.Namespace))
|
|
{
|
|
// An Authentication protocol specified in the confirmation method might need this
|
|
// data. Just store this content value as string.
|
|
subject.SubjectConfirmationData = reader.ReadElementString();
|
|
}
|
|
|
|
if (reader.IsStartElement(XmlSignatureConstants.Elements.KeyInfo, XmlSignatureConstants.Namespace))
|
|
{
|
|
subject.KeyIdentifier = ReadSubjectKeyInfo(reader);
|
|
SecurityKey key = ResolveSubjectKeyIdentifier(subject.KeyIdentifier);
|
|
if (key != null)
|
|
{
|
|
subject.Crypto = key;
|
|
}
|
|
else
|
|
{
|
|
subject.Crypto = new SecurityKeyElement(subject.KeyIdentifier, this.Configuration.ServiceTokenResolver);
|
|
}
|
|
}
|
|
|
|
|
|
if ((subject.ConfirmationMethods.Count == 0) && (string.IsNullOrEmpty(subject.Name)))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4089)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return subject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize the given SamlSubject into an XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter into which the SamlSubject is serialized.</param>
|
|
/// <param name="subject">SamlSubject to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'subject' or 'writer' is null.</exception>
|
|
protected virtual void WriteSubject(XmlWriter writer, SamlSubject subject)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Subject, SamlConstants.Namespace);
|
|
if (!string.IsNullOrEmpty(subject.Name))
|
|
{
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.NameIdentifier, SamlConstants.Namespace);
|
|
if (!string.IsNullOrEmpty(subject.NameFormat))
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.NameIdentifierFormat, null, subject.NameFormat);
|
|
}
|
|
if (subject.NameQualifier != null)
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.NameIdentifierNameQualifier, null, subject.NameQualifier);
|
|
}
|
|
writer.WriteString(subject.Name);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
if (subject.ConfirmationMethods.Count > 0)
|
|
{
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.SubjectConfirmation, SamlConstants.Namespace);
|
|
|
|
foreach (string method in subject.ConfirmationMethods)
|
|
{
|
|
writer.WriteElementString(SamlConstants.ElementNames.SubjectConfirmationMethod, SamlConstants.Namespace, method);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(subject.SubjectConfirmationData))
|
|
{
|
|
writer.WriteElementString(SamlConstants.ElementNames.SubjectConfirmationData, SamlConstants.Namespace, subject.SubjectConfirmationData);
|
|
}
|
|
|
|
if (subject.KeyIdentifier != null)
|
|
{
|
|
WriteSubjectKeyInfo(writer, subject.KeyIdentifier);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the SamlSubject KeyIdentifier from a XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at the SamlSubject KeyIdentifier.</param>
|
|
/// <returns>SamlSubject Key as a SecurityKeyIdentifier.</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">XmlReader is not positioned at a valid SecurityKeyIdentifier.</exception>
|
|
protected virtual SecurityKeyIdentifier ReadSubjectKeyInfo(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (KeyInfoSerializer.CanReadKeyIdentifier(reader))
|
|
{
|
|
return KeyInfoSerializer.ReadKeyIdentifier(reader);
|
|
}
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4090)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the SamlSubject SecurityKeyIdentifier to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to write the SecurityKeyIdentifier.</param>
|
|
/// <param name="subjectSki">SecurityKeyIdentifier to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'writer' or 'subjectSki' is null.</exception>
|
|
protected virtual void WriteSubjectKeyInfo(XmlWriter writer, SecurityKeyIdentifier subjectSki)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (subjectSki == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subjectSki");
|
|
}
|
|
|
|
if (KeyInfoSerializer.CanWriteKeyIdentifier(subjectSki))
|
|
{
|
|
KeyInfoSerializer.WriteKeyIdentifier(writer, subjectSki);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("subjectSki", SR.GetString(SR.ID4091, subjectSki.GetType()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read saml:AttributeStatement from the given XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a saml:AttributeStatement element.</param>
|
|
/// <returns>SamlAttributeStatement</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">XmlReader is not positioned at a saml:AttributeStatement element or
|
|
/// the AttributeStatement contains unrecognized elements.</exception>
|
|
protected virtual SamlAttributeStatement ReadAttributeStatement(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
reader.ReadStartElement();
|
|
|
|
SamlAttributeStatement attributeStatement = new SamlAttributeStatement();
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace))
|
|
{
|
|
attributeStatement.SamlSubject = ReadSubject(reader);
|
|
}
|
|
else
|
|
{
|
|
// SAML Subject is a required Attribute Statement clause.
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4092)));
|
|
}
|
|
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Attribute, SamlConstants.Namespace))
|
|
{
|
|
// SAML Attribute is a extensibility point. So ask the SAML serializer
|
|
// to load this part.
|
|
attributeStatement.Attributes.Add(ReadAttribute(reader));
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (attributeStatement.Attributes.Count == 0)
|
|
{
|
|
// Each Attribute statement should have at least one attribute.
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4093)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return attributeStatement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize a SamlAttributeStatement.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the given statement.</param>
|
|
/// <param name="statement">SamlAttributeStatement to write to the XmlWriter.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'statement' is null.</exception>
|
|
protected virtual void WriteAttributeStatement(XmlWriter writer, SamlAttributeStatement statement)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (statement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AttributeStatement, SamlConstants.Namespace);
|
|
|
|
WriteSubject(writer, statement.SamlSubject);
|
|
|
|
for (int i = 0; i < statement.Attributes.Count; i++)
|
|
{
|
|
WriteAttribute(writer, statement.Attributes[i]);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an saml:Attribute element.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a saml:Attribute element.</param>
|
|
/// <returns>SamlAttribute</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned on a valid saml:Attribute element.</exception>
|
|
protected virtual SamlAttribute ReadAttribute(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
SamlAttribute attribute = new SamlAttribute();
|
|
|
|
attribute.Name = reader.GetAttribute(SamlConstants.AttributeNames.AttributeName, null);
|
|
if (string.IsNullOrEmpty(attribute.Name))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4094)));
|
|
}
|
|
|
|
attribute.Namespace = reader.GetAttribute(SamlConstants.AttributeNames.AttributeNamespace, null);
|
|
if (string.IsNullOrEmpty(attribute.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4095)));
|
|
}
|
|
|
|
//
|
|
// OriginalIssuer is an optional attribute.
|
|
// We are lax on read here, and will accept the following namespaces for original issuer, in order:
|
|
// http://schemas.xmlsoap.org/ws/2009/09/identity/claims
|
|
// http://schemas.microsoft.com/ws/2008/06/identity
|
|
//
|
|
string originalIssuer = reader.GetAttribute(SamlConstants.AttributeNames.OriginalIssuer, ClaimType2009Namespace);
|
|
|
|
if (originalIssuer == null)
|
|
{
|
|
originalIssuer = reader.GetAttribute(SamlConstants.AttributeNames.OriginalIssuer, ProductConstants.NamespaceUri);
|
|
}
|
|
|
|
if (originalIssuer == String.Empty)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4252)));
|
|
}
|
|
attribute.OriginalIssuer = originalIssuer;
|
|
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
while (reader.IsStartElement(SamlConstants.ElementNames.AttributeValue, SamlConstants.Namespace))
|
|
{
|
|
// FIP 9570 - ENTERPRISE SCENARIO: Saml11SecurityTokenHandler.ReadAttribute is not checking the AttributeValue XSI type correctly.
|
|
// Lax on receive. If we dont find the AttributeValueXsiType in the format we are looking for in the xml, we default to string.
|
|
// Read the xsi:type. We are expecting a value of the form "some-non-empty-string" or "some-non-empty-local-prefix:some-non-empty-string".
|
|
// ":some-non-empty-string" and "some-non-empty-string:" are edge-cases where defaulting to string is reasonable.
|
|
string attributeValueXsiTypePrefix = null;
|
|
string attributeValueXsiTypeSuffix = null;
|
|
string attributeValueXsiTypeSuffixWithLocalPrefix = reader.GetAttribute("type", XmlSchema.InstanceNamespace);
|
|
if (!string.IsNullOrEmpty(attributeValueXsiTypeSuffixWithLocalPrefix))
|
|
{
|
|
if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) == -1) // "some-non-empty-string" case
|
|
{
|
|
attributeValueXsiTypePrefix = reader.LookupNamespace(String.Empty);
|
|
attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix;
|
|
}
|
|
else if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) > 0 &&
|
|
attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) < attributeValueXsiTypeSuffixWithLocalPrefix.Length - 1) // "some-non-empty-local-prefix:some-non-empty-string" case
|
|
{
|
|
string localPrefix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(0, attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal));
|
|
attributeValueXsiTypePrefix = reader.LookupNamespace(localPrefix);
|
|
// For attributeValueXsiTypeSuffix, we want the portion after the local prefix in "some-non-empty-local-prefix:some-non-empty-string"
|
|
attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) + 1);
|
|
}
|
|
}
|
|
if (attributeValueXsiTypePrefix != null && attributeValueXsiTypeSuffix != null)
|
|
{
|
|
attribute.AttributeValueXsiType = String.Concat(attributeValueXsiTypePrefix, "#", attributeValueXsiTypeSuffix);
|
|
}
|
|
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
reader.Read();
|
|
attribute.AttributeValues.Add(string.Empty);
|
|
}
|
|
else
|
|
{
|
|
attribute.AttributeValues.Add(ReadAttributeValue(reader, attribute));
|
|
}
|
|
}
|
|
|
|
if (attribute.AttributeValues.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4212)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return attribute;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Reads an attribute value.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader to read from.</param>
|
|
/// <param name="attribute">The current attribute that is being read.</param>
|
|
/// <returns>The attribute value as a string.</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
protected virtual string ReadAttributeValue(XmlReader reader, SamlAttribute attribute)
|
|
{
|
|
// This code was designed realizing that the writter of the xml controls how our
|
|
// reader will report the NodeType. A completely differnet system (IBM, etc) could write the values.
|
|
// Considering NodeType is important, because we need to read the entire value, end element and not loose anything significant.
|
|
//
|
|
// Couple of cases to help understand the design choices.
|
|
//
|
|
// 1.
|
|
// "<MyElement xmlns=""urn:mynamespace""><another>complex</another></MyElement><sibling>value</sibling>"
|
|
// Could result in the our reader reporting the NodeType as Text OR Element, depending if '<' was entitized to '<'
|
|
//
|
|
// 2.
|
|
// " <MyElement xmlns=""urn:mynamespace""><another>complex</another></MyElement><sibling>value</sibling>"
|
|
// Could result in the our reader reporting the NodeType as Text OR Whitespace. Post Whitespace processing, the NodeType could be
|
|
// reported as Text or Element, depending if '<' was entitized to '<'
|
|
//
|
|
// 3.
|
|
// "/r/n/t "
|
|
// Could result in the our reader reporting the NodeType as whitespace.
|
|
//
|
|
// Since an AttributeValue with ONLY Whitespace and a complex Element proceeded by whitespace are reported as the same NodeType (2. and 3.)
|
|
// the whitespace is remembered and discarded if an found is found, otherwise it becomes the value. This is to help users who accidently put a space when adding claims in ADFS
|
|
// If we just skipped the Whitespace, then an AttributeValue that started with Whitespace would loose that part and claims generated from the AttributeValue
|
|
// would be missing that part.
|
|
//
|
|
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
string result = String.Empty;
|
|
string whiteSpace = String.Empty;
|
|
|
|
reader.ReadStartElement(Saml2Constants.Elements.AttributeValue, SamlConstants.Namespace);
|
|
|
|
while (reader.NodeType == XmlNodeType.Whitespace)
|
|
{
|
|
whiteSpace += reader.Value;
|
|
reader.Read();
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
if (reader.NodeType == XmlNodeType.Element)
|
|
{
|
|
while (reader.NodeType == XmlNodeType.Element)
|
|
{
|
|
result += reader.ReadOuterXml();
|
|
reader.MoveToContent();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = whiteSpace;
|
|
result += reader.ReadContentAsString();
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes a given SamlAttribute.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlAttribute.</param>
|
|
/// <param name="attribute">SamlAttribute to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'attribute' is null.</exception>
|
|
protected virtual void WriteAttribute(XmlWriter writer, SamlAttribute attribute)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (attribute == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("attribute");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Attribute, SamlConstants.Namespace);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.AttributeName, null, attribute.Name);
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.AttributeNamespace, null, attribute.Namespace);
|
|
|
|
SamlAttribute SamlAttribute = attribute as SamlAttribute;
|
|
if ((SamlAttribute != null) && (SamlAttribute.OriginalIssuer != null))
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.OriginalIssuer, ClaimType2009Namespace, SamlAttribute.OriginalIssuer);
|
|
}
|
|
|
|
string xsiTypePrefix = null;
|
|
string xsiTypeSuffix = null;
|
|
if (SamlAttribute != null && !StringComparer.Ordinal.Equals(SamlAttribute.AttributeValueXsiType, ClaimValueTypes.String))
|
|
{
|
|
// ClaimValueTypes are URIs of the form prefix#suffix, while xsi:type should be a QName.
|
|
// Hence, the tokens-to-claims spec requires that ClaimValueTypes be serialized as xmlns:tn="prefix" xsi:type="tn:suffix"
|
|
int indexOfHash = SamlAttribute.AttributeValueXsiType.IndexOf('#');
|
|
xsiTypePrefix = SamlAttribute.AttributeValueXsiType.Substring(0, indexOfHash);
|
|
xsiTypeSuffix = SamlAttribute.AttributeValueXsiType.Substring(indexOfHash + 1);
|
|
}
|
|
|
|
for (int i = 0; i < attribute.AttributeValues.Count; i++)
|
|
{
|
|
if (attribute.AttributeValues[i] == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4096)));
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AttributeValue, SamlConstants.Namespace);
|
|
if ((xsiTypePrefix != null) && (xsiTypeSuffix != null))
|
|
{
|
|
writer.WriteAttributeString("xmlns", ProductConstants.ClaimValueTypeSerializationPrefix, null, xsiTypePrefix);
|
|
writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, String.Concat(ProductConstants.ClaimValueTypeSerializationPrefixWithColon, xsiTypeSuffix));
|
|
}
|
|
WriteAttributeValue(writer, attribute.AttributeValues[i], attribute);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the saml:Attribute value.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to which to write.</param>
|
|
/// <param name="value">Attribute value to be written.</param>
|
|
/// <param name="attribute">The SAML attribute whose value is being written.</param>
|
|
/// <remarks>By default the method writes the value as a string.</remarks>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' is null.</exception>
|
|
protected virtual void WriteAttributeValue(XmlWriter writer, string value, SamlAttribute attribute)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
writer.WriteString(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the saml:AuthenticationStatement.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a saml:AuthenticationStatement.</param>
|
|
/// <returns>SamlAuthenticationStatement</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned on a saml:AuthenticationStatement
|
|
/// or the statement contains a unknown child element.</exception>
|
|
protected virtual SamlAuthenticationStatement ReadAuthenticationStatement(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
SamlAuthenticationStatement authnStatement = new SamlAuthenticationStatement();
|
|
string authInstance = reader.GetAttribute(SamlConstants.AttributeNames.AuthenticationInstant, null);
|
|
if (string.IsNullOrEmpty(authInstance))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4097)));
|
|
}
|
|
authnStatement.AuthenticationInstant = DateTime.ParseExact(
|
|
authInstance, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime();
|
|
|
|
authnStatement.AuthenticationMethod = reader.GetAttribute(SamlConstants.AttributeNames.AuthenticationMethod, null);
|
|
if (string.IsNullOrEmpty(authnStatement.AuthenticationMethod))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4098)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace))
|
|
{
|
|
authnStatement.SamlSubject = ReadSubject(reader);
|
|
}
|
|
else
|
|
{
|
|
// Subject is a required element for a Authentication Statement clause.
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4099)));
|
|
}
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.SubjectLocality, SamlConstants.Namespace))
|
|
{
|
|
authnStatement.DnsAddress = reader.GetAttribute(SamlConstants.AttributeNames.SubjectLocalityDNSAddress, null);
|
|
authnStatement.IPAddress = reader.GetAttribute(SamlConstants.AttributeNames.SubjectLocalityIPAddress, null);
|
|
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
}
|
|
else
|
|
{
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
reader.ReadEndElement();
|
|
}
|
|
}
|
|
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace))
|
|
{
|
|
authnStatement.AuthorityBindings.Add(ReadAuthorityBinding(reader));
|
|
}
|
|
else
|
|
{
|
|
// We do not understand this element.
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return authnStatement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes a given SamlAuthenticationStatement.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to which SamlAuthenticationStatement is serialized.</param>
|
|
/// <param name="statement">SamlAuthenticationStatement to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'statement' is null.</exception>
|
|
protected virtual void WriteAuthenticationStatement(XmlWriter writer, SamlAuthenticationStatement statement)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (statement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthenticationStatement, SamlConstants.Namespace);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.AuthenticationMethod, null, statement.AuthenticationMethod);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.AuthenticationInstant, null,
|
|
XmlConvert.ToString(statement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated));
|
|
|
|
|
|
WriteSubject(writer, statement.SamlSubject);
|
|
|
|
if ((statement.IPAddress != null) || (statement.DnsAddress != null))
|
|
{
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.SubjectLocality, SamlConstants.Namespace);
|
|
|
|
if (statement.IPAddress != null)
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.SubjectLocalityIPAddress, null, statement.IPAddress);
|
|
}
|
|
|
|
if (statement.DnsAddress != null)
|
|
{
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.SubjectLocalityDNSAddress, null, statement.DnsAddress);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
for (int i = 0; i < statement.AuthorityBindings.Count; i++)
|
|
{
|
|
WriteAuthorityBinding(writer, statement.AuthorityBindings[i]);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the saml:AuthorityBinding element.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at the saml:AuthorityBinding element.</param>
|
|
/// <returns>SamlAuthorityBinding</returns>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">XmlReader is not positioned at a saml:AuthorityBinding element or
|
|
/// contains a unrecognized or invalid child element.</exception>
|
|
protected virtual SamlAuthorityBinding ReadAuthorityBinding(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
SamlAuthorityBinding authorityBinding = new SamlAuthorityBinding();
|
|
string authKind = reader.GetAttribute(SamlConstants.AttributeNames.AuthorityKind, null);
|
|
if (string.IsNullOrEmpty(authKind))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4200)));
|
|
}
|
|
|
|
string[] authKindParts = authKind.Split(':');
|
|
if (authKindParts.Length > 2)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4201, authKind)));
|
|
}
|
|
|
|
string localName;
|
|
string prefix;
|
|
string nameSpace;
|
|
if (authKindParts.Length == 2)
|
|
{
|
|
prefix = authKindParts[0];
|
|
localName = authKindParts[1];
|
|
}
|
|
else
|
|
{
|
|
prefix = String.Empty;
|
|
localName = authKindParts[0];
|
|
}
|
|
|
|
nameSpace = reader.LookupNamespace(prefix);
|
|
|
|
authorityBinding.AuthorityKind = new XmlQualifiedName(localName, nameSpace);
|
|
|
|
authorityBinding.Binding = reader.GetAttribute(SamlConstants.AttributeNames.Binding, null);
|
|
if (string.IsNullOrEmpty(authorityBinding.Binding))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4202)));
|
|
}
|
|
|
|
authorityBinding.Location = reader.GetAttribute(SamlConstants.AttributeNames.Location, null);
|
|
if (string.IsNullOrEmpty(authorityBinding.Location))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4203)));
|
|
}
|
|
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
}
|
|
else
|
|
{
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return authorityBinding;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize a SamlAuthorityBinding.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlAuthorityBinding</param>
|
|
/// <param name="authorityBinding">SamlAuthoriyBinding to be serialized.</param>
|
|
protected virtual void WriteAuthorityBinding(XmlWriter writer, SamlAuthorityBinding authorityBinding)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (authorityBinding == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthorityBinding, SamlConstants.Namespace);
|
|
|
|
string prefix = null;
|
|
if (!string.IsNullOrEmpty(authorityBinding.AuthorityKind.Namespace))
|
|
{
|
|
writer.WriteAttributeString(String.Empty, SamlConstants.AttributeNames.NamespaceAttributePrefix, null, authorityBinding.AuthorityKind.Namespace);
|
|
prefix = writer.LookupPrefix(authorityBinding.AuthorityKind.Namespace);
|
|
}
|
|
|
|
writer.WriteStartAttribute(SamlConstants.AttributeNames.AuthorityKind, null);
|
|
if (string.IsNullOrEmpty(prefix))
|
|
{
|
|
writer.WriteString(authorityBinding.AuthorityKind.Name);
|
|
}
|
|
else
|
|
{
|
|
writer.WriteString(prefix + ":" + authorityBinding.AuthorityKind.Name);
|
|
}
|
|
writer.WriteEndAttribute();
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Location, null, authorityBinding.Location);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Binding, null, authorityBinding.Binding);
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets a boolean indicating if the SecurityTokenHandler can Serialize Tokens. Return true by default.
|
|
/// </summary>
|
|
public override bool CanWriteToken
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given SecurityToken to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter into which the token is serialized.</param>
|
|
/// <param name="token">SecurityToken to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'writer' or 'token' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">The given 'token' is not a SamlSecurityToken.</exception>
|
|
public override void WriteToken(XmlWriter writer, SecurityToken token)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
SamlSecurityToken samlSecurityToken = token as SamlSecurityToken;
|
|
if (samlSecurityToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4217, token.GetType(), typeof(SamlSecurityToken))));
|
|
}
|
|
|
|
WriteAssertion(writer, samlSecurityToken.Assertion);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the saml:AuthorizationDecisionStatement element.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader position at saml:AuthorizationDecisionStatement.</param>
|
|
/// <returns>SamlAuthorizationDecisionStatement</returns>
|
|
/// <exception cref="ArgumentNullException">The inpur parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at a saml:AuthorizationDecisionStatement or
|
|
/// the statement contains child elments that are unknown or invalid.</exception>
|
|
protected virtual
|
|
SamlAuthorizationDecisionStatement ReadAuthorizationDecisionStatement(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
SamlAuthorizationDecisionStatement authzStatement = new SamlAuthorizationDecisionStatement();
|
|
authzStatement.Resource = reader.GetAttribute(SamlConstants.AttributeNames.Resource, null);
|
|
if (string.IsNullOrEmpty(authzStatement.Resource))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4205)));
|
|
}
|
|
|
|
string decisionString = reader.GetAttribute(SamlConstants.AttributeNames.Decision, null);
|
|
if (string.IsNullOrEmpty(decisionString))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4204)));
|
|
}
|
|
|
|
if (decisionString.Equals(SamlAccessDecision.Deny.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
authzStatement.AccessDecision = SamlAccessDecision.Deny;
|
|
}
|
|
else if (decisionString.Equals(SamlAccessDecision.Permit.ToString(), StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
authzStatement.AccessDecision = SamlAccessDecision.Permit;
|
|
}
|
|
else
|
|
{
|
|
authzStatement.AccessDecision = SamlAccessDecision.Indeterminate;
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.Read();
|
|
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Subject, SamlConstants.Namespace))
|
|
{
|
|
authzStatement.SamlSubject = ReadSubject(reader);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4206)));
|
|
}
|
|
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.Action, SamlConstants.Namespace))
|
|
{
|
|
authzStatement.SamlActions.Add(ReadAction(reader));
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.Evidence, SamlConstants.Namespace))
|
|
{
|
|
if (authzStatement.Evidence != null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4207)));
|
|
}
|
|
|
|
authzStatement.Evidence = ReadEvidence(reader);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4208, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
if (authzStatement.SamlActions.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4209)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return authzStatement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serialize a SamlAuthorizationDecisionStatement.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to which the SamlAuthorizationStatement is serialized.</param>
|
|
/// <param name="statement">SamlAuthorizationDecisionStatement to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'statement' is null.</exception>
|
|
protected virtual void WriteAuthorizationDecisionStatement(XmlWriter writer, SamlAuthorizationDecisionStatement statement)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (statement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.AuthorizationDecisionStatement, SamlConstants.Namespace);
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Decision, null, statement.AccessDecision.ToString());
|
|
|
|
writer.WriteAttributeString(SamlConstants.AttributeNames.Resource, null, statement.Resource);
|
|
|
|
WriteSubject(writer, statement.SamlSubject);
|
|
|
|
foreach (SamlAction action in statement.SamlActions)
|
|
{
|
|
WriteAction(writer, action);
|
|
}
|
|
|
|
if (statement.Evidence != null)
|
|
{
|
|
WriteEvidence(writer, statement.Evidence);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the saml:Evidence element.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at saml:Evidence element.</param>
|
|
/// <returns>SamlEvidence</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The XmlReader is not positioned at a saml:Evidence element or
|
|
/// the element contains unrecognized or invalid child elements.</exception>
|
|
protected virtual SamlEvidence ReadEvidence(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(SamlConstants.ElementNames.Evidence, SamlConstants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4082, SamlConstants.ElementNames.Evidence, SamlConstants.Namespace, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
|
|
SamlEvidence evidence = new SamlEvidence();
|
|
reader.ReadStartElement();
|
|
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace))
|
|
{
|
|
evidence.AssertionIdReferences.Add(reader.ReadElementString());
|
|
}
|
|
else if (reader.IsStartElement(SamlConstants.ElementNames.Assertion, SamlConstants.Namespace))
|
|
{
|
|
evidence.Assertions.Add(ReadAssertion(reader));
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4210, reader.LocalName, reader.NamespaceURI)));
|
|
}
|
|
}
|
|
|
|
if ((evidence.AssertionIdReferences.Count == 0) && (evidence.Assertions.Count == 0))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4211)));
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
reader.ReadEndElement();
|
|
|
|
return evidence;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes a given SamlEvidence.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SamlEvidence.</param>
|
|
/// <param name="evidence">SamlEvidence to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'evidence' is null.</exception>
|
|
protected virtual void WriteEvidence(XmlWriter writer, SamlEvidence evidence)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (evidence == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("evidence");
|
|
}
|
|
|
|
writer.WriteStartElement(SamlConstants.Prefix, SamlConstants.ElementNames.Evidence, SamlConstants.Namespace);
|
|
|
|
for (int i = 0; i < evidence.AssertionIdReferences.Count; i++)
|
|
{
|
|
writer.WriteElementString(SamlConstants.Prefix, SamlConstants.ElementNames.AssertionIdReference, SamlConstants.Namespace, evidence.AssertionIdReferences[i]);
|
|
}
|
|
|
|
for (int i = 0; i < evidence.Assertions.Count; i++)
|
|
{
|
|
WriteAssertion(writer, evidence.Assertions[i]);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the SecurityKeyIdentifier specified in a saml:Subject element.
|
|
/// </summary>
|
|
/// <param name="subjectKeyIdentifier">SecurityKeyIdentifier to resolve into a key.</param>
|
|
/// <returns>SecurityKey</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'subjectKeyIdentifier' is null.</exception>
|
|
protected virtual SecurityKey ResolveSubjectKeyIdentifier(SecurityKeyIdentifier subjectKeyIdentifier)
|
|
{
|
|
if (subjectKeyIdentifier == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subjectKeyIdentifier");
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (this.Configuration.ServiceTokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276));
|
|
}
|
|
|
|
SecurityKey key = null;
|
|
foreach (SecurityKeyIdentifierClause clause in subjectKeyIdentifier)
|
|
{
|
|
if (this.Configuration.ServiceTokenResolver.TryResolveSecurityKey(clause, out key))
|
|
{
|
|
return key;
|
|
}
|
|
}
|
|
|
|
if (subjectKeyIdentifier.CanCreateKey)
|
|
{
|
|
return subjectKeyIdentifier.CreateKey();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the Signing Key Identifier to a SecurityToken.
|
|
/// </summary>
|
|
/// <param name="assertion">The Assertion for which the Issuer token is to be resolved.</param>
|
|
/// <param name="issuerResolver">The current SecurityTokenResolver associated with this handler.</param>
|
|
/// <returns>Instance of SecurityToken</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'assertion' is null.</exception>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'issuerResolver' is null.</exception>///
|
|
/// <exception cref="SecurityTokenException">Unable to resolve token.</exception>
|
|
protected virtual SecurityToken ResolveIssuerToken(SamlAssertion assertion, SecurityTokenResolver issuerResolver)
|
|
{
|
|
if (null == assertion)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
if (null == issuerResolver)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("issuerResolver");
|
|
}
|
|
|
|
SecurityToken token;
|
|
if (TryResolveIssuerToken(assertion, issuerResolver, out token))
|
|
{
|
|
return token;
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4220)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the Signing Key Identifier to a SecurityToken.
|
|
/// </summary>
|
|
/// <param name="assertion">The Assertion for which the Issuer token is to be resolved.</param>
|
|
/// <param name="issuerResolver">The current SecurityTokenResolver associated with this handler.</param>
|
|
/// <param name="token">Resolved token.</param>
|
|
/// <returns>True if token is resolved.</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'assertion' is null.</exception>
|
|
protected virtual bool TryResolveIssuerToken(SamlAssertion assertion, SecurityTokenResolver issuerResolver, out SecurityToken token)
|
|
{
|
|
if (null == assertion)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
if (assertion.SigningCredentials != null
|
|
&& assertion.SigningCredentials.SigningKeyIdentifier != null
|
|
&& issuerResolver != null)
|
|
{
|
|
SecurityKeyIdentifier keyIdentifier = assertion.SigningCredentials.SigningKeyIdentifier;
|
|
return issuerResolver.TryResolveToken(keyIdentifier, out token);
|
|
}
|
|
else
|
|
{
|
|
token = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the ds:KeyInfo element inside the Saml Signature.
|
|
/// </summary>
|
|
/// <param name="reader">An XmlReader that can be positioned at a ds:KeyInfo element.</param>
|
|
/// <param name="assertion">The assertion that is having the signature checked.</param>
|
|
/// <returns>The <see cref="SecurityKeyIdentifier"/> that defines the key to use to check the signature.</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Unable to read the KeyIdentifier from the XmlReader.</exception>
|
|
/// <remarks>If the reader is not positioned at a ds:KeyInfo element, the <see cref="SecurityKeyIdentifier"/> returned will
|
|
/// contain a single <see cref="SecurityKeyIdentifierClause"/> of type <see cref="EmptySecurityKeyIdentifierClause"/></remarks>
|
|
protected virtual SecurityKeyIdentifier ReadSigningKeyInfo(XmlReader reader, SamlAssertion assertion)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
SecurityKeyIdentifier ski;
|
|
|
|
if (KeyInfoSerializer.CanReadKeyIdentifier(reader))
|
|
{
|
|
ski = KeyInfoSerializer.ReadKeyIdentifier(reader);
|
|
}
|
|
else
|
|
{
|
|
KeyInfo keyInfo = new KeyInfo(KeyInfoSerializer);
|
|
keyInfo.ReadXml(XmlDictionaryReader.CreateDictionaryReader(reader));
|
|
ski = keyInfo.KeyIdentifier;
|
|
}
|
|
|
|
// no key info
|
|
if (ski.Count == 0)
|
|
{
|
|
return new SecurityKeyIdentifier(new SamlSecurityKeyIdentifierClause(assertion));
|
|
}
|
|
|
|
return ski;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the Signing SecurityKeyIdentifier.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to serialize the SecurityKeyIdentifier.</param>
|
|
/// <param name="signingKeyIdentifier">Signing SecurityKeyIdentifier.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'signingKeyIdentifier' is null.</exception>
|
|
protected virtual void WriteSigningKeyInfo(XmlWriter writer, SecurityKeyIdentifier signingKeyIdentifier)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (signingKeyIdentifier == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("signingKeyIdentifier");
|
|
}
|
|
|
|
if (KeyInfoSerializer.CanWriteKeyIdentifier(signingKeyIdentifier))
|
|
{
|
|
KeyInfoSerializer.WriteKeyIdentifier(writer, signingKeyIdentifier);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4221, signingKeyIdentifier));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates the subject in each statement in the collection of Saml statements.
|
|
/// </summary>
|
|
/// <param name="statements"></param>
|
|
private void ValidateStatements(IList<SamlStatement> statements)
|
|
{
|
|
if (statements == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statements");
|
|
}
|
|
List<SamlSubject> subjects = new List<SamlSubject>();
|
|
foreach (SamlStatement statement in statements)
|
|
{
|
|
if (statement is SamlAttributeStatement)
|
|
{
|
|
subjects.Add((statement as SamlAttributeStatement).SamlSubject);
|
|
}
|
|
|
|
if (statement is SamlAuthenticationStatement)
|
|
{
|
|
subjects.Add((statement as SamlAuthenticationStatement).SamlSubject);
|
|
}
|
|
|
|
if (statement is SamlAuthorizationDecisionStatement)
|
|
{
|
|
subjects.Add((statement as SamlAuthorizationDecisionStatement).SamlSubject);
|
|
}
|
|
//
|
|
// skip all custom statements
|
|
//
|
|
|
|
}
|
|
|
|
if (subjects.Count == 0)
|
|
{
|
|
//
|
|
// All statements are custom and we cannot validate
|
|
//
|
|
return;
|
|
}
|
|
string requiredSubjectName = subjects[0].Name;
|
|
string requiredSubjectFormat = subjects[0].NameFormat;
|
|
string requiredSubjectQualifier = subjects[0].NameQualifier;
|
|
|
|
foreach (SamlSubject subject in subjects)
|
|
{
|
|
if (!StringComparer.Ordinal.Equals(subject.Name, requiredSubjectName) ||
|
|
!StringComparer.Ordinal.Equals(subject.NameFormat, requiredSubjectFormat) ||
|
|
!StringComparer.Ordinal.Equals(subject.NameQualifier, requiredSubjectQualifier))
|
|
{
|
|
//
|
|
// The SamlSubjects in the statements do not match
|
|
//
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4225, subject));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Returns the saml token's token type that is supported by this handler.
|
|
/// </summary>
|
|
public override string[] GetTokenTypeIdentifiers()
|
|
{
|
|
return _tokenTypeIdentifiers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or Sets a SecurityTokenSerializers that will be used to serialize and deserializer
|
|
/// SecurtyKeyIdentifier. For example, SamlSubject SecurityKeyIdentifier or Signature
|
|
/// SecurityKeyIdentifier.
|
|
/// </summary>
|
|
public SecurityTokenSerializer KeyInfoSerializer
|
|
{
|
|
get
|
|
{
|
|
if (_keyInfoSerializer == null)
|
|
{
|
|
lock (_syncObject)
|
|
{
|
|
if (_keyInfoSerializer == null)
|
|
{
|
|
SecurityTokenHandlerCollection sthc = (ContainingCollection != null) ?
|
|
ContainingCollection : SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
|
|
_keyInfoSerializer = new SecurityTokenSerializerAdapter(sthc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return _keyInfoSerializer;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
}
|
|
|
|
_keyInfoSerializer = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the System.Type of the SecurityToken is supported by ththis handler.
|
|
/// </summary>
|
|
public override Type TokenType
|
|
{
|
|
get { return typeof(SamlSecurityToken); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="SamlSecurityTokenRequirement"/>
|
|
/// </summary>
|
|
public SamlSecurityTokenRequirement SamlSecurityTokenRequirement
|
|
{
|
|
get
|
|
{
|
|
return _samlSecurityTokenRequirement;
|
|
}
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
}
|
|
_samlSecurityTokenRequirement = value;
|
|
}
|
|
}
|
|
|
|
// This thin wrapper is used to pass a serializer down into the
|
|
// EnvelopedSignatureReader that will use the Saml11SecurityTokenHandlers's
|
|
// ReadKeyInfo method to read the KeyInfo.
|
|
class WrappedSerializer : SecurityTokenSerializer
|
|
{
|
|
SamlSecurityTokenHandler _parent;
|
|
SamlAssertion _assertion;
|
|
|
|
public WrappedSerializer(SamlSecurityTokenHandler parent, SamlAssertion assertion)
|
|
{
|
|
if (parent == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("parent");
|
|
}
|
|
|
|
_parent = parent;
|
|
_assertion = assertion;
|
|
}
|
|
|
|
protected override bool CanReadKeyIdentifierClauseCore(XmlReader reader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool CanReadKeyIdentifierCore(XmlReader reader)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
protected override bool CanReadTokenCore(XmlReader reader)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool CanWriteKeyIdentifierClauseCore(SecurityKeyIdentifierClause keyIdentifierClause)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool CanWriteKeyIdentifierCore(SecurityKeyIdentifier keyIdentifier)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool CanWriteTokenCore(SecurityToken token)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override SecurityKeyIdentifierClause ReadKeyIdentifierClauseCore(XmlReader reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
|
|
protected override SecurityKeyIdentifier ReadKeyIdentifierCore(XmlReader reader)
|
|
{
|
|
return _parent.ReadSigningKeyInfo(reader, _assertion);
|
|
}
|
|
|
|
protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
|
|
protected override void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
|
|
protected override void WriteKeyIdentifierCore(XmlWriter writer, SecurityKeyIdentifier keyIdentifier)
|
|
{
|
|
_parent.WriteSigningKeyInfo(writer, keyIdentifier);
|
|
}
|
|
|
|
protected override void WriteTokenCore(XmlWriter writer, SecurityToken token)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
}
|
|
}
|
|
}
|