e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
825 lines
35 KiB
C#
825 lines
35 KiB
C#
//------------------------------------------------------------
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------
|
|
|
|
namespace System.IdentityModel.Tokens
|
|
{
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.IdentityModel.Configuration;
|
|
using System.IdentityModel.Diagnostics;
|
|
using System.IdentityModel.Selectors;
|
|
using System.IO;
|
|
using System.Runtime;
|
|
using System.Security.Claims;
|
|
using System.ServiceModel.Security;
|
|
using System.Xml;
|
|
using SessionDictionary = System.IdentityModel.Claims.SessionDictionary;
|
|
using SysUniqueId = System.Xml.UniqueId;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
|
|
/// <summary>
|
|
/// A <see cref="SecurityTokenHandler"/> that processes <see cref="SessionSecurityToken"/>.
|
|
/// </summary>
|
|
public class SessionSecurityTokenHandler : SecurityTokenHandler
|
|
{
|
|
const string DefaultCookieElementName = "Cookie";
|
|
const string DefaultCookieNamespace = "http://schemas.microsoft.com/ws/2006/05/security";
|
|
private const string SecureConversationTokenIdentifier = "http://schemas.microsoft.com/ws/2006/05/servicemodel/tokens/SecureConversation";
|
|
|
|
#pragma warning disable 1591
|
|
public static readonly TimeSpan DefaultLifetime = TimeSpan.FromHours(10);
|
|
public static readonly ReadOnlyCollection<CookieTransform> DefaultCookieTransforms = (new List<CookieTransform>(new CookieTransform[] { new DeflateCookieTransform(), new ProtectedDataCookieTransform() }).AsReadOnly());
|
|
#pragma warning restore 1591
|
|
|
|
TimeSpan _tokenLifetime = DefaultLifetime;
|
|
ReadOnlyCollection<CookieTransform> _transforms;
|
|
|
|
/// <summary>
|
|
/// Initializes an instance of <see cref="SessionSecurityTokenHandler"/>
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Properties are used for defaults:
|
|
/// DefaultCookieTransforms
|
|
/// DefaultLifetime
|
|
/// </remarks>
|
|
public SessionSecurityTokenHandler()
|
|
: this(SessionSecurityTokenHandler.DefaultCookieTransforms)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes an instance of <see cref="SessionSecurityTokenHandler"/>
|
|
/// </summary>
|
|
/// <param name="transforms">The transforms to apply when encoding the cookie.</param>
|
|
/// <remarks>
|
|
/// Properties are used for the remaining defaults:
|
|
/// DefaultLifetime
|
|
/// </remarks>
|
|
public SessionSecurityTokenHandler(ReadOnlyCollection<CookieTransform> transforms)
|
|
: this(transforms, DefaultLifetime)
|
|
{ }
|
|
|
|
/// <summary>
|
|
/// Initializes an instance of <see cref="SessionSecurityTokenHandler"/>
|
|
/// </summary>
|
|
/// <param name="transforms">The transforms to apply when encoding the cookie.</param>
|
|
/// <param name="tokenLifetime">The default for a token.</param>
|
|
/// <exception cref="ArgumentNullException">Is thrown if 'transforms' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Is thrown if 'tokenLifetime' is less than or equal to TimeSpan.Zero.</exception>
|
|
public SessionSecurityTokenHandler(ReadOnlyCollection<CookieTransform> transforms, TimeSpan tokenLifetime)
|
|
{
|
|
if (transforms == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("transforms");
|
|
}
|
|
|
|
if (tokenLifetime <= TimeSpan.Zero)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID0016)));
|
|
}
|
|
|
|
_transforms = transforms;
|
|
_tokenLifetime = tokenLifetime;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load custom configuration from Xml
|
|
/// </summary>
|
|
/// <param name="customConfigElements">XmlElement to custom configuration.</param>
|
|
/// <exception cref="ArgumentNullException">The param 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 customConfigElement in configNodes)
|
|
{
|
|
if (!StringComparer.Ordinal.Equals(customConfigElement.LocalName, ConfigurationStrings.SessionTokenRequirement))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (foundValidConfig)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7026, ConfigurationStrings.SessionTokenRequirement)));
|
|
}
|
|
|
|
_tokenLifetime = DefaultLifetime;
|
|
|
|
foreach (XmlAttribute attribute in customConfigElement.Attributes)
|
|
{
|
|
if (StringComparer.OrdinalIgnoreCase.Equals(attribute.LocalName, ConfigurationStrings.Lifetime))
|
|
{
|
|
TimeSpan outTokenLifetime = DefaultLifetime;
|
|
if (!TimeSpan.TryParse(attribute.Value, out outTokenLifetime))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7017, attribute.Value)));
|
|
}
|
|
if (outTokenLifetime < TimeSpan.Zero)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7018)));
|
|
}
|
|
_tokenLifetime = outTokenLifetime;
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID7004, attribute.LocalName, customConfigElement.LocalName)));
|
|
}
|
|
}
|
|
|
|
foundValidConfig = true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the name for the cookie element.
|
|
/// </summary>
|
|
public virtual string CookieElementName
|
|
{
|
|
get { return DefaultCookieElementName; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the namspace for the cookie element.
|
|
/// </summary>
|
|
public virtual string CookieNamespace
|
|
{
|
|
get { return DefaultCookieNamespace; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Applies Transforms to the cookie.
|
|
/// </summary>
|
|
/// <param name="cookie">The cookie that will be transformed.</param>
|
|
/// <param name="outbound">Controls if the cookie should be encoded (true) or decoded (false)</param>
|
|
/// <returns>Encoded cookie.</returns>
|
|
protected virtual byte[] ApplyTransforms(byte[] cookie, bool outbound)
|
|
{
|
|
byte[] transformedCookie = cookie;
|
|
|
|
if (Transforms == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4296)));
|
|
}
|
|
|
|
if (outbound)
|
|
{
|
|
for (int i = 0; i < _transforms.Count; i++)
|
|
{
|
|
transformedCookie = _transforms[i].Encode(transformedCookie);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = _transforms.Count; i > 0; i--)
|
|
{
|
|
transformedCookie = _transforms[i - 1].Decode(transformedCookie);
|
|
}
|
|
}
|
|
|
|
return transformedCookie;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the reader if this is a SecurityContextToken.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader over the incoming SecurityToken.</param>
|
|
/// <returns>'True' if the reader points to a SecurityContextToken.</returns>
|
|
/// <exception cref="ArgumentNullException">The input argument 'reader' is null.</exception>
|
|
public override bool CanReadToken(XmlReader reader)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
return (reader.IsStartElement(WSSecureConversationFeb2005Constants.ElementNames.Name, WSSecureConversationFeb2005Constants.Namespace)
|
|
|| reader.IsStartElement(WSSecureConversation13Constants.ElementNames.Name, WSSecureConversation13Constants.Namespace));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether this handler supports validation of tokens.
|
|
/// </summary>
|
|
/// <returns>'True' if the class is capable of SecurityToken validation.</returns>
|
|
public override bool CanValidateToken
|
|
{
|
|
get { return true; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets information on whether this Token Handler can write tokens.
|
|
/// </summary>
|
|
public override bool CanWriteToken
|
|
{
|
|
get
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a security token based on a token descriptor.
|
|
/// </summary>
|
|
/// <param name="tokenDescriptor">The token descriptor.</param>
|
|
/// <returns>A security token.</returns>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'tokenDescriptor' is null.</exception>
|
|
public override SecurityToken CreateToken(SecurityTokenDescriptor tokenDescriptor)
|
|
{
|
|
if (null == tokenDescriptor)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenDescriptor");
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4272)));
|
|
}
|
|
|
|
ClaimsPrincipal principal = new ClaimsPrincipal(tokenDescriptor.Subject);
|
|
|
|
if (this.Configuration.SaveBootstrapContext)
|
|
{
|
|
SecurityTokenHandlerCollection bootstrapTokenCollection = CreateBootstrapTokenHandlerCollection();
|
|
if (!bootstrapTokenCollection.CanWriteToken(tokenDescriptor.Token))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4010, tokenDescriptor.Token.GetType().ToString())));
|
|
}
|
|
|
|
(principal.Identities as ReadOnlyCollection<ClaimsIdentity>)[0].BootstrapContext = new BootstrapContext(tokenDescriptor.Token, bootstrapTokenCollection[tokenDescriptor.Token.GetType()]);
|
|
}
|
|
|
|
DateTime validFrom = (tokenDescriptor.Lifetime.Created.HasValue) ? (DateTime)tokenDescriptor.Lifetime.Created : DateTime.UtcNow;
|
|
DateTime validTo = (tokenDescriptor.Lifetime.Expires.HasValue) ? (DateTime)tokenDescriptor.Lifetime.Expires : DateTime.UtcNow + SessionSecurityTokenHandler.DefaultTokenLifetime;
|
|
|
|
return new SessionSecurityToken(principal, null, validFrom, validTo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a <see cref="SessionSecurityToken"/> based on an <see cref="ClaimsPrincipal"/> and a valid time range.
|
|
/// </summary>
|
|
/// <param name="principal"><see cref="ClaimsPrincipal"/></param>
|
|
/// <param name="context">Caller defined context string</param>
|
|
/// <param name="endpointId">Identifier of the endpoint to which the token is scoped.</param>
|
|
/// <param name="validFrom">Earliest valid time.</param>
|
|
/// <param name="validTo">Latest valid time.</param>
|
|
public virtual SessionSecurityToken CreateSessionSecurityToken(
|
|
ClaimsPrincipal principal,
|
|
string context,
|
|
string endpointId,
|
|
DateTime validFrom,
|
|
DateTime validTo)
|
|
{
|
|
if (null == principal)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("principal");
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4272)));
|
|
}
|
|
|
|
return new SessionSecurityToken(principal, context, endpointId, validFrom, validTo);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the default token lifetime
|
|
/// </summary>
|
|
public static TimeSpan DefaultTokenLifetime
|
|
{
|
|
get { return DefaultLifetime; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the SessionSecurityToken from a stream of bytes.
|
|
/// </summary>
|
|
/// <param name="token">token.</param>
|
|
/// <param name="tokenResolver">SecurityTokenResolver that can be used to resolve the SessionSecurityToken.</param>
|
|
/// <returns>Instance of SessionSecurityToken.</returns>
|
|
public virtual SecurityToken ReadToken(byte[] token, SecurityTokenResolver tokenResolver)
|
|
{
|
|
// Our implementation of ReadToken( byte[] ) will always return null. We make the above call not to
|
|
// break SharePoint. SharePoint has overridden ReadToken(byte[] token) and expect the SessionAuthenticationModule to
|
|
// call that. So SessionAuthenticationModule will calls this method which does the correct thing.
|
|
using (XmlReader reader = XmlDictionaryReader.CreateTextReader(token, XmlDictionaryReaderQuotas.Max))
|
|
{
|
|
return this.ReadToken(reader, tokenResolver);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the SessionSecurityToken from the given reader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader over the SessionSecurityToken.</param>
|
|
/// <returns>An instance of <see cref="SessionSecurityToken"/>.</returns>
|
|
/// <exception cref="ArgumentNullException">The input argument 'reader' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">The 'reader' is not positioned at a SessionSecurityToken
|
|
/// or the SessionSecurityToken cannot be read.</exception>
|
|
public override SecurityToken ReadToken(XmlReader reader)
|
|
{
|
|
return this.ReadToken(reader, EmptySecurityTokenResolver.Instance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the SessionSecurityToken from the given reader.
|
|
/// </summary>
|
|
/// <param name="reader">XmlReader over the SessionSecurityToken.</param>
|
|
/// <param name="tokenResolver">SecurityTokenResolver that can used to resolve SessionSecurityToken.</param>
|
|
/// <returns>An instance of <see cref="SessionSecurityToken"/>.</returns>
|
|
/// <exception cref="ArgumentNullException">The input argument 'reader' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">The 'reader' is not positioned at a SessionSecurityToken
|
|
/// or the SessionSecurityToken cannot be read.</exception>
|
|
public override SecurityToken ReadToken(XmlReader reader, SecurityTokenResolver tokenResolver)
|
|
{
|
|
if (reader == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
|
|
}
|
|
|
|
if (tokenResolver == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenResolver");
|
|
}
|
|
|
|
byte[] encodedCookie = null;
|
|
SysUniqueId contextId = null;
|
|
SysUniqueId keyGeneration = null;
|
|
|
|
string ns = null;
|
|
string identifier = null;
|
|
string instance = null;
|
|
|
|
SecurityToken securityContextToken = null;
|
|
SessionDictionary dictionary = SessionDictionary.Instance;
|
|
|
|
XmlDictionaryReader dicReader = XmlDictionaryReader.CreateDictionaryReader(reader);
|
|
|
|
if (dicReader.IsStartElement(WSSecureConversationFeb2005Constants.ElementNames.Name, WSSecureConversationFeb2005Constants.Namespace))
|
|
{
|
|
ns = WSSecureConversationFeb2005Constants.Namespace;
|
|
identifier = WSSecureConversationFeb2005Constants.ElementNames.Identifier;
|
|
instance = WSSecureConversationFeb2005Constants.ElementNames.Instance;
|
|
}
|
|
else if (dicReader.IsStartElement(WSSecureConversation13Constants.ElementNames.Name, WSSecureConversation13Constants.Namespace))
|
|
{
|
|
ns = WSSecureConversation13Constants.Namespace;
|
|
identifier = WSSecureConversation13Constants.ElementNames.Identifier;
|
|
instance = WSSecureConversation13Constants.ElementNames.Instance;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Something is wrong
|
|
//
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(
|
|
SR.GetString(SR.ID4230, WSSecureConversationFeb2005Constants.ElementNames.Name, dicReader.Name)));
|
|
}
|
|
|
|
string id = dicReader.GetAttribute(WSUtilityConstants.Attributes.IdAttribute, WSUtilityConstants.NamespaceURI);
|
|
|
|
dicReader.ReadFullStartElement();
|
|
if (!dicReader.IsStartElement(identifier, ns))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(
|
|
SR.GetString(SR.ID4230, WSSecureConversation13Constants.ElementNames.Identifier, dicReader.Name)));
|
|
}
|
|
|
|
contextId = dicReader.ReadElementContentAsUniqueId();
|
|
if (contextId == null || string.IsNullOrEmpty(contextId.ToString()))
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4242)));
|
|
}
|
|
|
|
//
|
|
// The token can be a renewed token, in which case we need to know the
|
|
// instance id, which will be the secondary key to the context id for
|
|
// cache lookups
|
|
//
|
|
if (dicReader.IsStartElement(instance, ns))
|
|
{
|
|
keyGeneration = dicReader.ReadElementContentAsUniqueId();
|
|
}
|
|
|
|
if (dicReader.IsStartElement(CookieElementName, CookieNamespace))
|
|
{
|
|
// Get the token from the Cache, which is returned as an SCT
|
|
SecurityToken cachedToken = null;
|
|
|
|
SecurityContextKeyIdentifierClause sctClause = null;
|
|
if (keyGeneration == null)
|
|
{
|
|
sctClause = new SecurityContextKeyIdentifierClause(contextId);
|
|
}
|
|
else
|
|
{
|
|
sctClause = new SecurityContextKeyIdentifierClause(contextId, keyGeneration);
|
|
}
|
|
|
|
tokenResolver.TryResolveToken(sctClause, out cachedToken);
|
|
|
|
if (cachedToken != null)
|
|
{
|
|
securityContextToken = cachedToken;
|
|
|
|
dicReader.Skip();
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// CookieMode
|
|
//
|
|
encodedCookie = dicReader.ReadElementContentAsBase64();
|
|
|
|
if (encodedCookie == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4237)));
|
|
}
|
|
//
|
|
// appply transforms
|
|
//
|
|
byte[] decodedCookie = ApplyTransforms(encodedCookie, false);
|
|
|
|
using (MemoryStream ms = new MemoryStream(decodedCookie))
|
|
{
|
|
BinaryFormatter formatter = new BinaryFormatter();
|
|
securityContextToken = formatter.Deserialize(ms) as SecurityToken;
|
|
}
|
|
|
|
SessionSecurityToken sessionToken = securityContextToken as SessionSecurityToken;
|
|
if (sessionToken != null && sessionToken.ContextId != contextId)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4229, sessionToken.ContextId, contextId)));
|
|
}
|
|
|
|
if (sessionToken != null && sessionToken.Id != id)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4227, sessionToken.Id, id)));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// SessionMode
|
|
//
|
|
|
|
// Get the token from the Cache.
|
|
SecurityToken cachedToken = null;
|
|
|
|
SecurityContextKeyIdentifierClause sctClause = null;
|
|
if (keyGeneration == null)
|
|
{
|
|
sctClause = new SecurityContextKeyIdentifierClause(contextId);
|
|
}
|
|
else
|
|
{
|
|
sctClause = new SecurityContextKeyIdentifierClause(contextId, keyGeneration);
|
|
}
|
|
|
|
tokenResolver.TryResolveToken(sctClause, out cachedToken);
|
|
|
|
if (cachedToken != null)
|
|
{
|
|
securityContextToken = cachedToken;
|
|
}
|
|
}
|
|
|
|
dicReader.ReadEndElement();
|
|
|
|
if (securityContextToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(SR.GetString(SR.ID4243)));
|
|
}
|
|
|
|
return securityContextToken;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the TokenLifetime.
|
|
/// </summary>
|
|
public virtual TimeSpan TokenLifetime
|
|
{
|
|
get { return _tokenLifetime; }
|
|
set
|
|
{
|
|
if (value <= TimeSpan.Zero)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument("value", SR.GetString(SR.ID0016));
|
|
}
|
|
|
|
_tokenLifetime = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the bootstrap token handler collection.
|
|
/// </summary>
|
|
SecurityTokenHandlerCollection CreateBootstrapTokenHandlerCollection()
|
|
{
|
|
SecurityTokenHandlerCollection tokenHandlerCollection = this.ContainingCollection ?? SecurityTokenHandlerCollection.CreateDefaultSecurityTokenHandlerCollection();
|
|
return tokenHandlerCollection;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the token type URIs
|
|
/// </summary>
|
|
public override string[] GetTokenTypeIdentifiers()
|
|
{
|
|
return new string[] { SecureConversationTokenIdentifier,
|
|
WSSecureConversation13Constants.TokenTypeURI,
|
|
WSSecureConversationFeb2005Constants.TokenTypeURI };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the type of token this handler can work with.
|
|
/// </summary>
|
|
public override Type TokenType
|
|
{
|
|
get { return typeof(SessionSecurityToken); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the transforms that will be applied to the cookie.
|
|
/// </summary>
|
|
public ReadOnlyCollection<CookieTransform> Transforms
|
|
{
|
|
get
|
|
{
|
|
return _transforms;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the transforms that will be applied to cookies.
|
|
/// </summary>
|
|
/// <param name="transforms">The <see cref="CookieTransform"/> objects to use. </param>
|
|
protected void SetTransforms(IEnumerable<CookieTransform> transforms)
|
|
{
|
|
_transforms = new List<CookieTransform>(transforms).AsReadOnly();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a <see cref="SessionSecurityToken"/>.
|
|
/// </summary>
|
|
/// <param name="token">The <see cref="SessionSecurityToken"/> 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 assignable from <see cref="SessionSecurityToken"/>.</exception>
|
|
public override ReadOnlyCollection<ClaimsIdentity> ValidateToken(SecurityToken token)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
SessionSecurityToken sessionToken = token as SessionSecurityToken;
|
|
if (sessionToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4292, token.GetType().ToString(), this.GetType().ToString())));
|
|
}
|
|
|
|
try
|
|
{
|
|
if (DiagnosticUtility.ShouldTrace(TraceEventType.Verbose))
|
|
{
|
|
TraceUtility.TraceEvent(
|
|
TraceEventType.Verbose,
|
|
TraceCode.Diagnostics,
|
|
SR.GetString(SR.TraceValidateToken),
|
|
new SecurityTraceRecordHelper.TokenTraceRecord(token),
|
|
null,
|
|
null);
|
|
}
|
|
|
|
this.ValidateSession(sessionToken);
|
|
|
|
this.TraceTokenValidationSuccess(token);
|
|
|
|
List<ClaimsIdentity> identitites = new List<ClaimsIdentity>(1);
|
|
identitites.AddRange(sessionToken.ClaimsPrincipal.Identities);
|
|
return identitites.AsReadOnly();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (Fx.IsFatal(e))
|
|
{
|
|
throw;
|
|
}
|
|
|
|
this.TraceTokenValidationFailure(token, e.Message);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Validates a token and returns its claims.
|
|
/// </summary>
|
|
/// <param name="token">The <see cref="SessionSecurityToken"/> to validate.</param>
|
|
/// <param name="endpointId">Identifier to the endpoint to which the token is scoped.</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="ArgumentNullException">The parameter 'endpointId' is null.</exception>
|
|
/// <exception cref="SecurityTokenException">token.EndpointId != endpointId.</exception>
|
|
public virtual ReadOnlyCollection<ClaimsIdentity> ValidateToken(SessionSecurityToken token, string endpointId)
|
|
{
|
|
if (token == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("token");
|
|
}
|
|
|
|
if (endpointId == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("endpointId");
|
|
}
|
|
|
|
// We consider SessionTokens with String.Empty as the endpoint Id to be
|
|
// globally scoped tokens. This in insecure, we are allowing this only
|
|
// for compatibility with customers who have overriden SessionSecurityTokenHandler.
|
|
if (!string.IsNullOrEmpty(token.EndpointId))
|
|
{
|
|
if (token.EndpointId != endpointId)
|
|
{
|
|
string errorMessage = SR.GetString(SR.ID4291, token);
|
|
this.TraceTokenValidationFailure(token, errorMessage);
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenException(errorMessage));
|
|
}
|
|
}
|
|
|
|
return this.ValidateToken(token);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks the valid time of a SecurityToken.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The token is invalid if the securityToken.ValidFrom > DateTime.UtcNow OR securityToken.ValidTo < DateTime.UtcNow
|
|
/// </remarks>
|
|
/// <param name="token">The <see cref="SessionSecurityToken"/> to validate.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'securityToken' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">Thrown if 'Configuration' is null.</exception>
|
|
/// <exception cref="SecurityTokenNotYetValidException">Thrown if securityToken.ValidFrom > DateTime.UtcNow.</exception>
|
|
/// <exception cref="SecurityTokenExpiredException">Thrown if securityToken.ValidTo < DateTime.UtcNow.</exception>
|
|
protected virtual void ValidateSession(SessionSecurityToken securityToken)
|
|
{
|
|
if (securityToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityToken");
|
|
}
|
|
|
|
if (this.Configuration == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4274)));
|
|
}
|
|
|
|
Fx.Assert(this.Configuration != null, SR.GetString(SR.ID8027));
|
|
|
|
DateTime utcNow = DateTime.UtcNow;
|
|
|
|
// apply clock skew here.
|
|
DateTime maxTime = DateTimeUtil.Add(utcNow, Configuration.MaxClockSkew);
|
|
DateTime minTime = DateTimeUtil.Add(utcNow, -Configuration.MaxClockSkew);
|
|
|
|
if (securityToken.ValidFrom > maxTime)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenNotYetValidException(SR.GetString(SR.ID4255, securityToken.ValidTo, securityToken.ValidFrom, utcNow)));
|
|
}
|
|
|
|
if (securityToken.ValidTo < minTime)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenExpiredException(SR.GetString(SR.ID4255, securityToken.ValidTo, securityToken.ValidFrom, utcNow)));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the token into a byte array.
|
|
/// </summary>
|
|
/// <param name="sessionToken">The SessionSecurityToken to write.</param>
|
|
/// <exception cref="ArgumentNullException">Thrown if 'sessiontoken' is null.</exception>
|
|
/// <returns>An encoded byte array.</returns>
|
|
public virtual byte[] WriteToken(SessionSecurityToken sessionToken)
|
|
{
|
|
if (sessionToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("sessionToken");
|
|
}
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
using (XmlWriter writer = XmlWriter.Create(ms))
|
|
{
|
|
WriteToken(writer, sessionToken);
|
|
writer.Flush();
|
|
}
|
|
|
|
return ms.ToArray();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializes the given token to the XmlWriter.
|
|
/// </summary>
|
|
/// <param name="writer">XmlWriter to which the token needs to be serialized</param>
|
|
/// <param name="token">The SecurityToken to be serialized.</param>
|
|
/// <exception cref="ArgumentNullException">The input argument 'writer' is null.</exception>
|
|
/// <exception cref="InvalidOperationException">The input argument 'token' is either null or not of type
|
|
/// SessionSecurityToken.</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");
|
|
}
|
|
|
|
SessionSecurityToken sessionToken = token as SessionSecurityToken;
|
|
if (sessionToken == null)
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4046, token, TokenType)));
|
|
}
|
|
|
|
string ns, elementName, contextIdElementName, instance;
|
|
|
|
if (sessionToken.SecureConversationVersion == WSSecureConversationFeb2005Constants.NamespaceUri)
|
|
{
|
|
ns = WSSecureConversationFeb2005Constants.Namespace;
|
|
elementName = WSSecureConversationFeb2005Constants.ElementNames.Name;
|
|
contextIdElementName = WSSecureConversationFeb2005Constants.ElementNames.Identifier;
|
|
instance = WSSecureConversationFeb2005Constants.ElementNames.Instance;
|
|
}
|
|
else if (sessionToken.SecureConversationVersion == WSSecureConversation13Constants.NamespaceUri)
|
|
{
|
|
ns = WSSecureConversation13Constants.Namespace;
|
|
elementName = WSSecureConversation13Constants.ElementNames.Name;
|
|
contextIdElementName = WSSecureConversation13Constants.ElementNames.Identifier;
|
|
instance = WSSecureConversation13Constants.ElementNames.Instance;
|
|
}
|
|
else
|
|
{
|
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID4050)));
|
|
}
|
|
|
|
XmlDictionaryWriter dicWriter;
|
|
SessionDictionary dictionary = SessionDictionary.Instance;
|
|
|
|
if (writer is XmlDictionaryWriter)
|
|
{
|
|
dicWriter = (XmlDictionaryWriter)writer;
|
|
}
|
|
else
|
|
{
|
|
dicWriter = XmlDictionaryWriter.CreateDictionaryWriter(writer);
|
|
}
|
|
|
|
dicWriter.WriteStartElement(elementName, ns);
|
|
if (sessionToken.Id != null)
|
|
{
|
|
dicWriter.WriteAttributeString(WSUtilityConstants.Attributes.IdAttribute, WSUtilityConstants.NamespaceURI, sessionToken.Id);
|
|
}
|
|
|
|
dicWriter.WriteElementString(contextIdElementName, ns, sessionToken.ContextId.ToString());
|
|
|
|
if (sessionToken.KeyGeneration != null)
|
|
{
|
|
dicWriter.WriteStartElement(instance, ns);
|
|
dicWriter.WriteValue(sessionToken.KeyGeneration);
|
|
dicWriter.WriteEndElement();
|
|
}
|
|
|
|
if (!sessionToken.IsReferenceMode)
|
|
{
|
|
dicWriter.WriteStartElement(CookieElementName, CookieNamespace);
|
|
byte[] cookie;
|
|
|
|
using (MemoryStream ms = new MemoryStream())
|
|
{
|
|
BinaryFormatter formatter = new BinaryFormatter();
|
|
formatter.Serialize(ms, token);
|
|
cookie = ms.ToArray();
|
|
}
|
|
|
|
cookie = ApplyTransforms(cookie, true);
|
|
dicWriter.WriteBase64(cookie, 0, cookie.Length);
|
|
dicWriter.WriteEndElement();
|
|
}
|
|
|
|
dicWriter.WriteEndElement();
|
|
dicWriter.Flush();
|
|
}
|
|
|
|
}
|
|
}
|