You've already forked linux-packaging-mono
Rewrite with hard-coded offsets into the PE file format to discern if a binary is PE32 or PE32+, and then to determine if it contains a "CLR Data Directory" entry that looks valid. Tested with PE32 and PE32+ compiled Mono binaries, PE32 and PE32+ native binaries, and a random assortment of garbage files. Former-commit-id: 9e7ac86ec84f653a2f79b87183efd5b0ebda001b
5640 lines
241 KiB
C#
5640 lines
241 KiB
C#
//-----------------------------------------------------------------------
|
|
// <copyright file="Saml2SecurityTokenHandler.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//-----------------------------------------------------------------------
|
|
|
|
namespace System.IdentityModel.Tokens
|
|
{
|
|
using System;
|
|
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 SAML2 = System.IdentityModel.Tokens.Saml2Constants;
|
|
using WSC = System.IdentityModel.WSSecureConversationFeb2005Constants;
|
|
using WSC13 = System.IdentityModel.WSSecureConversation13Constants;
|
|
using WSSE = System.IdentityModel.WSSecurity10Constants;
|
|
using WSSE11 = System.IdentityModel.WSSecurity11Constants;
|
|
|
|
/// <summary>
|
|
/// Creates SAML2 assertion-based security tokens
|
|
/// </summary>
|
|
public class Saml2SecurityTokenHandler : SecurityTokenHandler
|
|
{
|
|
/// <summary>
|
|
/// The key identifier value type for SAML 2.0 assertion IDs, as defined
|
|
/// by the OASIS Web Services Security SAML Token Profile 1.1.
|
|
/// </summary>
|
|
public const string TokenProfile11ValueType = "http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLID";
|
|
private const string Actor = "Actor";
|
|
private const string Attribute = "Attribute";
|
|
private static string[] tokenTypeIdentifiers = new string[] { SecurityTokenTypes.Saml2TokenProfile11, SecurityTokenTypes.OasisWssSaml2TokenProfile11 };
|
|
private SamlSecurityTokenRequirement samlSecurityTokenRequirement;
|
|
private SecurityTokenSerializer keyInfoSerializer;
|
|
|
|
const string ClaimType2009Namespace = "http://schemas.xmlsoap.org/ws/2009/09/identity/claims";
|
|
object syncObject = new object();
|
|
|
|
/// <summary>
|
|
/// Creates an instance of <see cref="Saml2SecurityTokenHandler"/>
|
|
/// </summary>
|
|
public Saml2SecurityTokenHandler()
|
|
: this(new SamlSecurityTokenRequirement())
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an instance of <see cref="Saml2SecurityTokenHandler"/>
|
|
/// </summary>
|
|
/// <param name="samlSecurityTokenRequirement">The SamlSecurityTokenRequirement to be used by the Saml2SecurityTokenHandler instance when validating tokens.</param>
|
|
public Saml2SecurityTokenHandler(SamlSecurityTokenRequirement samlSecurityTokenRequirement)
|
|
{
|
|
if (samlSecurityTokenRequirement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlSecurityTokenRequirement");
|
|
}
|
|
|
|
this.samlSecurityTokenRequirement = samlSecurityTokenRequirement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load custom configuration from Xml
|
|
/// </summary>
|
|
/// <param name="customConfigElements">SAML token authentication requirements.</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));
|
|
}
|
|
|
|
this.samlSecurityTokenRequirement = new SamlSecurityTokenRequirement(configElement);
|
|
|
|
foundValidConfig = true;
|
|
}
|
|
|
|
if (!foundValidConfig)
|
|
{
|
|
this.samlSecurityTokenRequirement = new SamlSecurityTokenRequirement();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a value that indicates if this handler can validate <see cref="SecurityToken"/>.
|
|
/// </summary>
|
|
/// <returns>'True', indicating this instance can validate <see cref="SecurityToken"/>.</returns>
|
|
public override bool CanValidateToken
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the token type supported by this handler.
|
|
/// </summary>
|
|
public override Type TokenType
|
|
{
|
|
get { return typeof(Saml2SecurityToken); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="X509CertificateValidator"/> that is used by the current instance to validate
|
|
/// certificates that have signed the <see cref="Saml2SecurityToken"/>.
|
|
/// </summary>
|
|
public X509CertificateValidator CertificateValidator
|
|
{
|
|
get
|
|
{
|
|
if (this.samlSecurityTokenRequirement.CertificateValidator == null)
|
|
{
|
|
if (Configuration != null)
|
|
{
|
|
return Configuration.CertificateValidator;
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return this.samlSecurityTokenRequirement.CertificateValidator;
|
|
}
|
|
}
|
|
|
|
set
|
|
{
|
|
this.samlSecurityTokenRequirement.CertificateValidator = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets a <see cref="SecurityTokenSerializer"/> that will be used to serialize and deserialize
|
|
/// a <see cref="SecurityKeyIdentifier"/>. For example, SamlSubject SecurityKeyIdentifier or Signature
|
|
/// SecurityKeyIdentifier.
|
|
/// </summary>
|
|
public SecurityTokenSerializer KeyInfoSerializer
|
|
{
|
|
get
|
|
{
|
|
if ( this.keyInfoSerializer == null )
|
|
{
|
|
lock ( this.syncObject )
|
|
{
|
|
if ( this.keyInfoSerializer == null )
|
|
{
|
|
SecurityTokenHandlerCollection sthc = ( ContainingCollection != null ) ?
|
|
ContainingCollection : SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
|
|
this.keyInfoSerializer = new SecurityTokenSerializerAdapter(sthc);
|
|
}
|
|
}
|
|
}
|
|
|
|
return this.keyInfoSerializer;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
}
|
|
|
|
this.keyInfoSerializer = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the value if this instance can write a token.
|
|
/// </summary>
|
|
public override bool CanWriteToken
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the <see cref="SamlSecurityTokenRequirement"/>.
|
|
/// </summary>
|
|
public SamlSecurityTokenRequirement SamlSecurityTokenRequirement
|
|
{
|
|
get
|
|
{
|
|
return this.samlSecurityTokenRequirement;
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
|
}
|
|
|
|
this.samlSecurityTokenRequirement = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="SecurityKeyIdentifierClause"/> to be used as 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 <see cref="SamlAssertionKeyIdentifierClause"/> instance.</returns>
|
|
public override SecurityKeyIdentifierClause CreateSecurityTokenReference(SecurityToken token, bool attached)
|
|
{
|
|
if (null == token)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
return token.CreateKeyIdentifierClause<Saml2AssertionKeyIdentifierClause>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="SecurityToken"/> based on a information contained in the <see cref="SecurityTokenDescriptor"/>.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The <see cref="SecurityTokenDescriptor"/> that has creation information.</param>
|
|
/// <returns>A <see cref="SecurityToken"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'tokenDescriptor' is null.</exception>
|
|
public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
// Assertion/issuer
|
|
Saml2Assertion assertion = new Saml2Assertion(this.CreateIssuerNameIdentifier(tokenDescriptor));
|
|
|
|
// Subject
|
|
assertion.Subject = this.CreateSamlSubject(tokenDescriptor);
|
|
|
|
// Signature
|
|
assertion.SigningCredentials = this.GetSigningCredentials(tokenDescriptor);
|
|
|
|
// Conditions
|
|
assertion.Conditions = this.CreateConditions(tokenDescriptor.Lifetime, tokenDescriptor.AppliesToAddress, tokenDescriptor);
|
|
|
|
// Advice
|
|
assertion.Advice = this.CreateAdvice(tokenDescriptor);
|
|
|
|
// Statements
|
|
IEnumerable<Saml2Statement> statements = this.CreateStatements(tokenDescriptor);
|
|
if (null != statements)
|
|
{
|
|
foreach (Saml2Statement statement in statements)
|
|
{
|
|
assertion.Statements.Add(statement);
|
|
}
|
|
}
|
|
|
|
// encrypting credentials
|
|
assertion.EncryptingCredentials = this.GetEncryptingCredentials(tokenDescriptor);
|
|
|
|
SecurityToken token = new Saml2SecurityToken(assertion);
|
|
|
|
return token;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the token type identifier(s) supported by this handler.
|
|
/// </summary>
|
|
/// <returns>A collection of strings that identify the tokens this instance can handle.</returns>
|
|
public override string[] GetTokenTypeIdentifiers()
|
|
{
|
|
return tokenTypeIdentifiers;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a <see cref="Saml2SecurityToken"/>.
|
|
/// </summary>
|
|
/// <param name="token">The <see cref="Saml2SecurityToken"/> to validate.</param>
|
|
/// <returns>A <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 of assignable from <see cref="Saml2SecurityToken"/>.</exception>
|
|
/// <exception cref="InvalidOperationException">Configuration <see cref="SecurityTokenHandlerConfiguration"/>is null.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">Thrown if Saml2SecurityToken.Assertion.IssuerToken is null.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">Thrown if Saml2SecurityToken.Assertion.SigningToken is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Saml2SecurityToken.Assertion is null.</exception>
|
|
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
Saml2SecurityToken samlToken = token as Saml2SecurityToken;
|
|
if (samlToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID4151));
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
try
|
|
{
|
|
TraceUtility.TraceEvent(TraceEventType.Verbose, TraceCode.Diagnostics, SR.GetString(SR.TraceValidateToken), new SecurityTraceRecordHelper.TokenTraceRecord(token), null, null);
|
|
|
|
if (samlToken.IssuerToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4152)));
|
|
}
|
|
|
|
if (samlToken.Assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1034));
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
Saml2SubjectConfirmation subjectConfirmation = samlToken.Assertion.Subject.SubjectConfirmations[0];
|
|
if (subjectConfirmation.SubjectConfirmationData != null)
|
|
{
|
|
this.ValidateConfirmationData(subjectConfirmation.SubjectConfirmationData);
|
|
}
|
|
|
|
// If the backing token is x509, validate trust
|
|
X509SecurityToken issuerToken = samlToken.IssuerToken as X509SecurityToken;
|
|
if (issuerToken != null)
|
|
{
|
|
this.CertificateValidator.Validate(issuerToken.Certificate);
|
|
}
|
|
|
|
ClaimsIdentity claimsIdentity = null;
|
|
|
|
if (this.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.
|
|
claimsIdentity = this.CreateWindowsIdentity(this.FindUpn(claimsIdentity));
|
|
|
|
// PARTIAL TRUST: will fail when adding claims, AddClaims is SecurityCritical.
|
|
claimsIdentity.AddClaims(this.CreateClaims(samlToken).Claims);
|
|
}
|
|
else
|
|
{
|
|
claimsIdentity = this.CreateClaims(samlToken);
|
|
}
|
|
|
|
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>
|
|
/// Writes a Saml2 Token using the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityToken"/>.</param>
|
|
/// <param name="token">The <see cref="SecurityToken"/> to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The input argument 'writer' or 'token' is null.</exception>
|
|
/// <exception cref="ArgumentException">The input argument 'token' is not a <see cref="Saml2SecurityToken"/>.</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");
|
|
}
|
|
|
|
Saml2SecurityToken samlToken = token as Saml2SecurityToken;
|
|
|
|
if (null != samlToken)
|
|
{
|
|
this.WriteAssertion(writer, samlToken.Assertion);
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID4160));
|
|
}
|
|
}
|
|
|
|
/// <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 <see cref="XmlReader"/> reader positioned at a start element. The reader should not be advanced.</param>
|
|
/// <returns>'True' if the ReadToken method can read the element.</returns>
|
|
public override bool CanReadToken(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return reader.IsStartElement(SAML2.Elements.Assertion, SAML2.Namespace)
|
|
|| reader.IsStartElement(SAML2.Elements.EncryptedAssertion, SAML2.Namespace);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if the current XML element is pointing to a KeyIdentifierClause that
|
|
/// can be serialized by this instance.
|
|
/// </summary>
|
|
/// <param name="reader">An <see cref="XmlReader"/> reader positioned at a start element. The reader should not be advanced.</param>
|
|
/// <returns>'True' if the ReadKeyIdentifierClause can read the element. 'False' otherwise.</returns>
|
|
public override bool CanReadKeyIdentifierClause(XmlReader reader)
|
|
{
|
|
return IsSaml2KeyIdentifierClause(reader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if the given SecurityKeyIdentifierClause can be serialized by this
|
|
/// instance.
|
|
/// </summary>
|
|
/// <param name="securityKeyIdentifierClause">SecurityKeyIdentifierClause to be serialized.</param>
|
|
/// <returns>"True' if the given SecurityKeyIdentifierClause can be serialized. 'False' otherwise.</returns>
|
|
public override bool CanWriteKeyIdentifierClause(SecurityKeyIdentifierClause securityKeyIdentifierClause)
|
|
{
|
|
return (securityKeyIdentifierClause is Saml2AssertionKeyIdentifierClause) ||
|
|
(securityKeyIdentifierClause is WrappedSaml2AssertionKeyIdentifierClause);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a SecurityKeyIdentifierClause.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> reader positioned at a <see cref="SecurityKeyIdentifierClause"/> element.</param>
|
|
/// <returns>A <see cref="SecurityKeyIdentifierClause"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'reader' is null.</exception>
|
|
public override SecurityKeyIdentifierClause ReadKeyIdentifierClause(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!IsSaml2KeyIdentifierClause(reader))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4161));
|
|
}
|
|
|
|
// disallow empty
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, WSSE.Elements.SecurityTokenReference, WSSE.Namespace));
|
|
}
|
|
|
|
try
|
|
{
|
|
// @attributes
|
|
string value;
|
|
string id;
|
|
byte[] nonce = null;
|
|
int length = 0;
|
|
|
|
// @wsse11:TokenType is checked by IsSaml2KeyIdentifierClause
|
|
|
|
// @wsc:Nonce and @wsc:Length, first try WSCFeb2005
|
|
value = reader.GetAttribute(WSC.Attributes.Nonce, WSC.Namespace);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
nonce = Convert.FromBase64String(value);
|
|
|
|
value = reader.GetAttribute(WSC.Attributes.Length, WSC.Namespace);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
length = XmlConvert.ToInt32(value);
|
|
}
|
|
else
|
|
{
|
|
length = WSC.DefaultDerivedKeyLength;
|
|
}
|
|
}
|
|
|
|
// @wsc:Nonce and @wsc:Length, now try WSC13
|
|
if (null == nonce)
|
|
{
|
|
value = reader.GetAttribute(WSC13.Attributes.Nonce, WSC13.Namespace);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
nonce = Convert.FromBase64String(value);
|
|
|
|
value = reader.GetAttribute(WSC13.Attributes.Length, WSC13.Namespace);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
length = XmlConvert.ToInt32(value);
|
|
}
|
|
else
|
|
{
|
|
length = WSC13.DefaultDerivedKeyLength;
|
|
}
|
|
}
|
|
}
|
|
|
|
// <wsse:SecurityTokenReference> content begins
|
|
reader.Read();
|
|
|
|
// <wsse:Reference> - throw exception
|
|
if (reader.IsStartElement(WSSE.Elements.Reference, WSSE.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4126));
|
|
}
|
|
|
|
// <wsse:KeyIdentifier>
|
|
if (!reader.IsStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace))
|
|
{
|
|
reader.ReadStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace);
|
|
}
|
|
|
|
// @ValueType - required
|
|
value = reader.GetAttribute(WSSE.Attributes.ValueType);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, WSSE.Attributes.ValueType, WSSE.Elements.KeyIdentifier));
|
|
}
|
|
|
|
if (!StringComparer.Ordinal.Equals(TokenProfile11ValueType, value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4127, value));
|
|
}
|
|
|
|
// <wsse:KeyIdentifier> Content is string </wsse:KeyIdentifier>
|
|
id = reader.ReadElementString();
|
|
|
|
// </wsse:SecurityTokenReference>
|
|
reader.ReadEndElement();
|
|
|
|
return new Saml2AssertionKeyIdentifierClause(id, nonce, length);
|
|
}
|
|
catch (Exception inner)
|
|
{
|
|
// Wrap common data-validation exceptions that may have bubbled up
|
|
if (inner is FormatException
|
|
|| inner is ArgumentException
|
|
|| inner is InvalidOperationException
|
|
|| inner is OverflowException)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4125), inner);
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads a SAML 2.0 token from the XmlReader.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> reader positioned at a <see cref="Saml2SecurityToken"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2SecurityToken"/>.</returns>
|
|
/// <exception cref="InvalidOperationException">Is thrown if 'Configuration', 'Configruation.IssuerTokenResolver' or 'Configuration.ServiceTokenResolver 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));
|
|
}
|
|
|
|
if (Configuration.ServiceTokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276));
|
|
}
|
|
|
|
Saml2Assertion assertion = this.ReadAssertion(reader);
|
|
|
|
ReadOnlyCollection<SecurityKey> keys = this.ResolveSecurityKeys(assertion, Configuration.ServiceTokenResolver);
|
|
|
|
// Resolve signing token if one is present. It may be deferred and signed by reference.
|
|
SecurityToken issuerToken;
|
|
this.TryResolveIssuerToken(assertion, Configuration.IssuerTokenResolver, out issuerToken);
|
|
|
|
return new Saml2SecurityToken(assertion, keys, issuerToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes a Saml2AssertionKeyIdentifierClause to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityKeyIdentifierClause"/>.</param>
|
|
/// <param name="securityKeyIdentifierClause">The <see cref="SecurityKeyIdentifierClause"/> to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'writer' or 'securityKeyIdentifierClause' is null.</exception>
|
|
public override void WriteKeyIdentifierClause(XmlWriter writer, SecurityKeyIdentifierClause securityKeyIdentifierClause)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (securityKeyIdentifierClause == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("keyIdentifierClause");
|
|
}
|
|
|
|
Saml2AssertionKeyIdentifierClause samlClause = null;
|
|
WrappedSaml2AssertionKeyIdentifierClause wrappedClause = securityKeyIdentifierClause as WrappedSaml2AssertionKeyIdentifierClause;
|
|
|
|
if (wrappedClause != null)
|
|
{
|
|
samlClause = wrappedClause.WrappedClause;
|
|
}
|
|
else
|
|
{
|
|
samlClause = securityKeyIdentifierClause as Saml2AssertionKeyIdentifierClause;
|
|
}
|
|
|
|
if (null == samlClause)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("keyIdentifierClause", SR.GetString(SR.ID4162));
|
|
}
|
|
|
|
// <wsse:SecurityTokenReference>
|
|
writer.WriteStartElement(WSSE.Elements.SecurityTokenReference, WSSE.Namespace);
|
|
|
|
// @wsc:Nonce
|
|
byte[] nonce = samlClause.GetDerivationNonce();
|
|
if (null != nonce)
|
|
{
|
|
writer.WriteAttributeString(WSC.Attributes.Nonce, WSC.Namespace, Convert.ToBase64String(nonce));
|
|
|
|
int length = samlClause.DerivationLength;
|
|
|
|
// Don't emit @wsc:Length since it's not actually in the spec/schema
|
|
if (length != 0 && length != WSC.DefaultDerivedKeyLength)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4129)));
|
|
}
|
|
}
|
|
|
|
// @wsse11:TokenType
|
|
writer.WriteAttributeString(WSSE11.Attributes.TokenType, WSSE11.Namespace, SecurityTokenTypes.OasisWssSaml2TokenProfile11);
|
|
|
|
// <wsse:KeyIdentifier>
|
|
writer.WriteStartElement(WSSE.Elements.KeyIdentifier, WSSE.Namespace);
|
|
|
|
// @ValueType
|
|
writer.WriteAttributeString(WSSE.Attributes.ValueType, TokenProfile11ValueType);
|
|
|
|
// ID is the string content
|
|
writer.WriteString(samlClause.Id);
|
|
|
|
// </wsse:KeyIdentifier>
|
|
writer.WriteEndElement();
|
|
|
|
// </wsse:SecurityTokenReference>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
internal static XmlDictionaryReader CreatePlaintextReaderFromEncryptedData(
|
|
XmlDictionaryReader reader,
|
|
SecurityTokenResolver serviceTokenResolver,
|
|
SecurityTokenSerializer keyInfoSerializer,
|
|
Collection<EncryptedKeyIdentifierClause> clauses,
|
|
out EncryptingCredentials encryptingCredentials)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
#pragma warning suppress 56504 // bogus - thinks reader.LocalName, reader.NamespaceURI need validation
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
encryptingCredentials = null;
|
|
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.EncryptedElementType, Saml2Constants.Namespace);
|
|
|
|
reader.ReadStartElement();
|
|
EncryptedDataElement encryptedData = new EncryptedDataElement(keyInfoSerializer);
|
|
|
|
// <xenc:EncryptedData> 1
|
|
encryptedData.ReadXml(reader);
|
|
|
|
// <xenc:EncryptedKey> 0-oo
|
|
reader.MoveToContent();
|
|
while (reader.IsStartElement(XmlEncryptionConstants.Elements.EncryptedKey, XmlEncryptionConstants.Namespace))
|
|
{
|
|
SecurityKeyIdentifierClause skic;
|
|
if (keyInfoSerializer.CanReadKeyIdentifierClause(reader))
|
|
{
|
|
skic = keyInfoSerializer.ReadKeyIdentifierClause(reader);
|
|
}
|
|
else
|
|
{
|
|
EncryptedKeyElement encryptedKey = new EncryptedKeyElement(keyInfoSerializer);
|
|
encryptedKey.ReadXml(reader);
|
|
skic = encryptedKey.GetClause();
|
|
}
|
|
|
|
EncryptedKeyIdentifierClause encryptedKeyClause = skic as EncryptedKeyIdentifierClause;
|
|
if (null == encryptedKeyClause)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4172));
|
|
}
|
|
|
|
clauses.Add(encryptedKeyClause);
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
// Try to resolve the decryption key from both the embedded
|
|
// KeyInfo and any external clauses
|
|
SecurityKey decryptionKey = null;
|
|
SecurityKeyIdentifierClause matchingClause = null;
|
|
|
|
foreach (SecurityKeyIdentifierClause clause in encryptedData.KeyIdentifier)
|
|
{
|
|
if (serviceTokenResolver.TryResolveSecurityKey(clause, out decryptionKey))
|
|
{
|
|
matchingClause = clause;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (null == decryptionKey)
|
|
{
|
|
foreach (SecurityKeyIdentifierClause clause in clauses)
|
|
{
|
|
if (serviceTokenResolver.TryResolveSecurityKey(clause, out decryptionKey))
|
|
{
|
|
matchingClause = clause;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (null == decryptionKey)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new EncryptedTokenDecryptionFailedException());
|
|
}
|
|
|
|
// Need a symmetric key
|
|
SymmetricSecurityKey symmetricKey = decryptionKey as SymmetricSecurityKey;
|
|
if (null == symmetricKey)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenException(SR.GetString(SR.ID4023)));
|
|
}
|
|
|
|
// Do the actual decryption
|
|
SymmetricAlgorithm decryptor = symmetricKey.GetSymmetricAlgorithm(encryptedData.Algorithm);
|
|
byte[] plainText = encryptedData.Decrypt(decryptor);
|
|
|
|
// Save off the encrypting credentials for roundtrip
|
|
encryptingCredentials = new ReceivedEncryptingCredentials(decryptionKey, new SecurityKeyIdentifier(matchingClause), encryptedData.Algorithm);
|
|
|
|
return XmlDictionaryReader.CreateTextReader(plainText, reader.Quotas);
|
|
}
|
|
|
|
// Wraps common data validation exceptions with an XmlException
|
|
// associated with the failing reader
|
|
internal static Exception TryWrapReadException(XmlReader reader, Exception inner)
|
|
{
|
|
if (inner is FormatException
|
|
|| inner is ArgumentException
|
|
|| inner is InvalidOperationException
|
|
|| inner is OverflowException)
|
|
{
|
|
return DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4125), inner);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if the current XML element is pointing to a Saml2SecurityKeyIdentifierClause.
|
|
/// </summary>
|
|
/// <param name="reader">An <see cref="XmlReader"/> reader.</param>
|
|
/// <returns>'True' if reader contains a <see cref="Saml2SecurityKeyIdentifierClause"/>. 'False' otherwise.</returns>
|
|
internal static bool IsSaml2KeyIdentifierClause(XmlReader reader)
|
|
{
|
|
if (!reader.IsStartElement(WSSE.Elements.SecurityTokenReference, WSSE.Namespace))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
string tokenType = reader.GetAttribute(WSSE11.Attributes.TokenType, WSSE11.Namespace);
|
|
return tokenTypeIdentifiers.Contains(tokenType);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates if the current XML element is pointing to a Saml2Assertion.
|
|
/// </summary>
|
|
/// <param name="reader">A reader that may contain a <see cref="Saml2Assertion"/>.</param>
|
|
/// <returns>'True' if reader contains a <see cref="Saml2Assertion"/>. 'False' otherwise.</returns>
|
|
internal static bool IsSaml2Assertion(XmlReader reader)
|
|
{
|
|
return reader.IsStartElement(SAML2.Elements.Assertion, SAML2.Namespace)
|
|
|| reader.IsStartElement(SAML2.Elements.EncryptedAssertion, SAML2.Namespace);
|
|
}
|
|
|
|
// Read an element that must not contain content.
|
|
internal static void ReadEmptyContentElement(XmlReader reader)
|
|
{
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
reader.ReadEndElement();
|
|
}
|
|
}
|
|
|
|
internal static Saml2Id ReadSimpleNCNameElement(XmlReader reader)
|
|
{
|
|
Fx.Assert(reader.IsStartElement(), "reader is not on start element");
|
|
|
|
try
|
|
{
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
XmlUtil.ValidateXsiType(reader, "NCName", XmlSchema.Namespace);
|
|
|
|
reader.MoveToElement();
|
|
string value = reader.ReadElementContentAsString();
|
|
|
|
return new Saml2Id(value);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reads an element with simple content anyURI. Since this is SAML,
|
|
// restricts the URI to absolute.
|
|
internal static Uri ReadSimpleUriElement(XmlReader reader)
|
|
{
|
|
return ReadSimpleUriElement(reader, UriKind.Absolute);
|
|
}
|
|
|
|
// Reads an element with simple content anyURI where a UriKind can be specified
|
|
internal static Uri ReadSimpleUriElement(XmlReader reader, UriKind kind)
|
|
{
|
|
return ReadSimpleUriElement(reader, kind, false);
|
|
}
|
|
|
|
// allow lax reading of relative URIs in some instances for interop
|
|
internal static Uri ReadSimpleUriElement(XmlReader reader, UriKind kind, bool allowLaxReading)
|
|
{
|
|
Fx.Assert(reader.IsStartElement(), "reader is not on start element");
|
|
|
|
try
|
|
{
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
XmlUtil.ValidateXsiType(reader, "anyURI", XmlSchema.Namespace);
|
|
|
|
reader.MoveToElement();
|
|
string value = reader.ReadElementContentAsString();
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0022));
|
|
}
|
|
|
|
if (!allowLaxReading && !UriUtil.CanCreateValidUri(value, kind))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(kind == UriKind.RelativeOrAbsolute ? SR.ID0019 : SR.ID0013));
|
|
}
|
|
|
|
return new Uri(value, kind);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the conditions for the assertion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Generally, conditions should be included in assertions to limit the
|
|
/// impact of misuse of the assertion. Specifying the NotBefore and
|
|
/// NotOnOrAfter conditions can limit the period of vulnerability in
|
|
/// the case of a compromised assertion. The AudienceRestrictionCondition
|
|
/// can be used to explicitly state the intended relying party or parties
|
|
/// of the assertion, which coupled with appropriate audience restriction
|
|
/// enforcement at relying parties can help to mitigate spoofing attacks
|
|
/// between relying parties.
|
|
/// </para>
|
|
/// <para>
|
|
/// The default implementation creates NotBefore and NotOnOrAfter conditions
|
|
/// based on the tokenDescriptor.Lifetime. It will also generate an
|
|
/// AudienceRestrictionCondition limiting consumption of the assertion to
|
|
/// tokenDescriptor.Scope.Address.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="tokenLifetime">Lifetime of the Token.</param>
|
|
/// <param name="relyingPartyAddress">The endpoint address to who the token is created. The address
|
|
/// is modeled as an AudienceRestriction condition.</param>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>A Saml2Conditions object.</returns>
|
|
protected virtual Saml2Conditions CreateConditions(Lifetime tokenLifetime, string relyingPartyAddress, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
bool hasLifetime = null != tokenLifetime;
|
|
bool hasScope = !string.IsNullOrEmpty(relyingPartyAddress);
|
|
if (!hasLifetime && !hasScope)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Saml2Conditions conditions = new Saml2Conditions();
|
|
if (hasLifetime)
|
|
{
|
|
conditions.NotBefore = tokenLifetime.Created;
|
|
conditions.NotOnOrAfter = tokenLifetime.Expires;
|
|
}
|
|
|
|
if (hasScope)
|
|
{
|
|
conditions.AudienceRestrictions.Add(new Saml2AudienceRestriction(new Uri(relyingPartyAddress)));
|
|
}
|
|
|
|
return conditions;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the advice for the assertion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// By default, this method returns null.
|
|
/// </remarks>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>A Saml2Advice object, default is null.</returns>
|
|
protected virtual Saml2Advice CreateAdvice(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a name identifier that identifies the assertion issuer.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// SAML2 assertions must contain a name identifier for the issuer.
|
|
/// This method may not return null.
|
|
/// </para>
|
|
/// <para>
|
|
/// The default implementation creates a simple name identifier
|
|
/// from the tokenDescriptor.Issuer.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>A <see cref="Saml2NameIdentifier"/> from the tokenDescriptor</returns>
|
|
protected virtual Saml2NameIdentifier CreateIssuerNameIdentifier(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
string issuerName = tokenDescriptor.TokenIssuerName;
|
|
|
|
// Must have an issuer
|
|
if (string.IsNullOrEmpty(issuerName))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4138)));
|
|
}
|
|
|
|
return new Saml2NameIdentifier(issuerName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates a Saml2Attribute from a claim.
|
|
/// </summary>
|
|
/// <param name="claim">The <see cref="Claim"/> from which to generate a <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="tokenDescriptor">Contains all the information that is used in token issuance.</param>
|
|
/// <returns>A <see cref="Saml2Attribute"/> based on the claim.</returns>
|
|
/// <exception cref="ArgumentNullException">The parameter 'claim' is null.</exception>
|
|
protected virtual Saml2Attribute CreateAttribute(Claim claim, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (claim == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("claim");
|
|
}
|
|
|
|
Saml2Attribute attribute = new Saml2Attribute(claim.Type, claim.Value);
|
|
if (!StringComparer.Ordinal.Equals(ClaimsIdentity.DefaultIssuer, claim.OriginalIssuer))
|
|
{
|
|
attribute.OriginalIssuer = claim.OriginalIssuer;
|
|
}
|
|
|
|
attribute.AttributeValueXsiType = claim.ValueType;
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeNameFormat))
|
|
{
|
|
string nameFormat = claim.Properties[ClaimProperties.SamlAttributeNameFormat];
|
|
if (!UriUtil.CanCreateValidUri(nameFormat, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("nameFormat", SR.GetString(SR.ID0013));
|
|
}
|
|
|
|
attribute.NameFormat = new Uri(nameFormat);
|
|
}
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlAttributeDisplayName))
|
|
{
|
|
attribute.FriendlyName = claim.Properties[ClaimProperties.SamlAttributeDisplayName];
|
|
}
|
|
|
|
return attribute;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates <see cref="Saml2AttributeStatement"/> from a <see cref="SecurityTokenDescriptor"/> and a <see cref="ClaimsIdentity"/>
|
|
/// </summary>
|
|
/// <remarks>This method may return null if the token descriptor does not contain any subject or the subject does not have any claims.
|
|
/// </remarks>
|
|
/// <param name="subject">The <see cref="ClaimsIdentity"/> that contains claims which will be converted to SAML Attributes.</param>
|
|
/// <param name="tokenDescriptor">The <see cref="SecurityTokenDescriptor"/> that contains information on building the <see cref="Saml2AttributeStatement"/>.</param>
|
|
/// <returns>A Saml2AttributeStatement.</returns>
|
|
protected virtual Saml2AttributeStatement CreateAttributeStatement(ClaimsIdentity subject, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (subject == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
// We treat everything else as an Attribute except the nameId claim, which is already processed
|
|
// for saml2subject
|
|
// AuthenticationInstant and AuthenticationType are not converted to Claims
|
|
if (subject.Claims != null)
|
|
{
|
|
List<Saml2Attribute> attributes = new List<Saml2Attribute>();
|
|
foreach (Claim claim in subject.Claims)
|
|
{
|
|
if (claim != null && claim.Type != ClaimTypes.NameIdentifier)
|
|
{
|
|
switch (claim.Type)
|
|
{
|
|
case ClaimTypes.AuthenticationInstant:
|
|
case ClaimTypes.AuthenticationMethod:
|
|
break;
|
|
default:
|
|
attributes.Add(this.CreateAttribute(claim, tokenDescriptor));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
this.AddDelegateToAttributes(subject, attributes, tokenDescriptor);
|
|
|
|
ICollection<Saml2Attribute> collectedAttributes = this.CollectAttributeValues(attributes);
|
|
if (collectedAttributes.Count > 0)
|
|
{
|
|
return new Saml2AttributeStatement(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>A <see cref="ICollection{T}"/> of <see cref="Saml2Attribute"/> with common attributes collected into value lists.</returns>
|
|
protected virtual ICollection<Saml2Attribute> CollectAttributeValues(ICollection<Saml2Attribute> attributes)
|
|
{
|
|
Dictionary<SamlAttributeKeyComparer.AttributeKey, Saml2Attribute> distinctAttributes = new Dictionary<SamlAttributeKeyComparer.AttributeKey, Saml2Attribute>(attributes.Count, new SamlAttributeKeyComparer());
|
|
|
|
// Use unique attribute if name, value type, or issuer differ
|
|
foreach (Saml2Attribute saml2Attribute in attributes)
|
|
{
|
|
if (saml2Attribute != null)
|
|
{
|
|
SamlAttributeKeyComparer.AttributeKey attributeKey = new SamlAttributeKeyComparer.AttributeKey(saml2Attribute);
|
|
|
|
if (distinctAttributes.ContainsKey(attributeKey))
|
|
{
|
|
foreach (string attributeValue in saml2Attribute.Values)
|
|
{
|
|
distinctAttributes[attributeKey].Values.Add(attributeValue);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
distinctAttributes.Add(attributeKey, saml2Attribute);
|
|
}
|
|
}
|
|
}
|
|
|
|
return distinctAttributes.Values;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds all the delegates associated with the subject into the attribute collection.
|
|
/// </summary>
|
|
/// <param name="subject">The delegate of this <see cref="ClaimsIdentity"/> will be serialized into a <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="attributes">A <see cref="ICollection{T}"/> of <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="tokenDescriptor">The <see cref="SecurityTokenDescriptor"/> that contains information on building the delegate.</param>
|
|
protected virtual void AddDelegateToAttributes(ClaimsIdentity subject, ICollection<Saml2Attribute> 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<Saml2Attribute> actingAsAttributes = new List<Saml2Attribute>();
|
|
|
|
foreach (Claim claim in subject.Actor.Claims)
|
|
{
|
|
if (claim != null)
|
|
{
|
|
actingAsAttributes.Add(this.CreateAttribute(claim, tokenDescriptor));
|
|
}
|
|
}
|
|
|
|
this.AddDelegateToAttributes(subject.Actor, actingAsAttributes, tokenDescriptor);
|
|
|
|
ICollection<Saml2Attribute> collectedAttributes = this.CollectAttributeValues(actingAsAttributes);
|
|
attributes.Add(this.CreateAttribute(new Claim(ClaimTypes.Actor, this.CreateXmlStringFromAttributes(collectedAttributes), ClaimValueTypes.String), tokenDescriptor));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Builds an XML formatted string from a collection of SAML attributes that represent the Actor.
|
|
/// </summary>
|
|
/// <param name="attributes">An enumeration of Saml2Attributes.</param>
|
|
/// <returns>A well-formed XML string.</returns>
|
|
/// <remarks>The string is of the form ""<Actor><Attribute name, ns><AttributeValue>...</AttributeValue>, ...</Attribute>...</Actor>"</remarks>
|
|
protected virtual string CreateXmlStringFromAttributes(IEnumerable<Saml2Attribute> attributes)
|
|
{
|
|
bool actorElementWritten = false;
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
using (XmlDictionaryWriter dicWriter = XmlDictionaryWriter.CreateTextWriter(ms, Encoding.UTF8, false))
|
|
{
|
|
foreach (Saml2Attribute samlAttribute in attributes)
|
|
{
|
|
if (samlAttribute != null)
|
|
{
|
|
if (!actorElementWritten)
|
|
{
|
|
dicWriter.WriteStartElement(Actor);
|
|
actorElementWritten = true;
|
|
}
|
|
|
|
this.WriteAttribute(dicWriter, samlAttribute);
|
|
}
|
|
}
|
|
|
|
if (actorElementWritten)
|
|
{
|
|
dicWriter.WriteEndElement();
|
|
}
|
|
|
|
dicWriter.Flush();
|
|
}
|
|
|
|
return Encoding.UTF8.GetString(ms.ToArray());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates an <see cref="IEnumerable{T}"/> of <see cref="Saml2Statement"/> to be included in the assertion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// Statements are not required in a SAML2 assertion. This method may
|
|
/// return an empty collection.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="tokenDescriptor">The <see cref="SecurityTokenDescriptor"/> that contains information on creating the <see cref="Saml2Statement"/>.</param>
|
|
/// <returns>An enumeration of Saml2Statements.</returns>
|
|
protected virtual IEnumerable<Saml2Statement> CreateStatements(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (tokenDescriptor == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
Collection<Saml2Statement> statements = new Collection<Saml2Statement>();
|
|
|
|
Saml2AttributeStatement attributeStatement = this.CreateAttributeStatement(tokenDescriptor.Subject, tokenDescriptor);
|
|
if (attributeStatement != null)
|
|
{
|
|
statements.Add(attributeStatement);
|
|
}
|
|
|
|
Saml2AuthenticationStatement authenticationStatement = this.CreateAuthenticationStatement(tokenDescriptor.AuthenticationInfo, tokenDescriptor);
|
|
if (authenticationStatement != null)
|
|
{
|
|
statements.Add(authenticationStatement);
|
|
}
|
|
|
|
return statements;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Given an AuthenticationInformation object, this routine creates a Saml2AuthenticationStatement
|
|
/// to be added to the Saml2Assertion that is produced by the factory.
|
|
/// </summary>
|
|
/// <param name="authInfo">
|
|
/// An AuthenticationInformation object containing the state to be wrapped as a Saml2AuthenticationStatement
|
|
/// object.
|
|
/// </param>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>
|
|
/// The Saml2AuthenticationStatement to add to the assertion being created or null to ignore the AuthenticationInformation
|
|
/// being wrapped as a statement.
|
|
/// </returns>
|
|
protected virtual Saml2AuthenticationStatement CreateAuthenticationStatement(AuthenticationInformation authInfo, SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
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", "SAML2"));
|
|
}
|
|
else if (authenticationInstant == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4270, "AuthenticationInstant", "SAML2"));
|
|
}
|
|
|
|
Uri saml2AuthenticationClass;
|
|
if (!UriUtil.TryCreateValidUri(this.DenormalizeAuthenticationType(authenticationMethod), UriKind.Absolute, out saml2AuthenticationClass))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4185, authenticationMethod));
|
|
}
|
|
|
|
Saml2AuthenticationContext authCtx = new Saml2AuthenticationContext(saml2AuthenticationClass);
|
|
DateTime authInstantTime = DateTime.ParseExact(authenticationInstant, DateTimeFormats.Accepted, DateTimeFormatInfo.InvariantInfo, DateTimeStyles.None).ToUniversalTime();
|
|
Saml2AuthenticationStatement authnStatement = new Saml2AuthenticationStatement(authCtx, authInstantTime);
|
|
|
|
if (authInfo != null)
|
|
{
|
|
if (!string.IsNullOrEmpty(authInfo.DnsName)
|
|
|| !string.IsNullOrEmpty(authInfo.Address))
|
|
{
|
|
authnStatement.SubjectLocality
|
|
= new Saml2SubjectLocality(authInfo.Address, authInfo.DnsName);
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(authInfo.Session))
|
|
{
|
|
authnStatement.SessionIndex = authInfo.Session;
|
|
}
|
|
|
|
authnStatement.SessionNotOnOrAfter = authInfo.NotOnOrAfter;
|
|
}
|
|
|
|
return authnStatement;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a SAML2 subject of the assertion.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The security token descriptor to create the subject.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown when 'tokenDescriptor' is null.</exception>
|
|
/// <returns>A Saml2Subject.</returns>
|
|
protected virtual Saml2Subject CreateSamlSubject(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
Saml2Subject saml2Subject = new Saml2Subject();
|
|
|
|
// Look for name identifier claims
|
|
string nameIdentifierClaim = null;
|
|
string nameIdentifierFormat = null;
|
|
string nameIdentifierNameQualifier = null;
|
|
string nameIdentifierSpProviderId = null;
|
|
string nameIdentifierSpNameQualifier = 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 != nameIdentifierClaim)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4139)));
|
|
}
|
|
|
|
nameIdentifierClaim = claim.Value;
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierFormat))
|
|
{
|
|
nameIdentifierFormat = claim.Properties[ClaimProperties.SamlNameIdentifierFormat];
|
|
}
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierNameQualifier))
|
|
{
|
|
nameIdentifierNameQualifier = claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier];
|
|
}
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierSPNameQualifier))
|
|
{
|
|
nameIdentifierSpNameQualifier = claim.Properties[ClaimProperties.SamlNameIdentifierSPNameQualifier];
|
|
}
|
|
|
|
if (claim.Properties.ContainsKey(ClaimProperties.SamlNameIdentifierSPProvidedId))
|
|
{
|
|
nameIdentifierSpProviderId = claim.Properties[ClaimProperties.SamlNameIdentifierSPProvidedId];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nameIdentifierClaim != null)
|
|
{
|
|
Saml2NameIdentifier nameIdentifier = new Saml2NameIdentifier(nameIdentifierClaim);
|
|
|
|
if (nameIdentifierFormat != null && UriUtil.CanCreateValidUri(nameIdentifierFormat, UriKind.Absolute))
|
|
{
|
|
nameIdentifier.Format = new Uri(nameIdentifierFormat);
|
|
}
|
|
|
|
nameIdentifier.NameQualifier = nameIdentifierNameQualifier;
|
|
nameIdentifier.SPNameQualifier = nameIdentifierSpNameQualifier;
|
|
nameIdentifier.SPProvidedId = nameIdentifierSpProviderId;
|
|
|
|
saml2Subject.NameId = nameIdentifier;
|
|
}
|
|
|
|
// Add subject confirmation data
|
|
Saml2SubjectConfirmation subjectConfirmation;
|
|
if (null == tokenDescriptor.Proof)
|
|
{
|
|
subjectConfirmation = new Saml2SubjectConfirmation(Saml2Constants.ConfirmationMethods.Bearer);
|
|
}
|
|
else
|
|
{
|
|
subjectConfirmation = new Saml2SubjectConfirmation(Saml2Constants.ConfirmationMethods.HolderOfKey, new Saml2SubjectConfirmationData());
|
|
subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Add(tokenDescriptor.Proof.KeyIdentifier);
|
|
}
|
|
|
|
saml2Subject.SubjectConfirmations.Add(subjectConfirmation);
|
|
|
|
return saml2Subject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Override this method to change the token encrypting credentials.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">Retrieve some scope encrypting credentials from the Scope object</param>
|
|
/// <returns>the token encrypting credentials</returns>
|
|
/// <exception cref="ArgumentNullException">When the given tokenDescriptor is null</exception>
|
|
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.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// SAML2 assertions used as security tokens should be signed.
|
|
/// </para>
|
|
/// <para>
|
|
/// The default implementation uses the
|
|
/// tokenDescriptor.Scope.SigningCredentials.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>The signing credential.</returns>
|
|
protected virtual SigningCredentials GetSigningCredentials(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
return tokenDescriptor.SigningCredentials;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rejects tokens that are not valid.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The token may not be valid for a number of reasons. For example, the
|
|
/// current time may not be within the token's validity period, the
|
|
/// token may contain data that is contradictory or not valid, or the token
|
|
/// may contain unsupported SAML2 elements.
|
|
/// </remarks>
|
|
/// <param name="conditions">SAML 2.0 condition to be validated.</param>
|
|
/// <param name="enforceAudienceRestriction">True to check for Audience Restriction condition.</param>
|
|
protected virtual void ValidateConditions(Saml2Conditions conditions, bool enforceAudienceRestriction)
|
|
{
|
|
if (conditions != null)
|
|
{
|
|
DateTime now = DateTime.UtcNow;
|
|
|
|
if (conditions.NotBefore != null
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew) < conditions.NotBefore.Value)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenNotYetValidException(SR.GetString(SR.ID4147, conditions.NotBefore.Value, now)));
|
|
}
|
|
|
|
if (conditions.NotOnOrAfter != null
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= conditions.NotOnOrAfter.Value)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenExpiredException(SR.GetString(SR.ID4148, conditions.NotOnOrAfter.Value, now)));
|
|
}
|
|
|
|
if (conditions.OneTimeUse)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4149)));
|
|
}
|
|
|
|
if (conditions.ProxyRestriction != null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID4150)));
|
|
}
|
|
}
|
|
|
|
if (enforceAudienceRestriction)
|
|
{
|
|
if (this.Configuration == null || this.Configuration.AudienceRestriction.AllowedAudienceUris.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID1032)));
|
|
}
|
|
|
|
if (conditions == null || conditions.AudienceRestrictions.Count == 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new AudienceUriValidationFailedException(SR.GetString(SR.ID1035)));
|
|
}
|
|
else
|
|
{
|
|
foreach (Saml2AudienceRestriction audienceRestriction in conditions.AudienceRestrictions)
|
|
{
|
|
SamlSecurityTokenRequirement.ValidateAudienceRestriction(this.Configuration.AudienceRestriction.AllowedAudienceUris, audienceRestriction.Audiences);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
protected virtual string FindUpn(ClaimsIdentity claimsIdentity)
|
|
{
|
|
return ClaimsHelper.FindUpn(claimsIdentity);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the Saml2 AuthenticationContext matching a normalized value.
|
|
/// </summary>
|
|
/// <param name="normalizedAuthenticationType">Normalized value.</param>
|
|
/// <returns>A string that represents the denormalized authentication type used to obtain the token.</returns>
|
|
protected virtual string DenormalizeAuthenticationType(string normalizedAuthenticationType)
|
|
{
|
|
return AuthenticationTypeMaps.Denormalize(normalizedAuthenticationType, AuthenticationTypeMaps.Saml2);
|
|
}
|
|
|
|
/// <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>
|
|
/// <param name="token">The token to detect for replay.</param>
|
|
/// <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' can not be cast as a 'Saml2SecurityToken'.</exception>
|
|
/// <exception cref="SecurityTokenValidationException">The Saml2SecurityToken.Assertion.Id.Value is null or empty.</exception>
|
|
/// <exception cref="SecurityTokenReplayDetectedException">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");
|
|
}
|
|
|
|
Saml2SecurityToken samlToken = token as Saml2SecurityToken;
|
|
if (null == samlToken)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("token", SR.GetString(SR.ID1064, 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.Id.Value))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.ID1065)));
|
|
}
|
|
|
|
StringBuilder stringBuilder = new StringBuilder();
|
|
string key;
|
|
|
|
using (HashAlgorithm hashAlgorithm = CryptoHelper.NewSha256HashAlgorithm())
|
|
{
|
|
if (string.IsNullOrEmpty(samlToken.Assertion.Issuer.Value))
|
|
{
|
|
stringBuilder.AppendFormat("{0}{1}", samlToken.Assertion.Id.Value, tokenTypeIdentifiers[0]);
|
|
}
|
|
else
|
|
{
|
|
stringBuilder.AppendFormat("{0}{1}{2}", samlToken.Assertion.Id.Value, samlToken.Assertion.Issuer.Value, tokenTypeIdentifiers[0]);
|
|
}
|
|
|
|
key = Convert.ToBase64String(hashAlgorithm.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString())));
|
|
}
|
|
|
|
if (Configuration.Caches.TokenReplayCache.Contains(key))
|
|
{
|
|
string issuer = (samlToken.Assertion.Issuer.Value != null) ? samlToken.Assertion.Issuer.Value : String.Empty;
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenReplayDetectedException(SR.GetString(SR.ID1066, typeof(Saml2SecurityToken).ToString(), samlToken.Assertion.Id.Value, issuer)));
|
|
}
|
|
else
|
|
{
|
|
Configuration.Caches.TokenReplayCache.AddOrUpdate(key, token, DateTimeUtil.Add(this.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 Saml2SecurityToken's validity period is greater than the expiration period set to TokenReplayCache.</exception>
|
|
/// <returns>A DateTime representing the expiration time.</returns>
|
|
/// <remarks>By default, this function returns the NotOnOrAfter of the SAML Condition if present.
|
|
/// If that value does not exist, it returns the NotOnOrAfter of the first SubjectConfirmationData.
|
|
/// This function will never return a value further from now than Configuration.TokenReplayCacheExpirationPeriod.</remarks>
|
|
protected virtual DateTime GetTokenReplayCacheEntryExpirationTime(Saml2SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
DateTime? tokenExpiration = null;
|
|
Saml2Assertion assertion = token.Assertion;
|
|
if (assertion != null)
|
|
{
|
|
if (assertion.Conditions != null && assertion.Conditions.NotOnOrAfter.HasValue)
|
|
{
|
|
// The Condition has a NotOnOrAfter set, use that.
|
|
tokenExpiration = assertion.Conditions.NotOnOrAfter.Value;
|
|
}
|
|
else if (assertion.Subject != null && assertion.Subject.SubjectConfirmations != null &&
|
|
assertion.Subject.SubjectConfirmations.Count != 0 &&
|
|
assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData != null &&
|
|
assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter.HasValue)
|
|
{
|
|
// The condition did not have NotOnOrAfter set, but SCD[0] has a NotOnOrAfter set, use that.
|
|
tokenExpiration = assertion.Subject.SubjectConfirmations[0].SubjectConfirmationData.NotOnOrAfter.Value;
|
|
}
|
|
}
|
|
|
|
// DateTimeUtil handles overflows
|
|
DateTime maximumExpirationTime = DateTimeUtil.Add(DateTime.UtcNow, Configuration.TokenReplayCacheExpirationPeriod);
|
|
|
|
// Use DateTime.MaxValue as expiration value for tokens without expiration
|
|
tokenExpiration = tokenExpiration ?? DateTime.MaxValue;
|
|
|
|
// If the refined token validity period is greater than the TokenReplayCacheExpirationPeriod, throw
|
|
if (DateTime.Compare(maximumExpirationTime, tokenExpiration.Value) < 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new SecurityTokenValidationException(SR.GetString(SR.ID1069, tokenExpiration.Value.ToString(), Configuration.TokenReplayCacheExpirationPeriod.ToString())));
|
|
}
|
|
|
|
return tokenExpiration.Value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the normalized value matching a SAML2 AuthenticationContext class reference.
|
|
/// </summary>
|
|
/// <param name="saml2AuthenticationContextClassReference">A string representing the <see cref="Saml2Constants.AuthenticationContextClasses"/></param>
|
|
/// <returns>Normalized value.</returns>
|
|
protected virtual string NormalizeAuthenticationContextClassReference(string saml2AuthenticationContextClassReference)
|
|
{
|
|
return AuthenticationTypeMaps.Normalize(saml2AuthenticationContextClassReference, AuthenticationTypeMaps.Saml2);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates claims from the Saml2Subject.
|
|
/// </summary>
|
|
/// <param name="assertionSubject">The Saml2Subject.</param>
|
|
/// <param name="subject">The ClaimsIdentity subject.</param>
|
|
/// <param name="issuer">The issuer.</param>
|
|
protected virtual void ProcessSamlSubject(Saml2Subject assertionSubject, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (assertionSubject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertionSubject");
|
|
}
|
|
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
Saml2NameIdentifier nameId = assertionSubject.NameId;
|
|
|
|
if (nameId != null)
|
|
{
|
|
Claim claim = new Claim(ClaimTypes.NameIdentifier, nameId.Value, ClaimValueTypes.String, issuer);
|
|
|
|
if (nameId.Format != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierFormat] = nameId.Format.AbsoluteUri;
|
|
}
|
|
|
|
if (nameId.NameQualifier != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierNameQualifier] = nameId.NameQualifier;
|
|
}
|
|
|
|
if (nameId.SPNameQualifier != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierSPNameQualifier] = nameId.SPNameQualifier;
|
|
}
|
|
|
|
if (nameId.SPProvidedId != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlNameIdentifierSPProvidedId] = nameId.SPProvidedId;
|
|
}
|
|
|
|
subject.AddClaim(claim);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates claims from a Saml2AttributeStatement.
|
|
/// </summary>
|
|
/// <param name="statement">The Saml2AttributeStatement.</param>
|
|
/// <param name="subject">The subject.</param>
|
|
/// <param name="issuer">The issuer.</param>
|
|
protected virtual void ProcessAttributeStatement(Saml2AttributeStatement statement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (statement == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("statement");
|
|
}
|
|
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
foreach (Saml2Attribute attribute in statement.Attributes)
|
|
{
|
|
if (StringComparer.Ordinal.Equals(attribute.Name, ClaimTypes.Actor))
|
|
{
|
|
if (subject.Actor != null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4218));
|
|
}
|
|
|
|
this.SetDelegateFromAttribute(attribute, subject, issuer);
|
|
}
|
|
else
|
|
{
|
|
foreach (string value in attribute.Values)
|
|
{
|
|
if (value != null)
|
|
{
|
|
string originalIssuer = issuer;
|
|
if (attribute.OriginalIssuer != null)
|
|
{
|
|
originalIssuer = attribute.OriginalIssuer;
|
|
}
|
|
|
|
Claim claim = new Claim(attribute.Name, value, attribute.AttributeValueXsiType, issuer, originalIssuer);
|
|
|
|
if (attribute.NameFormat != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlAttributeNameFormat] = attribute.NameFormat.AbsoluteUri;
|
|
}
|
|
|
|
if (attribute.FriendlyName != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlAttributeDisplayName] = attribute.FriendlyName;
|
|
}
|
|
|
|
subject.AddClaim(claim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method gets called when a special type of Saml2Attribute is detected. The Saml2Attribute passed in
|
|
/// wraps a Saml2Attribute that contains a collection of AttributeValues, each of which will get mapped to a
|
|
/// claim. All of the claims will be returned in an ClaimsIdentity with the specified issuer.
|
|
/// </summary>
|
|
/// <param name="attribute">The <see cref="Saml2Attribute"/> to use.</param>
|
|
/// <param name="subject">The <see cref="ClaimsIdentity"/> that is the subject of this token.</param>
|
|
/// <param name="issuer">The issuer of the claim.</param>
|
|
/// <exception cref="InvalidOperationException">Will be thrown if the Saml2Attribute does not contain any
|
|
/// valid Saml2AttributeValues.
|
|
/// </exception>
|
|
protected virtual void SetDelegateFromAttribute(Saml2Attribute attribute, ClaimsIdentity subject, string issuer)
|
|
{
|
|
// bail here; nothing to add.
|
|
if (subject == null || attribute == null || attribute.Values == null || attribute.Values.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Saml2Attribute actingAsAttribute = null;
|
|
Collection<Claim> claims = new Collection<Claim>();
|
|
|
|
foreach (string attributeValue in attribute.Values)
|
|
{
|
|
if (attributeValue != null)
|
|
{
|
|
using (XmlDictionaryReader dicReader = XmlDictionaryReader.CreateTextReader(Encoding.UTF8.GetBytes(attributeValue), XmlDictionaryReaderQuotas.Max))
|
|
{
|
|
dicReader.MoveToContent();
|
|
dicReader.ReadStartElement(Actor);
|
|
|
|
while (dicReader.IsStartElement(Attribute))
|
|
{
|
|
Saml2Attribute innerAttribute = this.ReadAttribute(dicReader);
|
|
if (innerAttribute != null)
|
|
{
|
|
if (innerAttribute.Name == 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.ID4218));
|
|
}
|
|
|
|
actingAsAttribute = innerAttribute;
|
|
}
|
|
else
|
|
{
|
|
string originalIssuer = innerAttribute.OriginalIssuer;
|
|
for (int k = 0; k < innerAttribute.Values.Count; ++k)
|
|
{
|
|
Claim claim = null;
|
|
if (string.IsNullOrEmpty(originalIssuer))
|
|
{
|
|
claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer);
|
|
}
|
|
else
|
|
{
|
|
claim = new Claim(innerAttribute.Name, innerAttribute.Values[k], innerAttribute.AttributeValueXsiType, issuer, originalIssuer);
|
|
}
|
|
|
|
if (innerAttribute.NameFormat != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlAttributeNameFormat] = innerAttribute.NameFormat.AbsoluteUri;
|
|
}
|
|
|
|
if (innerAttribute.FriendlyName != null)
|
|
{
|
|
claim.Properties[ClaimProperties.SamlAttributeDisplayName] = innerAttribute.FriendlyName;
|
|
}
|
|
|
|
claims.Add(claim);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
dicReader.ReadEndElement(); // Actor
|
|
}
|
|
}
|
|
}
|
|
|
|
subject.Actor = new ClaimsIdentity(claims, AuthenticationTypes.Federation);
|
|
|
|
this.SetDelegateFromAttribute(actingAsAttribute, subject.Actor, issuer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates claims from a Saml2AuthenticationStatement.
|
|
/// </summary>
|
|
/// <param name="statement">The Saml2AuthenticationStatement.</param>
|
|
/// <param name="subject">The subject.</param>
|
|
/// <param name="issuer">The issuer.</param>
|
|
protected virtual void ProcessAuthenticationStatement(Saml2AuthenticationStatement statement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
if (subject == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("subject");
|
|
}
|
|
|
|
if (statement.AuthenticationContext.DeclarationReference != null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4180));
|
|
}
|
|
|
|
if (statement.AuthenticationContext.ClassReference != null)
|
|
{
|
|
subject.AddClaim(
|
|
new Claim(
|
|
ClaimTypes.AuthenticationMethod,
|
|
this.NormalizeAuthenticationContextClassReference(statement.AuthenticationContext.ClassReference.AbsoluteUri),
|
|
ClaimValueTypes.String,
|
|
issuer));
|
|
}
|
|
|
|
subject.AddClaim(new Claim(ClaimTypes.AuthenticationInstant, XmlConvert.ToString(statement.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated), ClaimValueTypes.DateTime, issuer));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates claims from a Saml2AuthorizationDecisionStatement.
|
|
/// </summary>
|
|
/// <param name="statement">The Saml2AuthorizationDecisionStatement.</param>
|
|
/// <param name="subject">The subject.</param>
|
|
/// <param name="issuer">The issuer.</param>
|
|
protected virtual void ProcessAuthorizationDecisionStatement(Saml2AuthorizationDecisionStatement statement, ClaimsIdentity subject, string issuer)
|
|
{
|
|
}
|
|
|
|
/// <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(Collection<Saml2Statement> statements, ClaimsIdentity subject, string issuer)
|
|
{
|
|
Collection<Saml2AuthenticationStatement> authnStatements = new Collection<Saml2AuthenticationStatement>();
|
|
|
|
foreach (Saml2Statement statement in statements)
|
|
{
|
|
Saml2AttributeStatement attrStatement = statement as Saml2AttributeStatement;
|
|
if (attrStatement != null)
|
|
{
|
|
this.ProcessAttributeStatement(attrStatement, subject, issuer);
|
|
continue;
|
|
}
|
|
|
|
Saml2AuthenticationStatement authnStatement = statement as Saml2AuthenticationStatement;
|
|
if (authnStatement != null)
|
|
{
|
|
authnStatements.Add(authnStatement);
|
|
continue;
|
|
}
|
|
|
|
Saml2AuthorizationDecisionStatement authzStatement = statement as Saml2AuthorizationDecisionStatement;
|
|
if (authzStatement != null)
|
|
{
|
|
this.ProcessAuthorizationDecisionStatement(authzStatement, subject, issuer);
|
|
continue;
|
|
}
|
|
|
|
// We don't process custom statements. Just fall through.
|
|
}
|
|
|
|
foreach (Saml2AuthenticationStatement authStatement in authnStatements)
|
|
{
|
|
if (authStatement != null)
|
|
{
|
|
this.ProcessAuthenticationStatement(authStatement, subject, issuer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates claims from a Saml2 token.
|
|
/// </summary>
|
|
/// <param name="samlToken">The Saml2SecurityToken.</param>
|
|
/// <returns>An IClaimIdentity.</returns>
|
|
protected virtual ClaimsIdentity CreateClaims(Saml2SecurityToken samlToken)
|
|
{
|
|
if (samlToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("samlToken");
|
|
}
|
|
|
|
ClaimsIdentity subject = new ClaimsIdentity(AuthenticationTypes.Federation, SamlSecurityTokenRequirement.NameClaimType, SamlSecurityTokenRequirement.RoleClaimType);
|
|
|
|
Saml2Assertion assertion = samlToken.Assertion;
|
|
|
|
if (assertion == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("samlToken", SR.GetString(SR.ID1034));
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4274));
|
|
}
|
|
|
|
if (this.Configuration.IssuerNameRegistry == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4277));
|
|
}
|
|
|
|
string issuer = this.Configuration.IssuerNameRegistry.GetIssuerName(samlToken.IssuerToken, assertion.Issuer.Value);
|
|
|
|
if (string.IsNullOrEmpty(issuer))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4175)));
|
|
}
|
|
|
|
this.ProcessSamlSubject(assertion.Subject, subject, issuer);
|
|
this.ProcessStatement(assertion.Statements, subject, issuer);
|
|
|
|
return subject;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates the Saml2SubjectConfirmation data.
|
|
/// </summary>
|
|
/// <param name="confirmationData">The Saml2 subject confirmation data.</param>
|
|
protected virtual void ValidateConfirmationData(Saml2SubjectConfirmationData confirmationData)
|
|
{
|
|
if (null == confirmationData)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("confirmationData");
|
|
}
|
|
|
|
if (null != confirmationData.Address)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4153)));
|
|
}
|
|
|
|
if (null != confirmationData.InResponseTo)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4154)));
|
|
}
|
|
|
|
if (null != confirmationData.Recipient)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4157)));
|
|
}
|
|
|
|
DateTime now = DateTime.UtcNow;
|
|
|
|
if (null != confirmationData.NotBefore
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew) < confirmationData.NotBefore.Value)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4176, confirmationData.NotBefore.Value, now)));
|
|
}
|
|
|
|
if (null != confirmationData.NotOnOrAfter
|
|
&& DateTimeUtil.Add(now, Configuration.MaxClockSkew.Negate()) >= confirmationData.NotOnOrAfter.Value)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4177, confirmationData.NotOnOrAfter.Value, now)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resolves the collection of <see cref="SecurityKey"/> referenced in a <see cref="Saml2Assertion"/>.
|
|
/// </summary>
|
|
/// <param name="assertion"><see cref="Saml2Assertion"/> to process.</param>
|
|
/// <param name="resolver"><see cref="SecurityTokenResolver"/> to use in resolving the <see cref="SecurityKey"/>.</param>
|
|
/// <returns>A read only collection of <see cref="SecurityKey"/> contained in the assertion.</returns>
|
|
protected virtual ReadOnlyCollection<SecurityKey> ResolveSecurityKeys(Saml2Assertion assertion, SecurityTokenResolver resolver)
|
|
{
|
|
if (null == assertion)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
// Must have Subject
|
|
Saml2Subject subject = assertion.Subject;
|
|
if (null == subject)
|
|
{
|
|
// No Subject
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4130)));
|
|
}
|
|
|
|
// Must have one SubjectConfirmation
|
|
if (0 == subject.SubjectConfirmations.Count)
|
|
{
|
|
// No SubjectConfirmation
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4131)));
|
|
}
|
|
|
|
if (subject.SubjectConfirmations.Count > 1)
|
|
{
|
|
// More than one SubjectConfirmation
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4132)));
|
|
}
|
|
|
|
// Extract the keys for the given method
|
|
ReadOnlyCollection<SecurityKey> securityKeys;
|
|
|
|
Saml2SubjectConfirmation subjectConfirmation = subject.SubjectConfirmations[0];
|
|
|
|
// For bearer, ensure there are no keys, set the collection to empty
|
|
// For HolderOfKey, ensure there is at least one key, resolve and create collection
|
|
if (Saml2Constants.ConfirmationMethods.Bearer == subjectConfirmation.Method)
|
|
{
|
|
if (null != subjectConfirmation.SubjectConfirmationData
|
|
&& 0 != subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Count)
|
|
{
|
|
// Bearer but has keys
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4133)));
|
|
}
|
|
|
|
securityKeys = EmptyReadOnlyCollection<SecurityKey>.Instance;
|
|
}
|
|
else if (Saml2Constants.ConfirmationMethods.HolderOfKey == subjectConfirmation.Method)
|
|
{
|
|
if (null == subjectConfirmation.SubjectConfirmationData
|
|
|| 0 == subjectConfirmation.SubjectConfirmationData.KeyIdentifiers.Count)
|
|
{
|
|
// Holder-of-key but no keys
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4134)));
|
|
}
|
|
|
|
List<SecurityKey> holderKeys = new List<SecurityKey>();
|
|
SecurityKey key;
|
|
|
|
foreach (SecurityKeyIdentifier keyIdentifier in subjectConfirmation.SubjectConfirmationData.KeyIdentifiers)
|
|
{
|
|
key = null;
|
|
|
|
// Try the resolver first
|
|
foreach (SecurityKeyIdentifierClause clause in keyIdentifier)
|
|
{
|
|
if (null != resolver
|
|
&& resolver.TryResolveSecurityKey(clause, out key))
|
|
{
|
|
holderKeys.Add(key);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If that doesn't work, try to create the key (e.g. bare RSA or X509 raw)
|
|
if (null == key)
|
|
{
|
|
if (keyIdentifier.CanCreateKey)
|
|
{
|
|
key = keyIdentifier.CreateKey();
|
|
holderKeys.Add(key);
|
|
}
|
|
else
|
|
{
|
|
holderKeys.Add(new SecurityKeyElement(keyIdentifier, resolver));
|
|
}
|
|
}
|
|
}
|
|
|
|
securityKeys = holderKeys.AsReadOnly();
|
|
}
|
|
else
|
|
{
|
|
// SenderVouches, as well as other random things, aren't accepted
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4136, subjectConfirmation.Method)));
|
|
}
|
|
|
|
return securityKeys;
|
|
}
|
|
|
|
/// <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="SecurityTokenException">Unable to resolve token.</exception>
|
|
protected virtual SecurityToken ResolveIssuerToken(Saml2Assertion assertion, SecurityTokenResolver issuerResolver)
|
|
{
|
|
if (null == assertion)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
SecurityToken token;
|
|
if (this.TryResolveIssuerToken(assertion, issuerResolver, out token))
|
|
{
|
|
return token;
|
|
}
|
|
else
|
|
{
|
|
string exceptionMessage = SR.GetString(assertion.SigningCredentials == null ? SR.ID4141 : SR.ID4142);
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(exceptionMessage));
|
|
}
|
|
}
|
|
|
|
/// <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>
|
|
protected virtual bool TryResolveIssuerToken(Saml2Assertion assertion, SecurityTokenResolver issuerResolver, out SecurityToken token)
|
|
{
|
|
if (null == assertion)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("assertion");
|
|
}
|
|
|
|
if (assertion.SigningCredentials != null
|
|
&& assertion.SigningCredentials.SigningKeyIdentifier != null
|
|
&& issuerResolver != null)
|
|
{
|
|
SecurityKeyIdentifier keyIdentifier = assertion.SigningCredentials.SigningKeyIdentifier;
|
|
return issuerResolver.TryResolveToken(keyIdentifier, out token);
|
|
}
|
|
else
|
|
{
|
|
token = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This handles the construct used in <Subject> and <SubjectConfirmation> for ID:
|
|
/// <choice>
|
|
/// <element ref="saml:BaseID" />
|
|
/// <element ref="saml:NameID" />
|
|
/// <element ref="saml:EncryptedID" />
|
|
/// </choice>
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> reader positioned at a <see cref="Saml2NameIdentifier"/> element.</param>
|
|
/// <param name="parentElement">The parent element this SubjectID is part of.</param>
|
|
/// <returns>A <see cref="Saml2NameIdentifier"/> constructed from the XML.</returns>
|
|
protected virtual Saml2NameIdentifier ReadSubjectId(XmlReader reader, string parentElement)
|
|
{
|
|
// <NameID>, <EncryptedID>, <BaseID>
|
|
if (reader.IsStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadNameId(reader);
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadEncryptedId(reader);
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.BaseID, Saml2Constants.Namespace))
|
|
{
|
|
// Since BaseID is an abstract type, we have to switch off the xsi:type declaration
|
|
XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader);
|
|
|
|
// No declaration, or declaring that this is just a "BaseID", is invalid since
|
|
// statement is abstract
|
|
if (null == declaredType
|
|
|| XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.BaseIDAbstractType, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
// If it's NameID we can handle it
|
|
if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.NameIDType, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadNameIdType(reader);
|
|
}
|
|
else
|
|
{
|
|
// Instruct the user to override to handle custom <BaseID>
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4110, parentElement, declaredType.Name, declaredType.Namespace));
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Action> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Action"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Action"/> instance.</returns>
|
|
protected virtual Saml2Action ReadAction(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// disallow empty
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.Action, Saml2Constants.Namespace));
|
|
}
|
|
|
|
try
|
|
{
|
|
// Need the content to instantiate, so use locals
|
|
Uri actionNamespace;
|
|
|
|
// @attributes
|
|
string attributeValue;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ActionType, Saml2Constants.Namespace);
|
|
|
|
// @Namespace - required
|
|
attributeValue = reader.GetAttribute(Saml2Constants.Attributes.Namespace);
|
|
if (string.IsNullOrEmpty(attributeValue))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action));
|
|
}
|
|
|
|
if (!UriUtil.CanCreateValidUri(attributeValue, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action));
|
|
}
|
|
|
|
actionNamespace = new Uri(attributeValue);
|
|
|
|
// Content is string
|
|
return new Saml2Action(reader.ReadElementString(), actionNamespace);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Action> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Action"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Action"/> to serialize.</param>
|
|
protected virtual void WriteAction(XmlWriter writer, Saml2Action data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
if (null == data.Namespace)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data.Namespace");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(data.Namespace.ToString()))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("data.Namespace");
|
|
}
|
|
|
|
// <Action>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace);
|
|
|
|
// @Namespace - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Namespace, data.Namespace.AbsoluteUri);
|
|
|
|
// String content
|
|
writer.WriteString(data.Value);
|
|
|
|
// </Action>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Advice> element.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>
|
|
/// The Advice element has an extensibility point to allow XML elements
|
|
/// from non-SAML2 namespaces to be included. By default, because the
|
|
/// Advice may be ignored without affecting the semantics of the
|
|
/// assertion, any such elements are ignored. To handle the processing
|
|
/// of those elements, override this method.
|
|
/// </para>
|
|
/// </remarks>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Advice"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Advice"/> instance.</returns>
|
|
protected virtual Saml2Advice ReadAdvice(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2Advice advice = new Saml2Advice();
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AdviceType, Saml2Constants.Namespace);
|
|
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
// <AssertionIDRef|AssertionURIRef|Assertion|EncryptedAssertion|other:any> 0-OO
|
|
while (reader.IsStartElement())
|
|
{
|
|
// <AssertionIDRef>, <AssertionURIRef>, <Assertion>, <EncryptedAssertion>
|
|
if (reader.IsStartElement(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace))
|
|
{
|
|
advice.AssertionIdReferences.Add(ReadSimpleNCNameElement(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace))
|
|
{
|
|
advice.AssertionUriReferences.Add(ReadSimpleUriElement(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace))
|
|
{
|
|
advice.Assertions.Add(this.ReadAssertion(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace))
|
|
{
|
|
advice.Assertions.Add(this.ReadAssertion(reader));
|
|
}
|
|
else
|
|
{
|
|
TraceUtility.TraceString(TraceEventType.Warning, SR.GetString(SR.ID8006, reader.LocalName, reader.NamespaceURI));
|
|
reader.Skip();
|
|
}
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return advice;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Advice> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Advice"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Advice"/> to serialize.</param>
|
|
protected virtual void WriteAdvice(XmlWriter writer, Saml2Advice data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <Advice>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace);
|
|
|
|
// <AssertionIDRef> 0-OO
|
|
foreach (Saml2Id id in data.AssertionIdReferences)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace, id.Value);
|
|
}
|
|
|
|
// <AssertionURIRef> 0-OO
|
|
foreach (Uri uri in data.AssertionUriReferences)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace, uri.AbsoluteUri);
|
|
}
|
|
|
|
// <Assertion> 0-OO
|
|
foreach (Saml2Assertion assertion in data.Assertions)
|
|
{
|
|
this.WriteAssertion(writer, assertion);
|
|
}
|
|
|
|
// </Advice>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Assertion> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Assertion"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Assertion"/> instance.</returns>
|
|
protected virtual Saml2Assertion ReadAssertion(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
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));
|
|
}
|
|
|
|
if (this.Configuration.ServiceTokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4276));
|
|
}
|
|
|
|
XmlDictionaryReader plaintextReader = XmlDictionaryReader.CreateDictionaryReader(reader);
|
|
|
|
Saml2Assertion assertion = new Saml2Assertion(new Saml2NameIdentifier("__TemporaryIssuer__"));
|
|
|
|
// If it's an EncryptedAssertion, we need to retrieve the plaintext
|
|
// and repoint our reader
|
|
if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace))
|
|
{
|
|
EncryptingCredentials encryptingCredentials = null;
|
|
plaintextReader = CreatePlaintextReaderFromEncryptedData(
|
|
plaintextReader,
|
|
Configuration.ServiceTokenResolver,
|
|
this.KeyInfoSerializer,
|
|
assertion.ExternalEncryptedKeys,
|
|
out encryptingCredentials);
|
|
|
|
assertion.EncryptingCredentials = encryptingCredentials;
|
|
}
|
|
|
|
// Throw if wrong element
|
|
if (!plaintextReader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace))
|
|
{
|
|
plaintextReader.ReadStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// disallow empty
|
|
if (plaintextReader.IsEmptyElement)
|
|
{
|
|
#pragma warning suppress 56504 // bogus - thinks plaintextReader.LocalName, plaintextReader.NamespaceURI need validation
|
|
throw DiagnosticUtility.ThrowHelperXml(plaintextReader, SR.GetString(SR.ID3061, plaintextReader.LocalName, plaintextReader.NamespaceURI));
|
|
}
|
|
|
|
// Construct a wrapped serializer so that the EnvelopedSignatureReader's
|
|
// attempt to read the <ds:KeyInfo> will hit our ReadKeyInfo virtual.
|
|
WrappedSerializer wrappedSerializer = new WrappedSerializer(this, assertion);
|
|
|
|
// SAML supports enveloped signature, so we need to wrap our reader.
|
|
// We do not dispose this reader, since as a delegating reader it would
|
|
// dispose the inner reader, which we don't properly own.
|
|
EnvelopedSignatureReader realReader = new EnvelopedSignatureReader(plaintextReader, wrappedSerializer, this.Configuration.IssuerTokenResolver, false, false, false);
|
|
try
|
|
{
|
|
// Process @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(realReader, Saml2Constants.Types.AssertionType, Saml2Constants.Namespace);
|
|
|
|
// @Version - required - must be "2.0"
|
|
string version = realReader.GetAttribute(Saml2Constants.Attributes.Version);
|
|
if (string.IsNullOrEmpty(version))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Version, Saml2Constants.Elements.Assertion));
|
|
}
|
|
|
|
if (!StringComparer.Ordinal.Equals(assertion.Version, version))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID4100, version));
|
|
}
|
|
|
|
// @ID - required
|
|
value = realReader.GetAttribute(Saml2Constants.Attributes.ID);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.ID, Saml2Constants.Elements.Assertion));
|
|
}
|
|
|
|
assertion.Id = new Saml2Id(value);
|
|
|
|
// @IssueInstant - required
|
|
value = realReader.GetAttribute(Saml2Constants.Attributes.IssueInstant);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(realReader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.IssueInstant, Saml2Constants.Elements.Assertion));
|
|
}
|
|
|
|
assertion.IssueInstant = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
|
|
// Process <elements>
|
|
realReader.Read();
|
|
|
|
// <Issuer> 1
|
|
assertion.Issuer = this.ReadIssuer(realReader);
|
|
|
|
// <ds:Signature> 0-1
|
|
realReader.TryReadSignature();
|
|
|
|
// <Subject> 0-1
|
|
if (realReader.IsStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace))
|
|
{
|
|
assertion.Subject = this.ReadSubject(realReader);
|
|
}
|
|
|
|
// <Conditions> 0-1
|
|
if (realReader.IsStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace))
|
|
{
|
|
assertion.Conditions = this.ReadConditions(realReader);
|
|
}
|
|
|
|
// <Advice> 0-1
|
|
if (realReader.IsStartElement(Saml2Constants.Elements.Advice, Saml2Constants.Namespace))
|
|
{
|
|
assertion.Advice = this.ReadAdvice(realReader);
|
|
}
|
|
|
|
// <Statement|AuthnStatement|AuthzDecisionStatement|AttributeStatement>, 0-OO
|
|
while (realReader.IsStartElement())
|
|
{
|
|
Saml2Statement statement;
|
|
|
|
if (realReader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace))
|
|
{
|
|
statement = this.ReadStatement(realReader);
|
|
}
|
|
else if (realReader.IsStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace))
|
|
{
|
|
statement = this.ReadAttributeStatement(realReader);
|
|
}
|
|
else if (realReader.IsStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace))
|
|
{
|
|
statement = this.ReadAuthenticationStatement(realReader);
|
|
}
|
|
else if (realReader.IsStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace))
|
|
{
|
|
statement = this.ReadAuthorizationDecisionStatement(realReader);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
|
|
assertion.Statements.Add(statement);
|
|
}
|
|
|
|
realReader.ReadEndElement();
|
|
|
|
if (null == assertion.Subject)
|
|
{
|
|
// An assertion with no statements MUST contain a <Subject> element. [Saml2Core, line 585]
|
|
if (0 == assertion.Statements.Count)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4106));
|
|
}
|
|
|
|
// Furthermore, the built-in statement types all require the presence of a subject.
|
|
// [Saml2Core, lines 1050, 1168, 1280]
|
|
foreach (Saml2Statement statement in assertion.Statements)
|
|
{
|
|
if (statement is Saml2AuthenticationStatement
|
|
|| statement is Saml2AttributeStatement
|
|
|| statement is Saml2AuthorizationDecisionStatement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4119));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reading the end element will complete the signature;
|
|
// capture the signing creds
|
|
assertion.SigningCredentials = realReader.SigningCredentials;
|
|
|
|
// Save the captured on-the-wire data, which can then be used
|
|
// to re-emit this assertion, preserving the same signature.
|
|
assertion.CaptureSourceData(realReader);
|
|
|
|
return assertion;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(realReader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the provided SamlAssertion to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Assertion"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Assertion"/> to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The <paramref name="writer"/> or <paramref name="data"/> parameters are null.</exception>
|
|
/// <exception cref="InvalidOperationException"> The <paramref name="data"/> has both <see cref="EncryptingCredentials"/> and <see cref="ReceivedEncryptingCredentials"/> properties null.</exception>
|
|
/// <exception cref="InvalidOperationException">The <paramref name="data"/> must have a <see cref="Saml2Subject"/> if no <see cref="Saml2Statement"/> are present.</exception>
|
|
/// <exception cref="InvalidOperationException">The SAML2 authentication, attribute, and authorization decision <see cref="Saml2Statement"/> require a <see cref="Saml2Subject"/>.</exception>
|
|
/// <exception cref="CryptographicException">Token encrypting credentials must have a Symmetric Key specified.</exception>
|
|
protected virtual void WriteAssertion(XmlWriter writer, Saml2Assertion data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
XmlWriter originalWriter = writer;
|
|
MemoryStream plaintextStream = null;
|
|
XmlDictionaryWriter plaintextWriter = null;
|
|
|
|
// If an EncryptingCredentials is present then check if this is not of type ReceivedEncryptinCredentials.
|
|
// ReceivedEncryptingCredentials mean that it was credentials that were hydrated from a token received
|
|
// on the wire. We should not directly use this while re-serializing a token.
|
|
if ((null != data.EncryptingCredentials) && !(data.EncryptingCredentials is ReceivedEncryptingCredentials))
|
|
{
|
|
plaintextStream = new MemoryStream();
|
|
writer = plaintextWriter = XmlDictionaryWriter.CreateTextWriter(plaintextStream, Encoding.UTF8, false);
|
|
}
|
|
else if (data.ExternalEncryptedKeys == null || data.ExternalEncryptedKeys.Count > 0)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4173)));
|
|
}
|
|
|
|
// If we've saved off the token stream, re-emit it.
|
|
if (data.CanWriteSourceData)
|
|
{
|
|
data.WriteSourceData(writer);
|
|
}
|
|
else
|
|
{
|
|
// Wrap the writer if necessary for a signature
|
|
// We do not dispose this writer, since as a delegating writer it would
|
|
// dispose the inner writer, which we don't properly own.
|
|
EnvelopedSignatureWriter signatureWriter = null;
|
|
if (null != data.SigningCredentials)
|
|
{
|
|
#pragma warning suppress 56506
|
|
writer = signatureWriter = new EnvelopedSignatureWriter(writer, data.SigningCredentials, data.Id.Value, new WrappedSerializer(this, data));
|
|
}
|
|
|
|
if (null == data.Subject)
|
|
{
|
|
// An assertion with no statements MUST contain a <Subject> element. [Saml2Core, line 585]
|
|
if (data.Statements == null || 0 == data.Statements.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4106)));
|
|
}
|
|
|
|
// Furthermore, the built-in statement types all require the presence of a subject.
|
|
// [Saml2Core, lines 1050, 1168, 1280]
|
|
foreach (Saml2Statement statement in data.Statements)
|
|
{
|
|
if (statement is Saml2AuthenticationStatement
|
|
|| statement is Saml2AttributeStatement
|
|
|| statement is Saml2AuthorizationDecisionStatement)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4119)));
|
|
}
|
|
}
|
|
}
|
|
|
|
// <Assertion>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace);
|
|
|
|
// @ID - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.ID, data.Id.Value);
|
|
|
|
// @IssueInstant - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.IssueInstant, XmlConvert.ToString(data.IssueInstant.ToUniversalTime(), DateTimeFormats.Generated));
|
|
|
|
// @Version - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Version, data.Version);
|
|
|
|
// <Issuer> 1
|
|
this.WriteIssuer(writer, data.Issuer);
|
|
|
|
// <ds:Signature> 0-1
|
|
if (null != signatureWriter)
|
|
{
|
|
signatureWriter.WriteSignature();
|
|
}
|
|
|
|
// <Subject> 0-1
|
|
if (null != data.Subject)
|
|
{
|
|
this.WriteSubject(writer, data.Subject);
|
|
}
|
|
|
|
// <Conditions> 0-1
|
|
if (null != data.Conditions)
|
|
{
|
|
this.WriteConditions(writer, data.Conditions);
|
|
}
|
|
|
|
// <Advice> 0-1
|
|
if (null != data.Advice)
|
|
{
|
|
this.WriteAdvice(writer, data.Advice);
|
|
}
|
|
|
|
// <Statement|AuthnStatement|AuthzDecisionStatement|AttributeStatement>, 0-OO
|
|
foreach (Saml2Statement statement in data.Statements)
|
|
{
|
|
this.WriteStatement(writer, statement);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
// Finish off the encryption
|
|
if (null != plaintextWriter)
|
|
{
|
|
((IDisposable)plaintextWriter).Dispose();
|
|
plaintextWriter = null;
|
|
|
|
EncryptedDataElement encryptedData = new EncryptedDataElement();
|
|
encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element;
|
|
encryptedData.Algorithm = data.EncryptingCredentials.Algorithm;
|
|
encryptedData.KeyIdentifier = data.EncryptingCredentials.SecurityKeyIdentifier;
|
|
|
|
// Get the encryption key, which must be symmetric
|
|
SymmetricSecurityKey encryptingKey = data.EncryptingCredentials.SecurityKey as SymmetricSecurityKey;
|
|
if (encryptingKey == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID3064)));
|
|
}
|
|
|
|
// Do the actual encryption
|
|
SymmetricAlgorithm symmetricAlgorithm = encryptingKey.GetSymmetricAlgorithm(data.EncryptingCredentials.Algorithm);
|
|
encryptedData.Encrypt(symmetricAlgorithm, plaintextStream.GetBuffer(), 0, (int)plaintextStream.Length);
|
|
((IDisposable)plaintextStream).Dispose();
|
|
|
|
originalWriter.WriteStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace);
|
|
encryptedData.WriteXml(originalWriter, this.KeyInfoSerializer);
|
|
foreach (EncryptedKeyIdentifierClause clause in data.ExternalEncryptedKeys)
|
|
{
|
|
this.KeyInfoSerializer.WriteKeyIdentifierClause(originalWriter, clause);
|
|
}
|
|
|
|
originalWriter.WriteEndElement();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Attribute> element.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default implementation requires that the content of the
|
|
/// Attribute element be a simple string. To handle complex content
|
|
/// or content of declared simple types other than xs:string, override
|
|
/// this method.
|
|
/// </remarks>
|
|
/// <param name="reader">An <see cref="XmlReader"/> positioned at a <see cref="Saml2Attribute"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Attribute"/> instance.</returns>
|
|
protected virtual Saml2Attribute ReadAttribute(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2Attribute attribute;
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AttributeType, Saml2Constants.Namespace);
|
|
|
|
// @Name - required
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Name);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Name, Saml2Constants.Elements.Attribute));
|
|
}
|
|
|
|
attribute = new Saml2Attribute(value);
|
|
|
|
// @NameFormat - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NameFormat);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Namespace, Saml2Constants.Elements.Action));
|
|
}
|
|
|
|
attribute.NameFormat = new Uri(value);
|
|
}
|
|
|
|
// @FriendlyName - optional
|
|
attribute.FriendlyName = reader.GetAttribute(Saml2Constants.Attributes.FriendlyName);
|
|
|
|
// @OriginalIssuer - optional.
|
|
// 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(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace);
|
|
|
|
if (originalIssuer == null)
|
|
{
|
|
originalIssuer = reader.GetAttribute(Saml2Constants.Attributes.OriginalIssuer, ProductConstants.NamespaceUri);
|
|
}
|
|
|
|
if (originalIssuer == String.Empty)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new XmlException(SR.GetString(SR.ID4252)));
|
|
}
|
|
|
|
attribute.OriginalIssuer = originalIssuer;
|
|
|
|
// content
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
while (reader.IsStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace))
|
|
{
|
|
bool isEmptyValue = reader.IsEmptyElement;
|
|
bool isNil = XmlUtil.IsNil(reader);
|
|
|
|
// 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.
|
|
// For attributeValueXsiTypeSuffix, we want the portion after the local prefix in "some-non-empty-local-prefix:some-non-empty-string"
|
|
// "some-non-empty-local-prefix:some-non-empty-string" case
|
|
string attributeValueXsiTypePrefix = null;
|
|
string attributeValueXsiTypeSuffix = null;
|
|
string attributeValueXsiTypeSuffixWithLocalPrefix = reader.GetAttribute("type", XmlSchema.InstanceNamespace);
|
|
if (!string.IsNullOrEmpty(attributeValueXsiTypeSuffixWithLocalPrefix))
|
|
{
|
|
// "some-non-empty-string" case
|
|
if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) == -1)
|
|
{
|
|
attributeValueXsiTypePrefix = reader.LookupNamespace(String.Empty);
|
|
attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix;
|
|
}
|
|
else if (attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) > 0 &&
|
|
attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) < attributeValueXsiTypeSuffixWithLocalPrefix.Length - 1)
|
|
{
|
|
string localPrefix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(0, attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal));
|
|
attributeValueXsiTypePrefix = reader.LookupNamespace(localPrefix);
|
|
attributeValueXsiTypeSuffix = attributeValueXsiTypeSuffixWithLocalPrefix.Substring(attributeValueXsiTypeSuffixWithLocalPrefix.IndexOf(":", StringComparison.Ordinal) + 1);
|
|
}
|
|
}
|
|
|
|
if (attributeValueXsiTypePrefix != null && attributeValueXsiTypeSuffix != null)
|
|
{
|
|
attribute.AttributeValueXsiType = String.Concat(attributeValueXsiTypePrefix, "#", attributeValueXsiTypeSuffix);
|
|
}
|
|
|
|
if (isNil)
|
|
{
|
|
reader.Read();
|
|
if (!isEmptyValue)
|
|
{
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
attribute.Values.Add(null);
|
|
}
|
|
else if (isEmptyValue)
|
|
{
|
|
reader.Read();
|
|
attribute.Values.Add(string.Empty);
|
|
}
|
|
else
|
|
{
|
|
attribute.Values.Add(this.ReadAttributeValue(reader, attribute));
|
|
}
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return attribute;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads an attribute value.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="attribute">The <see cref="Saml2Attribute"/>.</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, Saml2Attribute 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, Saml2Constants.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>
|
|
/// Writes the <saml:Attribute> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Attribute"/> to serialize.</param>
|
|
protected virtual void WriteAttribute(XmlWriter writer, Saml2Attribute data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <Attribute>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace);
|
|
|
|
// @Name - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Name, data.Name);
|
|
|
|
// @NameFormat - optional
|
|
if (null != data.NameFormat)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NameFormat, data.NameFormat.AbsoluteUri);
|
|
}
|
|
|
|
// @FriendlyName - optional
|
|
if (null != data.FriendlyName)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.FriendlyName, data.FriendlyName);
|
|
}
|
|
|
|
// @OriginalIssuer - optional
|
|
if (null != data.OriginalIssuer)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.OriginalIssuer, ClaimType2009Namespace, data.OriginalIssuer);
|
|
}
|
|
|
|
string xsiTypePrefix = null;
|
|
string xsiTypeSuffix = null;
|
|
if (!StringComparer.Ordinal.Equals(data.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 = data.AttributeValueXsiType.IndexOf('#');
|
|
xsiTypePrefix = data.AttributeValueXsiType.Substring(0, indexOfHash);
|
|
xsiTypeSuffix = data.AttributeValueXsiType.Substring(indexOfHash + 1);
|
|
}
|
|
|
|
// <AttributeValue> 0-OO (nillable)
|
|
foreach (string value in data.Values)
|
|
{
|
|
writer.WriteStartElement(Saml2Constants.Elements.AttributeValue, Saml2Constants.Namespace);
|
|
|
|
if (null == value)
|
|
{
|
|
writer.WriteAttributeString("nil", XmlSchema.InstanceNamespace, XmlConvert.ToString(true));
|
|
}
|
|
else if (value.Length > 0)
|
|
{
|
|
if ((xsiTypePrefix != null) && (xsiTypeSuffix != null))
|
|
{
|
|
writer.WriteAttributeString("xmlns", ProductConstants.ClaimValueTypeSerializationPrefix, null, xsiTypePrefix);
|
|
writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, String.Concat(ProductConstants.ClaimValueTypeSerializationPrefixWithColon, xsiTypeSuffix));
|
|
}
|
|
|
|
this.WriteAttributeValue(writer, value, data);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
// </Attribute>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the saml:Attribute value.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Attribute"/>.</param>
|
|
/// <param name="value">The value of the attribute being serialized.</param>
|
|
/// <param name="attribute">The <see cref="Saml2Attribute"/> to serialize.</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, Saml2Attribute attribute)
|
|
{
|
|
if (writer == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
writer.WriteString(value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:AttributeStatement> element, or a
|
|
/// <saml:Statement element that specifies an xsi:type of
|
|
/// saml:AttributeStatementType.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2AttributeStatement"/> element.</param>
|
|
/// <returns>A <see cref="Saml2AttributeStatement"/> instance.</returns>
|
|
protected virtual Saml2AttributeStatement ReadAttributeStatement(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
bool isStatementElement = false;
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace))
|
|
{
|
|
isStatementElement = true;
|
|
}
|
|
else if (!reader.IsStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
// defer disallowing empty element until checking xsi:type
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AttributeStatementType, Saml2Constants.Namespace, isStatementElement);
|
|
|
|
// disallow empty element, since xsi:type is ok
|
|
if (isEmpty)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace));
|
|
}
|
|
|
|
// Content
|
|
Saml2AttributeStatement statement = new Saml2AttributeStatement();
|
|
reader.Read();
|
|
|
|
// <Attribute|EncryptedAttribute> 1-OO
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAttribute, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4158));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace))
|
|
{
|
|
statement.Attributes.Add(this.ReadAttribute(reader));
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
// At least one attribute expected
|
|
if (0 == statement.Attributes.Count)
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Attribute, Saml2Constants.Namespace);
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return statement;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:AttributeStatement> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2AttributeStatement"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2AttributeStatement"/> to serialize.</param>
|
|
protected virtual void WriteAttributeStatement(XmlWriter writer, Saml2AttributeStatement data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
if (data.Attributes == null || 0 == data.Attributes.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4124)));
|
|
}
|
|
|
|
// <AttributeStatement>
|
|
writer.WriteStartElement(Saml2Constants.Elements.AttributeStatement, Saml2Constants.Namespace);
|
|
|
|
// <Attribute> 1-OO
|
|
foreach (Saml2Attribute attribute in data.Attributes)
|
|
{
|
|
this.WriteAttribute(writer, attribute);
|
|
}
|
|
|
|
// </AttributeStatement>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:AudienceRestriction> element or a
|
|
/// <saml:Condition> element that specifies an xsi:type
|
|
/// of saml:AudienceRestrictionType.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2AudienceRestriction"/> element.</param>
|
|
/// <returns>A <see cref="Saml2AudienceRestriction"/> instance.</returns>
|
|
protected virtual Saml2AudienceRestriction ReadAudienceRestriction(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
bool isConditionElement = false;
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace))
|
|
{
|
|
isConditionElement = true;
|
|
}
|
|
else if (!reader.IsStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2AudienceRestriction audienceRestriction;
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type -- if we're a <Condition> element, this declaration must be present
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AudienceRestrictionType, Saml2Constants.Namespace, isConditionElement);
|
|
|
|
// disallow empty
|
|
if (isEmpty)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
// content
|
|
reader.Read();
|
|
|
|
// <Audience> - 1-OO
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// We are now laxing the uri check for audience restriction to support interop partners
|
|
// This is a specific request from server : Bug 11850
|
|
// ReadSimpleUriElement now has a flag that turns lax reading ON/OFF.
|
|
audienceRestriction = new Saml2AudienceRestriction(ReadSimpleUriElement(reader, UriKind.RelativeOrAbsolute, true));
|
|
while (reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace))
|
|
{
|
|
audienceRestriction.Audiences.Add(ReadSimpleUriElement(reader, UriKind.RelativeOrAbsolute, true));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return audienceRestriction;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:AudienceRestriction> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2AudienceRestriction"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2AudienceRestriction"/> to serialize.</param>
|
|
protected virtual void WriteAudienceRestriction(XmlWriter writer, Saml2AudienceRestriction data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// Schema requires at least one audience.
|
|
if (data.Audiences == null || 0 == data.Audiences.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4159)));
|
|
}
|
|
|
|
// <AudienceRestriction>
|
|
writer.WriteStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace);
|
|
|
|
// <Audience> - 1-OO
|
|
foreach (Uri audience in data.Audiences)
|
|
{
|
|
// 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(Saml2Constants.Elements.Audience, Saml2Constants.Namespace, audience.OriginalString);
|
|
}
|
|
|
|
// </AudienceRestriction>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:AuthnContext> element.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The default implementation does not handle the optional
|
|
/// <saml:AuthnContextDecl> element. To handle by-value
|
|
/// authentication context declarations, override this method.
|
|
/// </remarks>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2AuthenticationContext"/> element.</param>
|
|
/// <returns>A <see cref="Saml2AuthenticationContext"/> instance.</returns>
|
|
protected virtual Saml2AuthenticationContext ReadAuthenticationContext(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Disallow empty
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace));
|
|
}
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthnContextType, Saml2Constants.Namespace);
|
|
|
|
// Content
|
|
reader.ReadStartElement();
|
|
|
|
// At least one of ClassRef and ( Decl XOR DeclRef) must be present
|
|
// At this time, we do not support Decl, which is a by-value
|
|
// authentication context declaration.
|
|
Uri classRef = null;
|
|
Uri declRef = null;
|
|
|
|
// <AuthnContextClassRef> - see comment above
|
|
if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextClassRef, Saml2Constants.Namespace))
|
|
{
|
|
classRef = ReadSimpleUriElement(reader);
|
|
}
|
|
|
|
// <AuthnContextDecl> - see comment above
|
|
if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextDecl, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4118));
|
|
}
|
|
|
|
// <AuthnContextDeclRef> - see comment above
|
|
// If there was no ClassRef, there must be a DeclRef
|
|
if (reader.IsStartElement(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace))
|
|
{
|
|
declRef = ReadSimpleUriElement(reader);
|
|
}
|
|
else if (null == classRef)
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// Now we have enough data to create the object
|
|
Saml2AuthenticationContext authnContext = new Saml2AuthenticationContext(classRef, declRef);
|
|
|
|
// <AuthenticatingAuthority> - 0-OO
|
|
while (reader.IsStartElement(Saml2Constants.Elements.AuthenticatingAuthority, Saml2Constants.Namespace))
|
|
{
|
|
authnContext.AuthenticatingAuthorities.Add(ReadSimpleUriElement(reader));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return authnContext;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:AuthnContext> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2AuthenticationContext"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2AuthenticationContext"/> to serialize.</param>
|
|
protected virtual void WriteAuthenticationContext(XmlWriter writer, Saml2AuthenticationContext data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// One of ClassRef and DeclRef must be present.
|
|
if (null == data.ClassReference && null == data.DeclarationReference)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4117)));
|
|
}
|
|
|
|
// <AuthnContext>
|
|
writer.WriteStartElement(Saml2Constants.Elements.AuthnContext, Saml2Constants.Namespace);
|
|
|
|
// <AuthnContextClassReference> 0-1
|
|
if (null != data.ClassReference)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AuthnContextClassRef, Saml2Constants.Namespace, data.ClassReference.AbsoluteUri);
|
|
}
|
|
|
|
// <AuthnContextDeclRef> 0-1
|
|
if (null != data.DeclarationReference)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AuthnContextDeclRef, Saml2Constants.Namespace, data.DeclarationReference.AbsoluteUri);
|
|
}
|
|
|
|
// <AuthenticatingAuthority> 0-OO
|
|
foreach (Uri authority in data.AuthenticatingAuthorities)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AuthenticatingAuthority, Saml2Constants.Namespace, authority.AbsoluteUri);
|
|
}
|
|
|
|
// </AuthnContext>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:AuthnStatement> element or a <saml:Statement>
|
|
/// element that specifies an xsi:type of saml:AuthnStatementType.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2AuthenticationStatement"/> element.</param>
|
|
/// <returns>A <see cref="Saml2AuthenticationStatement"/> instance.</returns>
|
|
protected virtual Saml2AuthenticationStatement ReadAuthenticationStatement(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
bool isStatementElement = false;
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace))
|
|
{
|
|
isStatementElement = true;
|
|
}
|
|
else if (!reader.IsStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Must cache the individual data since the required
|
|
// AuthnContext comes last
|
|
DateTime authnInstant;
|
|
Saml2AuthenticationContext authnContext;
|
|
string sessionIndex;
|
|
DateTime? sessionNotOnOrAfter = null;
|
|
Saml2SubjectLocality subjectLocality = null;
|
|
|
|
// defer disallowing empty until after xsi:type
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type -- if we're a <Statement> element, this declaration must be present
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthnStatementType, Saml2Constants.Namespace, isStatementElement);
|
|
|
|
// disallow empty, since xsi:type is ok
|
|
if (isEmpty)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace));
|
|
}
|
|
|
|
// @AuthnInstant - required
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.AuthnInstant);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.AuthnInstant, Saml2Constants.Elements.AuthnStatement));
|
|
}
|
|
|
|
authnInstant = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
|
|
// @SessionIndex - optional
|
|
sessionIndex = reader.GetAttribute(Saml2Constants.Attributes.SessionIndex);
|
|
|
|
// @SessionNotOnOrAfter - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.SessionNotOnOrAfter);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
sessionNotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
}
|
|
|
|
// Content
|
|
reader.Read();
|
|
|
|
// <SubjectLocality> 0-1
|
|
if (reader.IsStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace))
|
|
{
|
|
subjectLocality = this.ReadSubjectLocality(reader);
|
|
}
|
|
|
|
// <AuthnContext> 1
|
|
authnContext = this.ReadAuthenticationContext(reader);
|
|
|
|
reader.ReadEndElement();
|
|
|
|
// Construct the actual object
|
|
Saml2AuthenticationStatement authnStatement = new Saml2AuthenticationStatement(authnContext, authnInstant);
|
|
authnStatement.SessionIndex = sessionIndex;
|
|
authnStatement.SessionNotOnOrAfter = sessionNotOnOrAfter;
|
|
authnStatement.SubjectLocality = subjectLocality;
|
|
|
|
return authnStatement;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:AuthnStatement> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2AuthenticationStatement"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2AuthenticationStatement"/> to serialize.</param>
|
|
protected virtual void WriteAuthenticationStatement(XmlWriter writer, Saml2AuthenticationStatement data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <AuthnStatement>
|
|
writer.WriteStartElement(Saml2Constants.Elements.AuthnStatement, Saml2Constants.Namespace);
|
|
|
|
// @AuthnInstant - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.AuthnInstant, XmlConvert.ToString(data.AuthenticationInstant.ToUniversalTime(), DateTimeFormats.Generated));
|
|
|
|
// @SessionIndex - optional
|
|
if (null != data.SessionIndex)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.SessionIndex, data.SessionIndex);
|
|
}
|
|
|
|
// @SessionNotOnOrAfter - optional
|
|
if (null != data.SessionNotOnOrAfter)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.SessionNotOnOrAfter, XmlConvert.ToString(data.SessionNotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated));
|
|
}
|
|
|
|
// <SubjectLocality> 0-1
|
|
if (null != data.SubjectLocality)
|
|
{
|
|
this.WriteSubjectLocality(writer, data.SubjectLocality);
|
|
}
|
|
|
|
// <AuthnContext> 1
|
|
this.WriteAuthenticationContext(writer, data.AuthenticationContext);
|
|
|
|
// </AuthnStatement>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:AuthzDecisionStatement> element or a
|
|
/// <saml:Statement element that specifies an xsi:type of
|
|
/// saml:AuthzDecisionStatementType.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2AuthorizationDecisionStatement"/> element.</param>
|
|
/// <returns>A <see cref="Saml2AuthorizationDecisionStatement"/> instance.</returns>
|
|
protected virtual Saml2AuthorizationDecisionStatement ReadAuthorizationDecisionStatement(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
bool isStatementElement = false;
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace))
|
|
{
|
|
isStatementElement = true;
|
|
}
|
|
else if (!reader.IsStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
// Need the attributes before we can instantiate
|
|
Saml2AuthorizationDecisionStatement statement;
|
|
SamlAccessDecision decision;
|
|
Uri resource;
|
|
|
|
// defer rejecting empty until processing xsi:type
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.AuthzDecisionStatementType, Saml2Constants.Namespace, isStatementElement);
|
|
|
|
// disallow empty, since xsi:type is ok
|
|
if (isEmpty)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace));
|
|
}
|
|
|
|
// @Decision - required
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Decision);
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Decision, Saml2Constants.Elements.AuthzDecisionStatement));
|
|
}
|
|
else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Permit.ToString(), value))
|
|
{
|
|
decision = SamlAccessDecision.Permit;
|
|
}
|
|
else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Deny.ToString(), value))
|
|
{
|
|
decision = SamlAccessDecision.Deny;
|
|
}
|
|
else if (StringComparer.Ordinal.Equals(SamlAccessDecision.Indeterminate.ToString(), value))
|
|
{
|
|
decision = SamlAccessDecision.Indeterminate;
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4123, value));
|
|
}
|
|
|
|
// @Resource - required
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Resource);
|
|
if (null == value)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Resource, Saml2Constants.Elements.AuthzDecisionStatement));
|
|
}
|
|
else if (0 == value.Length)
|
|
{
|
|
resource = Saml2AuthorizationDecisionStatement.EmptyResource;
|
|
}
|
|
else
|
|
{
|
|
if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4121));
|
|
}
|
|
|
|
resource = new Uri(value);
|
|
}
|
|
|
|
// Content
|
|
statement = new Saml2AuthorizationDecisionStatement(resource, decision);
|
|
reader.Read();
|
|
|
|
// <Action> 1-OO
|
|
do
|
|
{
|
|
statement.Actions.Add(this.ReadAction(reader));
|
|
}
|
|
while (reader.IsStartElement(Saml2Constants.Elements.Action, Saml2Constants.Namespace));
|
|
|
|
// <Evidence> 0-1
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace))
|
|
{
|
|
statement.Evidence = this.ReadEvidence(reader);
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return statement;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:AuthzDecisionStatement> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2AuthorizationDecisionStatement"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2AuthorizationDecisionStatement"/> to serialize.</param>
|
|
protected virtual void WriteAuthorizationDecisionStatement(XmlWriter writer, Saml2AuthorizationDecisionStatement data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
#pragma warning suppress 56506 // actions are never null
|
|
if (0 == data.Actions.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4122)));
|
|
}
|
|
|
|
// <AuthzDecisionStatement>
|
|
writer.WriteStartElement(Saml2Constants.Elements.AuthzDecisionStatement, Saml2Constants.Namespace);
|
|
|
|
// @Decision - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Decision, data.Decision.ToString());
|
|
|
|
// @Resource - required
|
|
#pragma warning suppress 56506 // Resource are never null
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Resource, data.Resource.Equals(Saml2AuthorizationDecisionStatement.EmptyResource) ? data.Resource.ToString() : data.Resource.AbsoluteUri);
|
|
|
|
// @Action 1-OO
|
|
foreach (Saml2Action action in data.Actions)
|
|
{
|
|
this.WriteAction(writer, action);
|
|
}
|
|
|
|
// Evidence 0-1
|
|
if (null != data.Evidence)
|
|
{
|
|
this.WriteEvidence(writer, data.Evidence);
|
|
}
|
|
|
|
// </AuthzDecisionStatement>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Conditions> element.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// To handle custom <saml:Condition> elements, override this
|
|
/// method.
|
|
/// </remarks>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Conditions"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Conditions"/> instance.</returns>
|
|
protected virtual Saml2Conditions ReadConditions(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2Conditions conditions = new Saml2Conditions();
|
|
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ConditionsType, Saml2Constants.Namespace);
|
|
|
|
// @NotBefore - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NotBefore);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
conditions.NotBefore = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
}
|
|
|
|
// @NotOnOrAfter - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NotOnOrAfter);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
conditions.NotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
}
|
|
|
|
// Content
|
|
reader.ReadStartElement();
|
|
if (!isEmpty)
|
|
{
|
|
// <Condition|AudienceRestriction|OneTimeUse|ProxyRestriction>, 0-OO
|
|
while (reader.IsStartElement())
|
|
{
|
|
// <Condition> - 0-OO
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace))
|
|
{
|
|
// Since Condition is abstract, must process based on xsi:type
|
|
XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader);
|
|
|
|
// No type, throw
|
|
if (null == declaredType
|
|
|| XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.ConditionAbstractType, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AudienceRestrictionType, Saml2Constants.Namespace))
|
|
{
|
|
conditions.AudienceRestrictions.Add(this.ReadAudienceRestriction(reader));
|
|
}
|
|
else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.OneTimeUseType, Saml2Constants.Namespace))
|
|
{
|
|
if (conditions.OneTimeUse)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.OneTimeUse));
|
|
}
|
|
|
|
ReadEmptyContentElement(reader);
|
|
conditions.OneTimeUse = true;
|
|
}
|
|
else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.ProxyRestrictionType, Saml2Constants.Namespace))
|
|
{
|
|
if (null != conditions.ProxyRestriction)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.ProxyRestricton));
|
|
}
|
|
|
|
conditions.ProxyRestriction = this.ReadProxyRestriction(reader);
|
|
}
|
|
else
|
|
{
|
|
// Unknown type - Instruct the user to override to handle custom <Condition>
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4113));
|
|
}
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.AudienceRestriction, Saml2Constants.Namespace))
|
|
{
|
|
conditions.AudienceRestrictions.Add(this.ReadAudienceRestriction(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.OneTimeUse, Saml2Constants.Namespace))
|
|
{
|
|
if (conditions.OneTimeUse)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.OneTimeUse));
|
|
}
|
|
|
|
ReadEmptyContentElement(reader);
|
|
conditions.OneTimeUse = true;
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace))
|
|
{
|
|
if (null != conditions.ProxyRestriction)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4115, Saml2Constants.Elements.ProxyRestricton));
|
|
}
|
|
|
|
conditions.ProxyRestriction = this.ReadProxyRestriction(reader);
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return conditions;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Conditions> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Conditions"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Conditions"/> to serialize.</param>
|
|
protected virtual void WriteConditions(XmlWriter writer, Saml2Conditions data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <Conditions>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Conditions, Saml2Constants.Namespace);
|
|
|
|
// @NotBefore - optional
|
|
if (null != data.NotBefore)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NotBefore, XmlConvert.ToString(data.NotBefore.Value.ToUniversalTime(), DateTimeFormats.Generated));
|
|
}
|
|
|
|
// @NotOnOrAfter - optional
|
|
if (null != data.NotOnOrAfter)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NotOnOrAfter, XmlConvert.ToString(data.NotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated));
|
|
}
|
|
|
|
// <AudienceRestriction> 0-OO
|
|
foreach (Saml2AudienceRestriction audienceRestriction in data.AudienceRestrictions)
|
|
{
|
|
this.WriteAudienceRestriction(writer, audienceRestriction);
|
|
}
|
|
|
|
// <OneTimeUse> - limited to one in SAML spec
|
|
if (data.OneTimeUse)
|
|
{
|
|
writer.WriteStartElement(Saml2Constants.Elements.OneTimeUse, Saml2Constants.Namespace);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
// <ProxyRestriction> - limited to one in SAML spec
|
|
if (null != data.ProxyRestriction)
|
|
{
|
|
this.WriteProxyRestriction(writer, data.ProxyRestriction);
|
|
}
|
|
|
|
// </Conditions>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Evidence> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Evidence"/> element.</param>
|
|
/// <returns>A <see cref="Saml2Evidence"/> instance.</returns>
|
|
protected virtual Saml2Evidence ReadEvidence(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// disallow empty
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, Saml2Constants.Elements.Evidence, Saml2Constants.Namespace));
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2Evidence evidence = new Saml2Evidence();
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.EvidenceType, Saml2Constants.Namespace);
|
|
|
|
reader.Read();
|
|
|
|
// <AssertionIDRef|AssertionURIRef|Assertion|EncryptedAssertion> 0-OO
|
|
while (reader.IsStartElement())
|
|
{
|
|
if (reader.IsStartElement(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace))
|
|
{
|
|
evidence.AssertionIdReferences.Add(ReadSimpleNCNameElement(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace))
|
|
{
|
|
evidence.AssertionUriReferences.Add(ReadSimpleUriElement(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.Assertion, Saml2Constants.Namespace))
|
|
{
|
|
evidence.Assertions.Add(this.ReadAssertion(reader));
|
|
}
|
|
else if (reader.IsStartElement(Saml2Constants.Elements.EncryptedAssertion, Saml2Constants.Namespace))
|
|
{
|
|
evidence.Assertions.Add(this.ReadAssertion(reader));
|
|
}
|
|
}
|
|
|
|
if (0 == evidence.AssertionIdReferences.Count
|
|
&& 0 == evidence.Assertions.Count
|
|
&& 0 == evidence.AssertionUriReferences.Count)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4120));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
return evidence;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Evidence> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Evidence"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Evidence"/> to serialize.</param>
|
|
protected virtual void WriteEvidence(XmlWriter writer, Saml2Evidence data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
if ((data.AssertionIdReferences == null || 0 == data.AssertionIdReferences.Count)
|
|
&& (data.Assertions == null || 0 == data.Assertions.Count)
|
|
&& (data.AssertionUriReferences == null || 0 == data.AssertionUriReferences.Count))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4120)));
|
|
}
|
|
|
|
// <Evidence>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Evidence, Saml2Constants.Namespace);
|
|
|
|
// <AssertionIDRef> 0-OO
|
|
foreach (Saml2Id id in data.AssertionIdReferences)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AssertionIDRef, Saml2Constants.Namespace, id.Value);
|
|
}
|
|
|
|
// <AssertionURIRef> 0-OO
|
|
foreach (Uri uri in data.AssertionUriReferences)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.AssertionURIRef, Saml2Constants.Namespace, uri.AbsoluteUri);
|
|
}
|
|
|
|
// <Assertion> 0-OO
|
|
foreach (Saml2Assertion assertion in data.Assertions)
|
|
{
|
|
this.WriteAssertion(writer, assertion);
|
|
}
|
|
|
|
// </Evidence>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Issuer> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2NameIdentifier"/> element.</param>
|
|
/// <returns>A <see cref="Saml2NameIdentifier"/> instance.</returns>
|
|
protected virtual Saml2NameIdentifier ReadIssuer(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace);
|
|
}
|
|
|
|
return this.ReadNameIdType(reader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Issuer> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2NameIdentifier"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2NameIdentifier"/> to serialize.</param>
|
|
protected virtual void WriteIssuer(XmlWriter writer, Saml2NameIdentifier data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
writer.WriteStartElement(Saml2Constants.Elements.Issuer, Saml2Constants.Namespace);
|
|
this.WriteNameIdType(writer, data);
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes the SAML Subject KeyInfo.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader positioned at a ds:KeyInfo element.</param>
|
|
/// <returns>A <see cref="SecurityKeyIdentifier"/> instance.</returns>
|
|
/// <exception cref="ArgumentNullException">Input parameter 'reader' is null.</exception>
|
|
protected virtual SecurityKeyIdentifier ReadSubjectKeyInfo(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
return this.KeyInfoSerializer.ReadKeyIdentifier(reader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializes the SAML Signing KeyInfo
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a than can be positioned at a ds:KeyInfo element.</param>
|
|
/// <param name="assertion">The <see cref="Saml2Assertion"/> 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">Input parameter 'reader' is null.</exception>
|
|
protected virtual SecurityKeyIdentifier ReadSigningKeyInfo(XmlReader reader, Saml2Assertion assertion)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
SecurityKeyIdentifier ski;
|
|
|
|
if (this.KeyInfoSerializer.CanReadKeyIdentifier(reader))
|
|
{
|
|
ski = this.KeyInfoSerializer.ReadKeyIdentifier(reader);
|
|
}
|
|
else
|
|
{
|
|
KeyInfo keyInfo = new KeyInfo(this.KeyInfoSerializer);
|
|
keyInfo.ReadXml(XmlDictionaryReader.CreateDictionaryReader(reader));
|
|
ski = keyInfo.KeyIdentifier;
|
|
}
|
|
|
|
// no key info
|
|
if (ski.Count == 0)
|
|
{
|
|
return new SecurityKeyIdentifier(new Saml2SecurityKeyIdentifierClause(assertion));
|
|
}
|
|
|
|
return ski;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the Subject KeyInfo into the given XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityKeyIdentifier"/>.</param>
|
|
/// <param name="data">The <see cref="SecurityKeyIdentifier"/> to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'data' is null.</exception>
|
|
protected virtual void WriteSubjectKeyInfo(XmlWriter writer, SecurityKeyIdentifier data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
this.KeyInfoSerializer.WriteKeyIdentifier(writer, data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the Signing KeyInfo into the given XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityKeyIdentifier"/>.</param>
|
|
/// <param name="data">The <see cref="SecurityKeyIdentifier"/> to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'signingKeyIdentifier' is null.</exception>
|
|
protected virtual void WriteSigningKeyInfo(XmlWriter writer, SecurityKeyIdentifier data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
if (this.KeyInfoSerializer.CanWriteKeyIdentifier(data))
|
|
{
|
|
this.KeyInfoSerializer.WriteKeyIdentifier(writer, data);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4221, data));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:NameID> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2NameIdentifier"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2NameIdentifier"/></returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
protected virtual Saml2NameIdentifier ReadNameId(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace);
|
|
}
|
|
|
|
return this.ReadNameIdType(reader);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:NameID> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2NameIdentifier"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2NameIdentifier"/> to serialize.</param>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'writer' or 'data' is null.</exception>
|
|
/// <exception cref="CryptographicException">Saml2NameIdentifier encrypting credentials must have a Symmetric Key specified.</exception>
|
|
protected virtual void WriteNameId(XmlWriter writer, Saml2NameIdentifier data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// If there are encrypting credentials, then we need to encrypt the name identifier
|
|
if (data.EncryptingCredentials != null)
|
|
{
|
|
EncryptingCredentials encryptingCredentials = data.EncryptingCredentials;
|
|
|
|
// Get the encryption key, which must be symmetric
|
|
SymmetricSecurityKey encryptingKey = encryptingCredentials.SecurityKey as SymmetricSecurityKey;
|
|
if (encryptingKey == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID3284)));
|
|
}
|
|
|
|
MemoryStream plaintextStream = null;
|
|
try
|
|
{
|
|
// Serialize an encrypted name ID
|
|
plaintextStream = new MemoryStream();
|
|
|
|
using (XmlWriter plaintextWriter = XmlDictionaryWriter.CreateTextWriter(plaintextStream, Encoding.UTF8, false))
|
|
{
|
|
plaintextWriter.WriteStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace);
|
|
this.WriteNameIdType(plaintextWriter, data);
|
|
plaintextWriter.WriteEndElement();
|
|
}
|
|
|
|
EncryptedDataElement encryptedData = new EncryptedDataElement();
|
|
encryptedData.Type = XmlEncryptionConstants.EncryptedDataTypes.Element;
|
|
encryptedData.Algorithm = encryptingCredentials.Algorithm;
|
|
encryptedData.KeyIdentifier = encryptingCredentials.SecurityKeyIdentifier;
|
|
|
|
// Perform encryption
|
|
SymmetricAlgorithm symmetricAlgorithm = encryptingKey.GetSymmetricAlgorithm(encryptingCredentials.Algorithm);
|
|
encryptedData.Encrypt(symmetricAlgorithm, plaintextStream.GetBuffer(), 0, (int)plaintextStream.Length);
|
|
((IDisposable)plaintextStream).Dispose();
|
|
|
|
writer.WriteStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace);
|
|
encryptedData.WriteXml(writer, this.KeyInfoSerializer);
|
|
|
|
foreach (EncryptedKeyIdentifierClause clause in data.ExternalEncryptedKeys)
|
|
{
|
|
this.KeyInfoSerializer.WriteKeyIdentifierClause(writer, clause);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
finally
|
|
{
|
|
if (plaintextStream != null)
|
|
{
|
|
plaintextStream.Dispose();
|
|
plaintextStream = null;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
writer.WriteStartElement(Saml2Constants.Elements.NameID, Saml2Constants.Namespace);
|
|
this.WriteNameIdType(writer, data);
|
|
writer.WriteEndElement();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Both <Issuer> and <NameID> are of NameIDType. This method reads
|
|
/// the content of either one of those elements.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2NameIdentifier"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2NameIdentifier"/></returns>
|
|
protected virtual Saml2NameIdentifier ReadNameIdType(XmlReader reader)
|
|
{
|
|
try
|
|
{
|
|
reader.MoveToContent();
|
|
|
|
Saml2NameIdentifier nameIdentifier = new Saml2NameIdentifier("__TemporaryName__");
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.NameIDType, Saml2Constants.Namespace);
|
|
|
|
// @Format - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Format);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Format, Saml2Constants.Elements.NameID));
|
|
}
|
|
|
|
nameIdentifier.Format = new Uri(value);
|
|
}
|
|
|
|
// @NameQualifier - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NameQualifier);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
nameIdentifier.NameQualifier = value;
|
|
}
|
|
|
|
// @SPNameQualifier - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.SPNameQualifier);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
nameIdentifier.SPNameQualifier = value;
|
|
}
|
|
|
|
// @SPProvidedID - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.SPProvidedID);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
nameIdentifier.SPProvidedId = value;
|
|
}
|
|
|
|
// Content is string
|
|
nameIdentifier.Value = reader.ReadElementString();
|
|
|
|
// According to section 8.3.6, if the name identifier format is of type 'urn:oasis:names:tc:SAML:2.0:nameid-format:entity'
|
|
// the name identifier value must be a uri and name qualifier, spname qualifier, and spproded id must be omitted.
|
|
if (nameIdentifier.Format != null &&
|
|
StringComparer.Ordinal.Equals(nameIdentifier.Format.AbsoluteUri, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri))
|
|
{
|
|
if (!UriUtil.CanCreateValidUri(nameIdentifier.Value, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4262, nameIdentifier.Value, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri));
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(nameIdentifier.NameQualifier)
|
|
|| !string.IsNullOrEmpty(nameIdentifier.SPNameQualifier)
|
|
|| !string.IsNullOrEmpty(nameIdentifier.SPProvidedId))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4263, nameIdentifier.Value, Saml2Constants.NameIdentifierFormats.Entity.AbsoluteUri));
|
|
}
|
|
}
|
|
|
|
return nameIdentifier;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:EncryptedId> element.
|
|
/// </summary>
|
|
/// <param name="reader"><see cref="XmlReader"/> pointing at the XML EncryptedId element</param>
|
|
/// <returns>An instance of <see cref="Saml2NameIdentifier"/> representing the EncryptedId that was read</returns>
|
|
/// <exception cref="ArgumentNullException">The input parameter 'reader' is null.</exception>
|
|
/// <exception cref="XmlException">The 'reader' is not positioned at an 'EncryptedID' element.</exception>
|
|
protected virtual Saml2NameIdentifier ReadEncryptedId(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
reader.MoveToContent();
|
|
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace))
|
|
{
|
|
// throw if wrong element
|
|
reader.ReadStartElement(Saml2Constants.Elements.EncryptedID, Saml2Constants.Namespace);
|
|
}
|
|
|
|
Collection<EncryptedKeyIdentifierClause> clauses = new Collection<EncryptedKeyIdentifierClause>();
|
|
EncryptingCredentials encryptingCredentials = null;
|
|
Saml2NameIdentifier saml2NameIdentifier = null;
|
|
|
|
using (StringReader sr = new StringReader(reader.ReadOuterXml()))
|
|
{
|
|
using (XmlDictionaryReader wrappedReader = new WrappedXmlDictionaryReader(XmlReader.Create(sr), XmlDictionaryReaderQuotas.Max))
|
|
{
|
|
XmlReader plaintextReader = CreatePlaintextReaderFromEncryptedData(
|
|
wrappedReader,
|
|
Configuration.ServiceTokenResolver,
|
|
this.KeyInfoSerializer,
|
|
clauses,
|
|
out encryptingCredentials);
|
|
|
|
saml2NameIdentifier = this.ReadNameIdType(plaintextReader);
|
|
saml2NameIdentifier.EncryptingCredentials = encryptingCredentials;
|
|
foreach (EncryptedKeyIdentifierClause clause in clauses)
|
|
{
|
|
saml2NameIdentifier.ExternalEncryptedKeys.Add(clause);
|
|
}
|
|
}
|
|
}
|
|
|
|
return saml2NameIdentifier;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Both <Issuer> and <NameID> are of NameIDType. This method writes
|
|
/// the content of either one of those elements.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2NameIdentifier"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2NameIdentifier"/> to serialize.</param>
|
|
protected virtual void WriteNameIdType(XmlWriter writer, Saml2NameIdentifier data)
|
|
{
|
|
// @Format - optional
|
|
if (null != data.Format)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Format, data.Format.AbsoluteUri);
|
|
}
|
|
|
|
// @NameQualifier - optional
|
|
if (!string.IsNullOrEmpty(data.NameQualifier))
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NameQualifier, data.NameQualifier);
|
|
}
|
|
|
|
// @SPNameQualifier - optional
|
|
if (!string.IsNullOrEmpty(data.SPNameQualifier))
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.SPNameQualifier, data.SPNameQualifier);
|
|
}
|
|
|
|
// @SPProvidedId - optional
|
|
if (!string.IsNullOrEmpty(data.SPProvidedId))
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.SPProvidedID, data.SPProvidedId);
|
|
}
|
|
|
|
// Content is string
|
|
writer.WriteString(data.Value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:ProxyRestriction> element, or a <saml:Condition>
|
|
/// element that specifies an xsi:type of saml:ProxyRestrictionType.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// In the default implementation, the maximum value of the Count attribute
|
|
/// is limited to Int32.MaxValue.
|
|
/// </remarks>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2ProxyRestriction"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2ProxyRestriction"/></returns>
|
|
protected virtual Saml2ProxyRestriction ReadProxyRestriction(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
bool isConditionElement = false;
|
|
if (reader.IsStartElement(Saml2Constants.Elements.Condition, Saml2Constants.Namespace))
|
|
{
|
|
isConditionElement = true;
|
|
}
|
|
else if (!reader.IsStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2ProxyRestriction proxyRestriction = new Saml2ProxyRestriction();
|
|
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type -- if we're a <Condition> element, this declaration must be present
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.ProxyRestrictionType, Saml2Constants.Namespace, isConditionElement);
|
|
|
|
// @Count - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Count);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
proxyRestriction.Count = XmlConvert.ToInt32(value);
|
|
}
|
|
|
|
// content
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
// <Audience> - 0-OO
|
|
while (reader.IsStartElement(Saml2Constants.Elements.Audience, Saml2Constants.Namespace))
|
|
{
|
|
proxyRestriction.Audiences.Add(ReadSimpleUriElement(reader));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return proxyRestriction;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:ProxyRestriction> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2ProxyRestriction"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2ProxyRestriction"/> to serialize.</param>
|
|
protected virtual void WriteProxyRestriction(XmlWriter writer, Saml2ProxyRestriction data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
writer.WriteStartElement(Saml2Constants.Elements.ProxyRestricton, Saml2Constants.Namespace);
|
|
|
|
// @Count - optional
|
|
if (null != data.Count)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Count, XmlConvert.ToString(data.Count.Value));
|
|
}
|
|
|
|
// <Audience> - 0-OO
|
|
foreach (Uri uri in data.Audiences)
|
|
{
|
|
writer.WriteElementString(Saml2Constants.Elements.Audience, uri.AbsoluteUri);
|
|
}
|
|
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Statement> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Statement"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2Statement"/> derived type.</returns>
|
|
/// <remarks>
|
|
/// The default implementation only handles Statement elements which
|
|
/// specify an xsi:type of saml:AttributeStatementType,
|
|
/// saml:AuthnStatementType, and saml:AuthzDecisionStatementType. To
|
|
/// handle custom statements, override this method.
|
|
/// </remarks>
|
|
protected virtual Saml2Statement ReadStatement(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Statement, Saml2Constants.Namespace);
|
|
}
|
|
|
|
// Since Statement is an abstract type, we have to switch off the xsi:type declaration
|
|
XmlQualifiedName declaredType = XmlUtil.GetXsiType(reader);
|
|
|
|
// No declaration, or declaring that this is just a "Statement", is invalid since
|
|
// statement is abstract
|
|
if (null == declaredType
|
|
|| XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.StatementAbstractType, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4104, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
// Reroute to the known statement types if applicable
|
|
if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AttributeStatementType, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadAttributeStatement(reader);
|
|
}
|
|
else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AuthnStatementType, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadAuthenticationStatement(reader);
|
|
}
|
|
else if (XmlUtil.EqualsQName(declaredType, Saml2Constants.Types.AuthzDecisionStatementType, Saml2Constants.Namespace))
|
|
{
|
|
return this.ReadAuthorizationDecisionStatement(reader);
|
|
}
|
|
else
|
|
{
|
|
// Throw if we encounter an unknown concrete type
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4105, declaredType.Name, declaredType.Namespace));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes a Saml2Statement.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// This method may write a <saml:AttributeStatement>, <saml:AuthnStatement>
|
|
/// or <saml:AuthzDecisionStatement> element. To handle custom Saml2Statement
|
|
/// classes for writing a <saml:Statement> element, override this method.
|
|
/// </remarks>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Statement"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Statement"/> to serialize.</param>
|
|
protected virtual void WriteStatement(XmlWriter writer, Saml2Statement data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
Saml2AttributeStatement attributeStatement = data as Saml2AttributeStatement;
|
|
if (null != attributeStatement)
|
|
{
|
|
this.WriteAttributeStatement(writer, attributeStatement);
|
|
return;
|
|
}
|
|
|
|
Saml2AuthenticationStatement authnStatement = data as Saml2AuthenticationStatement;
|
|
if (null != authnStatement)
|
|
{
|
|
this.WriteAuthenticationStatement(writer, authnStatement);
|
|
return;
|
|
}
|
|
|
|
Saml2AuthorizationDecisionStatement authzStatement = data as Saml2AuthorizationDecisionStatement;
|
|
if (null != authzStatement)
|
|
{
|
|
this.WriteAuthorizationDecisionStatement(writer, authzStatement);
|
|
return;
|
|
}
|
|
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
|
|
new InvalidOperationException(SR.GetString(SR.ID4107, data.GetType().AssemblyQualifiedName)));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:Subject> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2Subject"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2Subject"/> .</returns>
|
|
/// <remarks>
|
|
/// The default implementation does not handle the optional
|
|
/// <EncryptedID> element. To handle encryped IDs in the Subject,
|
|
/// override this method.
|
|
/// </remarks>
|
|
protected virtual Saml2Subject ReadSubject(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
// disallow empty
|
|
if (reader.IsEmptyElement)
|
|
{
|
|
#pragma warning suppress 56504 // bogus - thinks reader.LocalName, reader.NamespaceURI need validation
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID3061, reader.LocalName, reader.NamespaceURI));
|
|
}
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectType, Saml2Constants.Namespace);
|
|
|
|
// <elements>
|
|
Saml2Subject subject = new Saml2Subject();
|
|
reader.Read();
|
|
|
|
// <NameID> | <EncryptedID> | <BaseID> 0-1
|
|
subject.NameId = this.ReadSubjectId(reader, Saml2Constants.Elements.Subject);
|
|
|
|
// <SubjectConfirmation> 0-OO
|
|
while (reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace))
|
|
{
|
|
subject.SubjectConfirmations.Add(this.ReadSubjectConfirmation(reader));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
|
|
// Must have a NameID or a SubjectConfirmation
|
|
if (null == subject.NameId && 0 == subject.SubjectConfirmations.Count)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4108));
|
|
}
|
|
|
|
return subject;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:Subject> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2Subject"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2Subject"/> to serialize.</param>
|
|
protected virtual void WriteSubject(XmlWriter writer, Saml2Subject data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// If there's no ID, there has to be a SubjectConfirmation
|
|
#pragma warning suppress 56506 // SubjectConfirmations is never null
|
|
if (null == data.NameId && 0 == data.SubjectConfirmations.Count)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4108)));
|
|
}
|
|
|
|
// <Subject>
|
|
writer.WriteStartElement(Saml2Constants.Elements.Subject, Saml2Constants.Namespace);
|
|
|
|
// no attributes
|
|
|
|
// <NameID> 0-1
|
|
if (null != data.NameId)
|
|
{
|
|
this.WriteNameId(writer, data.NameId);
|
|
}
|
|
|
|
// <SubjectConfirmation> 0-OO
|
|
foreach (Saml2SubjectConfirmation subjectConfirmation in data.SubjectConfirmations)
|
|
{
|
|
this.WriteSubjectConfirmation(writer, subjectConfirmation);
|
|
}
|
|
|
|
// </Subject>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <SubjectConfirmation> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2SubjectConfirmation"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2SubjectConfirmation"/> .</returns>
|
|
protected virtual Saml2SubjectConfirmation ReadSubjectConfirmation(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectConfirmationType, Saml2Constants.Namespace);
|
|
|
|
// @Method - required
|
|
string method = reader.GetAttribute(Saml2Constants.Attributes.Method);
|
|
if (string.IsNullOrEmpty(method))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0001, Saml2Constants.Attributes.Method, Saml2Constants.Elements.SubjectConfirmation));
|
|
}
|
|
|
|
if (!UriUtil.CanCreateValidUri(method, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Method, Saml2Constants.Elements.SubjectConfirmation));
|
|
}
|
|
|
|
// Construct the appropriate SubjectConfirmation based on the method
|
|
Saml2SubjectConfirmation subjectConfirmation = new Saml2SubjectConfirmation(new Uri(method));
|
|
|
|
// <elements>
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
// <NameID> | <EncryptedID> | <BaseID> 0-1
|
|
subjectConfirmation.NameIdentifier = this.ReadSubjectId(reader, Saml2Constants.Elements.SubjectConfirmation);
|
|
|
|
// <SubjectConfirmationData> 0-1
|
|
if (reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace))
|
|
{
|
|
subjectConfirmation.SubjectConfirmationData = this.ReadSubjectConfirmationData(reader);
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return subjectConfirmation;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:SubjectConfirmation> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2SubjectConfirmation"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2SubjectConfirmation"/> to serialize.</param>
|
|
protected virtual void WriteSubjectConfirmation(XmlWriter writer, Saml2SubjectConfirmation data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
if (null == data.Method)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data.Method");
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(data.Method.ToString()))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperArgumentNullOrEmptyString("data.Method");
|
|
}
|
|
|
|
// <SubjectConfirmation>
|
|
writer.WriteStartElement(Saml2Constants.Elements.SubjectConfirmation, Saml2Constants.Namespace);
|
|
|
|
// @Method - required
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Method, data.Method.AbsoluteUri);
|
|
|
|
// <NameID> 0-1
|
|
if (null != data.NameIdentifier)
|
|
{
|
|
this.WriteNameId(writer, data.NameIdentifier);
|
|
}
|
|
|
|
// <SubjectConfirmationData> 0-1
|
|
if (null != data.SubjectConfirmationData)
|
|
{
|
|
this.WriteSubjectConfirmationData(writer, data.SubjectConfirmationData);
|
|
}
|
|
|
|
// </SubjectConfirmation>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:SubjectConfirmationData> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2SubjectConfirmationData"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2SubjectConfirmationData"/> .</returns>
|
|
/// <remarks>
|
|
/// The default implementation handles the unextended element
|
|
/// as well as the extended type saml:KeyInfoConfirmationDataType.
|
|
/// </remarks>
|
|
protected virtual Saml2SubjectConfirmationData ReadSubjectConfirmationData(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2SubjectConfirmationData confirmationData = new Saml2SubjectConfirmationData();
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
string value;
|
|
|
|
// @xsi:type
|
|
bool requireKeyInfo = false;
|
|
XmlQualifiedName type = XmlUtil.GetXsiType(reader);
|
|
|
|
if (null != type)
|
|
{
|
|
if (XmlUtil.EqualsQName(type, Saml2Constants.Types.KeyInfoConfirmationDataType, Saml2Constants.Namespace))
|
|
{
|
|
requireKeyInfo = true;
|
|
}
|
|
else if (!XmlUtil.EqualsQName(type, Saml2Constants.Types.SubjectConfirmationDataType, Saml2Constants.Namespace))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4112, type.Name, type.Namespace));
|
|
}
|
|
}
|
|
|
|
// KeyInfoConfirmationData cannot be empty
|
|
if (requireKeyInfo && isEmpty)
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.GetString(SR.ID4111)));
|
|
}
|
|
|
|
// @Address - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Address);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
confirmationData.Address = value;
|
|
}
|
|
|
|
// @InResponseTo - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.InResponseTo);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
confirmationData.InResponseTo = new Saml2Id(value);
|
|
}
|
|
|
|
// @NotBefore - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NotBefore);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
confirmationData.NotBefore = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
}
|
|
|
|
// @NotOnOrAfter - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.NotOnOrAfter);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
confirmationData.NotOnOrAfter = XmlConvert.ToDateTime(value, DateTimeFormats.Accepted);
|
|
}
|
|
|
|
// @Recipient - optional
|
|
value = reader.GetAttribute(Saml2Constants.Attributes.Recipient);
|
|
if (!string.IsNullOrEmpty(value))
|
|
{
|
|
if (!UriUtil.CanCreateValidUri(value, UriKind.Absolute))
|
|
{
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID0011, Saml2Constants.Attributes.Recipient, Saml2Constants.Elements.SubjectConfirmationData));
|
|
}
|
|
|
|
confirmationData.Recipient = new Uri(value);
|
|
}
|
|
|
|
// Contents
|
|
reader.Read();
|
|
|
|
if (!isEmpty)
|
|
{
|
|
// <ds:KeyInfo> 0-OO OR 1-OO
|
|
if (requireKeyInfo)
|
|
{
|
|
confirmationData.KeyIdentifiers.Add(this.ReadSubjectKeyInfo(reader));
|
|
}
|
|
|
|
while (reader.IsStartElement(XmlSignatureConstants.Elements.KeyInfo, XmlSignatureConstants.Namespace))
|
|
{
|
|
confirmationData.KeyIdentifiers.Add(this.ReadSubjectKeyInfo(reader));
|
|
}
|
|
|
|
// If this isn't KeyInfo restricted, there might be open content here ...
|
|
if (!requireKeyInfo && XmlNodeType.EndElement != reader.NodeType)
|
|
{
|
|
// So throw and tell the user how to handle the open content
|
|
throw DiagnosticUtility.ThrowHelperXml(reader, SR.GetString(SR.ID4114, Saml2Constants.Elements.SubjectConfirmationData));
|
|
}
|
|
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return confirmationData;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:SubjectConfirmationData> element.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// When the data.KeyIdentifiers collection is not empty, an xsi:type
|
|
/// attribute will be written specifying saml:KeyInfoConfirmationDataType.
|
|
/// </remarks>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2SubjectConfirmationData"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2SubjectConfirmationData"/> to serialize.</param>
|
|
protected virtual void WriteSubjectConfirmationData(XmlWriter writer, Saml2SubjectConfirmationData data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <SubjectConfirmationData>
|
|
writer.WriteStartElement(Saml2Constants.Elements.SubjectConfirmationData, Saml2Constants.Namespace);
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
if (data.KeyIdentifiers != null && data.KeyIdentifiers.Count > 0)
|
|
{
|
|
writer.WriteAttributeString("type", XmlSchema.InstanceNamespace, Saml2Constants.Types.KeyInfoConfirmationDataType);
|
|
}
|
|
|
|
// @Address - optional
|
|
if (!string.IsNullOrEmpty(data.Address))
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Address, data.Address);
|
|
}
|
|
|
|
// @InResponseTo - optional
|
|
if (null != data.InResponseTo)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.InResponseTo, data.InResponseTo.Value);
|
|
}
|
|
|
|
// @NotBefore - optional
|
|
if (null != data.NotBefore)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NotBefore, XmlConvert.ToString(data.NotBefore.Value.ToUniversalTime(), DateTimeFormats.Generated));
|
|
}
|
|
|
|
// @NotOnOrAfter - optional
|
|
if (null != data.NotOnOrAfter)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.NotOnOrAfter, XmlConvert.ToString(data.NotOnOrAfter.Value.ToUniversalTime(), DateTimeFormats.Generated));
|
|
}
|
|
|
|
// @Recipient - optional
|
|
if (null != data.Recipient)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Recipient, data.Recipient.OriginalString);
|
|
}
|
|
|
|
// Content
|
|
|
|
// <ds:KeyInfo> 0-OO
|
|
foreach (SecurityKeyIdentifier keyIdentifier in data.KeyIdentifiers)
|
|
{
|
|
this.WriteSubjectKeyInfo(writer, keyIdentifier);
|
|
}
|
|
|
|
// </SubjectConfirmationData>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the <saml:SubjectLocality> element.
|
|
/// </summary>
|
|
/// <param name="reader">A <see cref="XmlReader"/> positioned at a <see cref="Saml2SubjectLocality"/> element.</param>
|
|
/// <returns>An instance of <see cref="Saml2SubjectLocality"/> .</returns>
|
|
protected virtual Saml2SubjectLocality ReadSubjectLocality(XmlReader reader)
|
|
{
|
|
if (null == reader)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
// throw if wrong element
|
|
if (!reader.IsStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace))
|
|
{
|
|
reader.ReadStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace);
|
|
}
|
|
|
|
try
|
|
{
|
|
Saml2SubjectLocality subjectLocality = new Saml2SubjectLocality();
|
|
bool isEmpty = reader.IsEmptyElement;
|
|
|
|
// @attributes
|
|
|
|
// @xsi:type
|
|
XmlUtil.ValidateXsiType(reader, Saml2Constants.Types.SubjectLocalityType, Saml2Constants.Namespace);
|
|
|
|
// @Address - optional
|
|
subjectLocality.Address = reader.GetAttribute(Saml2Constants.Attributes.Address);
|
|
|
|
// @DNSName - optional
|
|
subjectLocality.DnsName = reader.GetAttribute(Saml2Constants.Attributes.DNSName);
|
|
|
|
// Empty content
|
|
reader.Read();
|
|
if (!isEmpty)
|
|
{
|
|
reader.ReadEndElement();
|
|
}
|
|
|
|
return subjectLocality;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (System.Runtime.Fx.IsFatal(e))
|
|
throw;
|
|
|
|
Exception wrapped = TryWrapReadException(reader, e);
|
|
if (null == wrapped)
|
|
{
|
|
throw;
|
|
}
|
|
else
|
|
{
|
|
throw wrapped;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the <saml:SubjectLocality> element.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="Saml2SubjectLocality"/>.</param>
|
|
/// <param name="data">The <see cref="Saml2SubjectLocality"/> to serialize.</param>
|
|
protected virtual void WriteSubjectLocality(XmlWriter writer, Saml2SubjectLocality data)
|
|
{
|
|
if (null == writer)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
|
|
}
|
|
|
|
if (null == data)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("data");
|
|
}
|
|
|
|
// <SubjectLocality>
|
|
writer.WriteStartElement(Saml2Constants.Elements.SubjectLocality, Saml2Constants.Namespace);
|
|
|
|
// @Address - optional
|
|
if (null != data.Address)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.Address, data.Address);
|
|
}
|
|
|
|
// @DNSName - optional
|
|
if (null != data.DnsName)
|
|
{
|
|
writer.WriteAttributeString(Saml2Constants.Attributes.DNSName, data.DnsName);
|
|
}
|
|
|
|
// </SubjectLocality>
|
|
writer.WriteEndElement();
|
|
}
|
|
|
|
// This thin wrapper is used to pass a serializer down into the
|
|
// EnvelpoedSignatureReader that will use the Saml2AssertionSerializer's
|
|
// ReadKeyInfo method to read the KeyInfo.
|
|
internal class WrappedSerializer : SecurityTokenSerializer
|
|
{
|
|
private Saml2SecurityTokenHandler parent;
|
|
private Saml2Assertion assertion;
|
|
|
|
public WrappedSerializer(Saml2SecurityTokenHandler parent, Saml2Assertion assertion)
|
|
{
|
|
this.assertion = assertion;
|
|
this.parent = parent;
|
|
}
|
|
|
|
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 this.parent.ReadSigningKeyInfo(reader, this.assertion);
|
|
}
|
|
|
|
protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extensibility point for providing custom serialization.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityKeyIdentifierClause"/>.</param>
|
|
/// <param name="keyIdentifierClause">The <see cref="SecurityKeyIdentifierClause"/> to serialize.</param>
|
|
/// <remarks>This is not supported.</remarks>
|
|
protected override void WriteKeyIdentifierClauseCore(XmlWriter writer, SecurityKeyIdentifierClause keyIdentifierClause)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extensibility point for providing custom serialization.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityKeyIdentifier"/>.</param>
|
|
/// <param name="keyIdentifier">The <see cref="SecurityKeyIdentifier"/> to serialize.</param>
|
|
protected override void WriteKeyIdentifierCore(XmlWriter writer, SecurityKeyIdentifier keyIdentifier)
|
|
{
|
|
this.parent.WriteSigningKeyInfo(writer, keyIdentifier);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extensibility point for providing custom serialization.
|
|
/// </summary>
|
|
/// <param name="writer">A <see cref="XmlWriter"/> to serialize the <see cref="SecurityToken"/>.</param>
|
|
/// <param name="token">The <see cref="SecurityToken"/> to serialize.</param>
|
|
protected override void WriteTokenCore(XmlWriter writer, SecurityToken token)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// When encrypted SAML 2.0 token is received, the credentials that are used
|
|
/// to encrypt the token will be hydrated as a ReceivedEncryptingCredentials.
|
|
/// This is to distinguish the case between a user explicitly setting an
|
|
/// encrypting credentials and a re-serialize case where a received token
|
|
/// is re-serialized by a proxy to a backend service, in which case the token
|
|
/// should not be encrypted.
|
|
/// </summary>
|
|
internal class ReceivedEncryptingCredentials : EncryptingCredentials
|
|
{
|
|
/// <summary>
|
|
/// Constructs an ReceivedEncryptingCredentials with a security key, a security key identifier and
|
|
/// the encryption algorithm.
|
|
/// </summary>
|
|
/// <param name="key">A security key for encryption.</param>
|
|
/// <param name="keyIdentifier">A security key identifier for the encryption key.</param>
|
|
/// <param name="algorithm">The encryption algorithm.</param>
|
|
public ReceivedEncryptingCredentials(SecurityKey key, SecurityKeyIdentifier keyIdentifier, string algorithm)
|
|
: base(key, keyIdentifier, algorithm)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|