|
//------------------------------------------------------------
// 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;
if (keyIdentifier.Count < 2 || LocalAppContextSwitches.ProcessMultipleSecurityKeyIdentifierClauses)
return issuerResolver.TryResolveToken(keyIdentifier, out token);
else
return issuerResolver.TryResolveToken(new SecurityKeyIdentifier(keyIdentifier[0]), 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());
}
}
}
}
|