1064 lines
44 KiB
C#
1064 lines
44 KiB
C#
|
//-----------------------------------------------------------------------------
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//-----------------------------------------------------------------------------
|
||
|
namespace System.ServiceModel.Security
|
||
|
{
|
||
|
using System.Collections.Generic;
|
||
|
using System.Collections.ObjectModel;
|
||
|
using System.Diagnostics;
|
||
|
using System.Globalization;
|
||
|
using System.IdentityModel.Policy;
|
||
|
using System.IdentityModel.Tokens;
|
||
|
using System.Runtime;
|
||
|
using System.ServiceModel;
|
||
|
using System.ServiceModel.Channels;
|
||
|
using System.ServiceModel.Description;
|
||
|
using System.ServiceModel.Diagnostics;
|
||
|
using System.ServiceModel.Dispatcher;
|
||
|
using System.ServiceModel.Security.Tokens;
|
||
|
using System.Xml;
|
||
|
|
||
|
abstract class NegotiationTokenAuthenticator<T> : CommunicationObjectSecurityTokenAuthenticator, IIssuanceSecurityTokenAuthenticator, ISecurityContextSecurityTokenCacheProvider
|
||
|
where T : NegotiationTokenAuthenticatorState
|
||
|
{
|
||
|
internal const string defaultServerMaxNegotiationLifetimeString = "00:01:00";
|
||
|
internal const string defaultServerIssuedTokenLifetimeString = "10:00:00";
|
||
|
internal const string defaultServerIssuedTransitionTokenLifetimeString = "00:15:00";
|
||
|
internal const int defaultServerMaxActiveNegotiations = 128;
|
||
|
internal static readonly TimeSpan defaultServerMaxNegotiationLifetime = TimeSpan.Parse(defaultServerMaxNegotiationLifetimeString, CultureInfo.InvariantCulture);
|
||
|
internal static readonly TimeSpan defaultServerIssuedTokenLifetime = TimeSpan.Parse(defaultServerIssuedTokenLifetimeString, CultureInfo.InvariantCulture);
|
||
|
internal static readonly TimeSpan defaultServerIssuedTransitionTokenLifetime = TimeSpan.Parse(defaultServerIssuedTransitionTokenLifetimeString, CultureInfo.InvariantCulture);
|
||
|
internal const int defaultServerMaxCachedTokens = 1000;
|
||
|
internal const bool defaultServerMaintainState = true;
|
||
|
internal static readonly SecurityStandardsManager defaultStandardsManager = SecurityStandardsManager.DefaultInstance;
|
||
|
internal static readonly SecurityStateEncoder defaultSecurityStateEncoder = new DataProtectionSecurityStateEncoder();
|
||
|
|
||
|
NegotiationTokenAuthenticatorStateCache<T> stateCache;
|
||
|
RenewedSecurityTokenHandler renewedSecurityTokenHandler;
|
||
|
NegotiationHost negotiationHost;
|
||
|
bool encryptStateInServiceToken;
|
||
|
TimeSpan serviceTokenLifetime;
|
||
|
int maximumCachedNegotiationState;
|
||
|
TimeSpan negotiationTimeout;
|
||
|
bool isClientAnonymous;
|
||
|
SecurityStandardsManager standardsManager;
|
||
|
SecurityAlgorithmSuite securityAlgorithmSuite;
|
||
|
SecurityTokenParameters issuedSecurityTokenParameters;
|
||
|
ISecurityContextSecurityTokenCache issuedTokenCache;
|
||
|
BindingContext issuerBindingContext;
|
||
|
Uri listenUri;
|
||
|
string sctUri;
|
||
|
AuditLogLocation auditLogLocation;
|
||
|
bool suppressAuditFailure;
|
||
|
AuditLevel messageAuthenticationAuditLevel;
|
||
|
SecurityStateEncoder securityStateEncoder;
|
||
|
SecurityContextCookieSerializer cookieSerializer;
|
||
|
IMessageFilterTable<EndpointAddress> endpointFilterTable;
|
||
|
IssuedSecurityTokenHandler issuedSecurityTokenHandler;
|
||
|
int maxMessageSize;
|
||
|
IList<Type> knownTypes;
|
||
|
int maximumConcurrentNegotiations;
|
||
|
List<IChannel> activeNegotiationChannels1;
|
||
|
List<IChannel> activeNegotiationChannels2;
|
||
|
IOThreadTimer idlingNegotiationSessionTimer;
|
||
|
bool isTimerCancelled;
|
||
|
|
||
|
protected NegotiationTokenAuthenticator() : base()
|
||
|
{
|
||
|
InitializeDefaults();
|
||
|
}
|
||
|
|
||
|
public IssuedSecurityTokenHandler IssuedSecurityTokenHandler
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.issuedSecurityTokenHandler;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.issuedSecurityTokenHandler = value;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public RenewedSecurityTokenHandler RenewedSecurityTokenHandler
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.renewedSecurityTokenHandler;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.renewedSecurityTokenHandler = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// settings
|
||
|
public bool EncryptStateInServiceToken
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.encryptStateInServiceToken;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.encryptStateInServiceToken = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public TimeSpan ServiceTokenLifetime
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.serviceTokenLifetime;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value <= TimeSpan.Zero)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", SR.GetString(SR.TimeSpanMustbeGreaterThanTimeSpanZero)));
|
||
|
}
|
||
|
|
||
|
if (TimeoutHelper.IsTooLarge(value))
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", value,
|
||
|
SR.GetString(SR.SFxTimeoutOutOfRangeTooBig)));
|
||
|
}
|
||
|
this.serviceTokenLifetime = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int MaximumCachedNegotiationState
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.maximumCachedNegotiationState;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value < 0)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", SR.GetString(SR.ValueMustBeNonNegative)));
|
||
|
}
|
||
|
this.maximumCachedNegotiationState = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int MaximumConcurrentNegotiations
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.maximumConcurrentNegotiations;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value < 0)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", SR.GetString(SR.ValueMustBeNonNegative)));
|
||
|
}
|
||
|
this.maximumConcurrentNegotiations = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public TimeSpan NegotiationTimeout
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.negotiationTimeout;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value <= TimeSpan.Zero)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", SR.GetString(SR.TimeSpanMustbeGreaterThanTimeSpanZero)));
|
||
|
}
|
||
|
|
||
|
if (TimeoutHelper.IsTooLarge(value))
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException("value", value,
|
||
|
SR.GetString(SR.SFxTimeoutOutOfRangeTooBig)));
|
||
|
}
|
||
|
this.negotiationTimeout = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsClientAnonymous
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.isClientAnonymous;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.isClientAnonymous = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SecurityAlgorithmSuite SecurityAlgorithmSuite
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.securityAlgorithmSuite;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.securityAlgorithmSuite = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IMessageFilterTable<EndpointAddress> EndpointFilterTable
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.endpointFilterTable;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.endpointFilterTable = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ISecurityContextSecurityTokenCache ISecurityContextSecurityTokenCacheProvider.TokenCache
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.IssuedTokenCache;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual XmlDictionaryString RequestSecurityTokenAction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.StandardsManager.TrustDriver.RequestSecurityTokenAction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual XmlDictionaryString RequestSecurityTokenResponseAction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.StandardsManager.TrustDriver.RequestSecurityTokenResponseAction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public virtual XmlDictionaryString RequestSecurityTokenResponseFinalAction
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.StandardsManager.TrustDriver.RequestSecurityTokenResponseFinalAction;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SecurityStandardsManager StandardsManager
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.standardsManager;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.standardsManager = (value != null ? value : SecurityStandardsManager.DefaultInstance);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SecurityTokenParameters IssuedSecurityTokenParameters
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.issuedSecurityTokenParameters;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.issuedSecurityTokenParameters = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public ISecurityContextSecurityTokenCache IssuedTokenCache
|
||
|
{
|
||
|
get { return this.issuedTokenCache; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.issuedTokenCache = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public AuditLogLocation AuditLogLocation
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.auditLogLocation;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.auditLogLocation = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool SuppressAuditFailure
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.suppressAuditFailure;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.suppressAuditFailure = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public AuditLevel MessageAuthenticationAuditLevel
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.messageAuthenticationAuditLevel;
|
||
|
}
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.messageAuthenticationAuditLevel = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public BindingContext IssuerBindingContext
|
||
|
{
|
||
|
get { return this.issuerBindingContext; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("value");
|
||
|
}
|
||
|
this.issuerBindingContext = value.Clone();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public Uri ListenUri
|
||
|
{
|
||
|
get { return this.listenUri; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.listenUri = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public SecurityStateEncoder SecurityStateEncoder
|
||
|
{
|
||
|
get { return this.securityStateEncoder; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.securityStateEncoder = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IList<Type> KnownTypes
|
||
|
{
|
||
|
get { return this.knownTypes; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
if (value != null)
|
||
|
{
|
||
|
this.knownTypes = new Collection<Type>(value);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.knownTypes = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public int MaxMessageSize
|
||
|
{
|
||
|
get { return this.maxMessageSize; }
|
||
|
set
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfDisposedOrImmutable();
|
||
|
this.maxMessageSize = value;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected string SecurityContextTokenUri
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfNotOpened();
|
||
|
return this.sctUri;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Object ThisLock
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return this.CommunicationObject;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// helpers
|
||
|
protected SecurityContextSecurityToken IssueSecurityContextToken(UniqueId contextId, string id, byte[] key,
|
||
|
DateTime tokenEffectiveTime, DateTime tokenExpirationTime,
|
||
|
ReadOnlyCollection<IAuthorizationPolicy> authorizationPolicies, bool isCookieMode)
|
||
|
{
|
||
|
return IssueSecurityContextToken(contextId, id, key, tokenEffectiveTime, tokenExpirationTime, null,
|
||
|
tokenEffectiveTime, tokenExpirationTime, authorizationPolicies, isCookieMode);
|
||
|
}
|
||
|
|
||
|
protected SecurityContextSecurityToken IssueSecurityContextToken(UniqueId contextId, string id, byte[] key,
|
||
|
DateTime tokenEffectiveTime, DateTime tokenExpirationTime, UniqueId keyGeneration, DateTime keyEffectiveTime,
|
||
|
DateTime keyExpirationTime, ReadOnlyCollection<IAuthorizationPolicy> authorizationPolicies, bool isCookieMode)
|
||
|
{
|
||
|
this.CommunicationObject.ThrowIfClosedOrNotOpen();
|
||
|
if (this.securityStateEncoder == null && isCookieMode)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SctCookieNotSupported)));
|
||
|
}
|
||
|
byte[] cookieBlob = (isCookieMode) ? this.cookieSerializer.CreateCookieFromSecurityContext(contextId, id, key, tokenEffectiveTime, tokenExpirationTime, keyGeneration,
|
||
|
keyEffectiveTime, keyExpirationTime, authorizationPolicies) : null;
|
||
|
|
||
|
SecurityContextSecurityToken issuedToken = new SecurityContextSecurityToken(contextId, id, key, tokenEffectiveTime, tokenExpirationTime,
|
||
|
authorizationPolicies, isCookieMode, cookieBlob, keyGeneration, keyEffectiveTime, keyExpirationTime);
|
||
|
return issuedToken;
|
||
|
}
|
||
|
|
||
|
void InitializeDefaults()
|
||
|
{
|
||
|
this.encryptStateInServiceToken = !defaultServerMaintainState;
|
||
|
this.serviceTokenLifetime = defaultServerIssuedTokenLifetime;
|
||
|
this.maximumCachedNegotiationState = defaultServerMaxActiveNegotiations;
|
||
|
this.negotiationTimeout = defaultServerMaxNegotiationLifetime;
|
||
|
this.isClientAnonymous = false;
|
||
|
this.standardsManager = defaultStandardsManager;
|
||
|
this.securityStateEncoder = defaultSecurityStateEncoder;
|
||
|
this.maximumConcurrentNegotiations = defaultServerMaxActiveNegotiations;
|
||
|
// we rely on the transport encoders to enforce the message size except in the
|
||
|
// mixed mode nego case, where the client is unauthenticated and the maxMessageSize is too
|
||
|
// large to be a mitigation
|
||
|
this.maxMessageSize = Int32.MaxValue;
|
||
|
}
|
||
|
|
||
|
public override void OnClose(TimeSpan timeout)
|
||
|
{
|
||
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
||
|
if (this.negotiationHost != null)
|
||
|
{
|
||
|
this.negotiationHost.Close(timeoutHelper.RemainingTime());
|
||
|
this.negotiationHost = null;
|
||
|
}
|
||
|
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.idlingNegotiationSessionTimer != null && !this.isTimerCancelled)
|
||
|
{
|
||
|
this.isTimerCancelled = true;
|
||
|
this.idlingNegotiationSessionTimer.Cancel();
|
||
|
}
|
||
|
}
|
||
|
base.OnClose(timeoutHelper.RemainingTime());
|
||
|
}
|
||
|
|
||
|
public override void OnAbort()
|
||
|
{
|
||
|
if (this.negotiationHost != null)
|
||
|
{
|
||
|
this.negotiationHost.Abort();
|
||
|
this.negotiationHost = null;
|
||
|
}
|
||
|
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.idlingNegotiationSessionTimer != null && !this.isTimerCancelled)
|
||
|
{
|
||
|
this.isTimerCancelled = true;
|
||
|
this.idlingNegotiationSessionTimer.Cancel();
|
||
|
}
|
||
|
}
|
||
|
base.OnAbort();
|
||
|
}
|
||
|
|
||
|
public override void OnOpen(TimeSpan timeout)
|
||
|
{
|
||
|
if (this.IssuerBindingContext == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.IssuerBuildContextNotSet, this.GetType())));
|
||
|
}
|
||
|
if (this.IssuedSecurityTokenParameters == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.IssuedSecurityTokenParametersNotSet, this.GetType())));
|
||
|
}
|
||
|
if (this.SecurityAlgorithmSuite == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.SecurityAlgorithmSuiteNotSet, this.GetType())));
|
||
|
}
|
||
|
if (this.IssuedTokenCache == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.IssuedTokenCacheNotSet, this.GetType())));
|
||
|
}
|
||
|
|
||
|
TimeoutHelper timeoutHelper = new TimeoutHelper(timeout);
|
||
|
this.SetupServiceHost();
|
||
|
negotiationHost.Open(timeoutHelper.RemainingTime());
|
||
|
this.stateCache = new NegotiationTokenAuthenticatorStateCache<T>(this.NegotiationTimeout, this.MaximumCachedNegotiationState);
|
||
|
this.sctUri = this.StandardsManager.SecureConversationDriver.TokenTypeUri;
|
||
|
if (this.SecurityStateEncoder != null)
|
||
|
{
|
||
|
this.cookieSerializer = new SecurityContextCookieSerializer(this.SecurityStateEncoder, this.KnownTypes);
|
||
|
}
|
||
|
if (this.negotiationTimeout < TimeSpan.MaxValue)
|
||
|
{
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
this.activeNegotiationChannels1 = new List<IChannel>();
|
||
|
this.activeNegotiationChannels2 = new List<IChannel>();
|
||
|
this.idlingNegotiationSessionTimer = new IOThreadTimer(new Action<object>(this.OnIdlingNegotiationSessionTimer), this, false);
|
||
|
this.isTimerCancelled = false;
|
||
|
this.idlingNegotiationSessionTimer.Set(this.negotiationTimeout);
|
||
|
}
|
||
|
}
|
||
|
base.OnOpen(timeoutHelper.RemainingTime());
|
||
|
}
|
||
|
|
||
|
protected override bool CanValidateTokenCore(SecurityToken token)
|
||
|
{
|
||
|
return (token is SecurityContextSecurityToken);
|
||
|
}
|
||
|
|
||
|
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
|
||
|
{
|
||
|
SecurityContextSecurityToken sct = (SecurityContextSecurityToken)token;
|
||
|
return sct.AuthorizationPolicies;
|
||
|
}
|
||
|
|
||
|
protected abstract Binding GetNegotiationBinding(Binding binding);
|
||
|
protected abstract bool IsMultiLegNegotiation { get; }
|
||
|
protected abstract MessageFilter GetListenerFilter();
|
||
|
|
||
|
void SetupServiceHost()
|
||
|
{
|
||
|
ChannelBuilder channelBuilder = new ChannelBuilder(this.IssuerBindingContext.Clone(), true);
|
||
|
channelBuilder.Binding.Elements.Insert(0, new ReplyAdapterBindingElement());
|
||
|
channelBuilder.Binding = new CustomBinding(this.GetNegotiationBinding(channelBuilder.Binding));
|
||
|
negotiationHost = new NegotiationHost(this, this.ListenUri, channelBuilder, this.GetListenerFilter());
|
||
|
}
|
||
|
|
||
|
|
||
|
// message processing abstract method
|
||
|
protected abstract BodyWriter ProcessRequestSecurityToken(Message request, RequestSecurityToken requestSecurityToken, out T negotiationState);
|
||
|
protected abstract BodyWriter ProcessRequestSecurityTokenResponse(T negotiationState, Message request, RequestSecurityTokenResponse requestSecurityTokenResponse);
|
||
|
|
||
|
// message handlers
|
||
|
protected virtual void ParseMessageBody(Message message, out string context, out RequestSecurityToken requestSecurityToken, out RequestSecurityTokenResponse requestSecurityTokenResponse)
|
||
|
{
|
||
|
requestSecurityToken = null;
|
||
|
requestSecurityTokenResponse = null;
|
||
|
if (message.Headers.Action == this.RequestSecurityTokenAction.Value)
|
||
|
{
|
||
|
XmlDictionaryReader reader = message.GetReaderAtBodyContents();
|
||
|
using (reader)
|
||
|
{
|
||
|
requestSecurityToken = RequestSecurityToken.CreateFrom(this.StandardsManager, reader);
|
||
|
message.ReadFromBodyContentsToEnd(reader);
|
||
|
}
|
||
|
context = requestSecurityToken.Context;
|
||
|
}
|
||
|
else if (message.Headers.Action == this.RequestSecurityTokenResponseAction.Value)
|
||
|
{
|
||
|
XmlDictionaryReader reader = message.GetReaderAtBodyContents();
|
||
|
using (reader)
|
||
|
{
|
||
|
requestSecurityTokenResponse = RequestSecurityTokenResponse.CreateFrom(this.StandardsManager, reader);
|
||
|
message.ReadFromBodyContentsToEnd(reader);
|
||
|
}
|
||
|
context = requestSecurityTokenResponse.Context;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
throw TraceUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.InvalidActionForNegotiationMessage, message.Headers.Action)), message);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static Message CreateReply(Message request, XmlDictionaryString action, BodyWriter body)
|
||
|
{
|
||
|
if (request.Headers.MessageId != null)
|
||
|
{
|
||
|
Message reply = Message.CreateMessage(request.Version, ActionHeader.Create(action, request.Version.Addressing), body);
|
||
|
reply.InitializeReply(request);
|
||
|
return reply;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// the message id may not be present if MapToHttp is true
|
||
|
return Message.CreateMessage(request.Version, ActionHeader.Create(action, request.Version.Addressing), body);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnTokenIssued(SecurityToken token)
|
||
|
{
|
||
|
if (this.issuedSecurityTokenHandler != null)
|
||
|
{
|
||
|
this.issuedSecurityTokenHandler(token, null);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void AddNegotiationChannelForIdleTracking()
|
||
|
{
|
||
|
if (OperationContext.Current.SessionId == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.idlingNegotiationSessionTimer == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
IChannel channel = OperationContext.Current.Channel;
|
||
|
if (!this.activeNegotiationChannels1.Contains(channel) && !this.activeNegotiationChannels2.Contains(channel))
|
||
|
{
|
||
|
this.activeNegotiationChannels1.Add(channel);
|
||
|
}
|
||
|
if (this.isTimerCancelled)
|
||
|
{
|
||
|
this.isTimerCancelled = false;
|
||
|
this.idlingNegotiationSessionTimer.Set(this.negotiationTimeout);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void RemoveNegotiationChannelFromIdleTracking()
|
||
|
{
|
||
|
if (OperationContext.Current.SessionId == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.idlingNegotiationSessionTimer == null)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
IChannel channel = OperationContext.Current.Channel;
|
||
|
this.activeNegotiationChannels1.Remove(channel);
|
||
|
this.activeNegotiationChannels2.Remove(channel);
|
||
|
if (this.activeNegotiationChannels1.Count == 0 && this.activeNegotiationChannels2.Count == 0)
|
||
|
{
|
||
|
this.isTimerCancelled = true;
|
||
|
this.idlingNegotiationSessionTimer.Cancel();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void OnIdlingNegotiationSessionTimer(object state)
|
||
|
{
|
||
|
lock (ThisLock)
|
||
|
{
|
||
|
if (this.isTimerCancelled || (this.CommunicationObject.State != CommunicationState.Opened && this.CommunicationObject.State != CommunicationState.Opening))
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
for (int i = 0; i < this.activeNegotiationChannels2.Count; ++i)
|
||
|
{
|
||
|
this.activeNegotiationChannels2[i].Abort();
|
||
|
}
|
||
|
List<IChannel> temp = this.activeNegotiationChannels2;
|
||
|
temp.Clear();
|
||
|
this.activeNegotiationChannels2 = this.activeNegotiationChannels1;
|
||
|
this.activeNegotiationChannels1 = temp;
|
||
|
}
|
||
|
#pragma warning suppress 56500
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (Fx.IsFatal(e))
|
||
|
{
|
||
|
throw;
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (this.CommunicationObject.State == CommunicationState.Opened || this.CommunicationObject.State == CommunicationState.Opening)
|
||
|
{
|
||
|
if (this.activeNegotiationChannels1.Count == 0 && this.activeNegotiationChannels2.Count == 0)
|
||
|
{
|
||
|
this.isTimerCancelled = true;
|
||
|
this.idlingNegotiationSessionTimer.Cancel();
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.idlingNegotiationSessionTimer.Set(this.negotiationTimeout);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Message ProcessRequestCore(Message request)
|
||
|
{
|
||
|
if (request == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("request");
|
||
|
}
|
||
|
Uri to = null;
|
||
|
RequestSecurityToken rst = null;
|
||
|
RequestSecurityTokenResponse rstr = null;
|
||
|
string context = null;
|
||
|
bool disposeRequest = false;
|
||
|
bool isNegotiationFailure = true;
|
||
|
T negotiationState = null;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
// validate the message size if needed
|
||
|
if (this.maxMessageSize < int.MaxValue)
|
||
|
{
|
||
|
string action = request.Headers.Action;
|
||
|
try
|
||
|
{
|
||
|
using (MessageBuffer buffer = request.CreateBufferedCopy(this.maxMessageSize))
|
||
|
{
|
||
|
request = buffer.CreateMessage();
|
||
|
disposeRequest = true;
|
||
|
}
|
||
|
}
|
||
|
catch (QuotaExceededException e)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityNegotiationException(SR.GetString(SR.SecurityNegotiationMessageTooLarge, action, this.maxMessageSize), e));
|
||
|
}
|
||
|
}
|
||
|
try
|
||
|
{
|
||
|
to = request.Headers.To;
|
||
|
ParseMessageBody(request, out context, out rst, out rstr);
|
||
|
// check if there is existing state
|
||
|
if (context != null)
|
||
|
{
|
||
|
negotiationState = this.stateCache.GetState(context);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
negotiationState = null;
|
||
|
}
|
||
|
bool disposeState = false;
|
||
|
BodyWriter replyBody;
|
||
|
try
|
||
|
{
|
||
|
if (rst != null)
|
||
|
{
|
||
|
if (negotiationState != null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new SecurityNegotiationException(SR.GetString(SR.NegotiationStateAlreadyPresent, context)));
|
||
|
}
|
||
|
replyBody = this.ProcessRequestSecurityToken(request, rst, out negotiationState);
|
||
|
lock (negotiationState.ThisLock)
|
||
|
{
|
||
|
if (negotiationState.IsNegotiationCompleted)
|
||
|
{
|
||
|
// if session-sct add it to cache and add a redirect header
|
||
|
if (!negotiationState.ServiceToken.IsCookieMode)
|
||
|
{
|
||
|
this.IssuedTokenCache.AddContext(negotiationState.ServiceToken);
|
||
|
}
|
||
|
this.OnTokenIssued(negotiationState.ServiceToken);
|
||
|
SecurityTraceRecordHelper.TraceServiceSecurityNegotiationCompleted(request, this, negotiationState.ServiceToken);
|
||
|
disposeState = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.stateCache.AddState(context, negotiationState);
|
||
|
disposeState = false;
|
||
|
}
|
||
|
AddNegotiationChannelForIdleTracking();
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (negotiationState == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new SecurityNegotiationException(SR.GetString(SR.CannotFindNegotiationState, context)));
|
||
|
}
|
||
|
lock (negotiationState.ThisLock)
|
||
|
{
|
||
|
replyBody = this.ProcessRequestSecurityTokenResponse(negotiationState, request, rstr);
|
||
|
if (negotiationState.IsNegotiationCompleted)
|
||
|
{
|
||
|
// if session-sct add it to cache and add a redirect header
|
||
|
if (!negotiationState.ServiceToken.IsCookieMode)
|
||
|
{
|
||
|
this.IssuedTokenCache.AddContext(negotiationState.ServiceToken);
|
||
|
}
|
||
|
this.OnTokenIssued(negotiationState.ServiceToken);
|
||
|
SecurityTraceRecordHelper.TraceServiceSecurityNegotiationCompleted(request, this, negotiationState.ServiceToken);
|
||
|
disposeState = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
disposeState = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (negotiationState.IsNegotiationCompleted && null != this.ListenUri)
|
||
|
{
|
||
|
if (AuditLevel.Success == (this.messageAuthenticationAuditLevel & AuditLevel.Success))
|
||
|
{
|
||
|
string primaryIdentity = negotiationState.GetRemoteIdentityName();
|
||
|
SecurityAuditHelper.WriteSecurityNegotiationSuccessEvent(this.auditLogLocation,
|
||
|
this.suppressAuditFailure, request, request.Headers.To, request.Headers.Action,
|
||
|
primaryIdentity, this.GetType().Name);
|
||
|
}
|
||
|
}
|
||
|
isNegotiationFailure = false;
|
||
|
}
|
||
|
catch (Exception exception)
|
||
|
{
|
||
|
if (Fx.IsFatal(exception))
|
||
|
throw;
|
||
|
|
||
|
if (PerformanceCounters.PerformanceCountersEnabled && null != this.ListenUri)
|
||
|
{
|
||
|
PerformanceCounters.AuthenticationFailed(request, this.ListenUri);
|
||
|
}
|
||
|
if (AuditLevel.Failure == (this.messageAuthenticationAuditLevel & AuditLevel.Failure))
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
string primaryIdentity = (negotiationState != null) ? negotiationState.GetRemoteIdentityName() : String.Empty;
|
||
|
SecurityAuditHelper.WriteSecurityNegotiationFailureEvent(this.auditLogLocation,
|
||
|
this.suppressAuditFailure, request, request.Headers.To, request.Headers.Action,
|
||
|
primaryIdentity, this.GetType().Name, exception);
|
||
|
}
|
||
|
#pragma warning suppress 56500
|
||
|
catch (Exception auditException)
|
||
|
{
|
||
|
if (Fx.IsFatal(auditException))
|
||
|
throw;
|
||
|
|
||
|
DiagnosticUtility.TraceHandledException(auditException, TraceEventType.Error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
disposeState = true;
|
||
|
throw;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (disposeState)
|
||
|
{
|
||
|
if (negotiationState != null)
|
||
|
{
|
||
|
if (context != null)
|
||
|
{
|
||
|
stateCache.RemoveState(context);
|
||
|
}
|
||
|
negotiationState.Dispose();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return CreateReply(request, (replyBody is RequestSecurityTokenResponseCollection) ? RequestSecurityTokenResponseFinalAction : RequestSecurityTokenResponseAction, replyBody);
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (disposeRequest)
|
||
|
{
|
||
|
request.Close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (isNegotiationFailure)
|
||
|
{
|
||
|
AddNegotiationChannelForIdleTracking();
|
||
|
}
|
||
|
else if (negotiationState != null && negotiationState.IsNegotiationCompleted)
|
||
|
{
|
||
|
RemoveNegotiationChannelFromIdleTracking();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// negotiation failure methods
|
||
|
Message HandleNegotiationException(Message request, Exception e)
|
||
|
{
|
||
|
|
||
|
SecurityTraceRecordHelper.TraceServiceSecurityNegotiationFailure<T>(
|
||
|
EventTraceActivityHelper.TryExtractActivity(request),
|
||
|
this,
|
||
|
e);
|
||
|
return CreateFault(request, e);
|
||
|
}
|
||
|
|
||
|
Message CreateFault(Message request, Exception e)
|
||
|
{
|
||
|
MessageVersion version = request.Version;
|
||
|
FaultCode subCode;
|
||
|
FaultReason reason;
|
||
|
bool isSenderFault;
|
||
|
if (e is SecurityTokenValidationException || e is System.ComponentModel.Win32Exception)
|
||
|
{
|
||
|
subCode = new FaultCode(TrustApr2004Strings.FailedAuthenticationFaultCode, TrustFeb2005Strings.Namespace);
|
||
|
reason = new FaultReason(SR.GetString(SR.FailedAuthenticationTrustFaultCode), CultureInfo.CurrentCulture);
|
||
|
isSenderFault = true;
|
||
|
}
|
||
|
else if (e is QuotaExceededException)
|
||
|
{
|
||
|
// send a receiver fault so that the sender can retry
|
||
|
subCode = new FaultCode(DotNetSecurityStrings.SecurityServerTooBusyFault, DotNetSecurityStrings.Namespace);
|
||
|
reason = new FaultReason(SR.GetString(SR.NegotiationQuotasExceededFaultReason), CultureInfo.CurrentCulture);
|
||
|
isSenderFault = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
subCode = new FaultCode(TrustApr2004Strings.InvalidRequestFaultCode, TrustFeb2005Strings.Namespace);
|
||
|
reason = new FaultReason(SR.GetString(SR.InvalidRequestTrustFaultCode), CultureInfo.CurrentCulture);
|
||
|
isSenderFault = true;
|
||
|
}
|
||
|
FaultCode faultCode;
|
||
|
if (isSenderFault)
|
||
|
{
|
||
|
faultCode = FaultCode.CreateSenderFaultCode(subCode);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
faultCode = FaultCode.CreateReceiverFaultCode(subCode);
|
||
|
}
|
||
|
MessageFault fault = MessageFault.CreateFault(faultCode, reason);
|
||
|
Message faultReply = Message.CreateMessage(version, fault, version.Addressing.DefaultFaultAction);
|
||
|
faultReply.Headers.RelatesTo = request.Headers.MessageId;
|
||
|
|
||
|
return faultReply;
|
||
|
}
|
||
|
|
||
|
class NegotiationHost : ServiceHostBase
|
||
|
{
|
||
|
NegotiationTokenAuthenticator<T> authenticator;
|
||
|
Uri listenUri;
|
||
|
ChannelBuilder channelBuilder;
|
||
|
MessageFilter listenerFilter;
|
||
|
|
||
|
public NegotiationHost(NegotiationTokenAuthenticator<T> authenticator, Uri listenUri, ChannelBuilder channelBuilder, MessageFilter listenerFilter)
|
||
|
{
|
||
|
this.authenticator = authenticator;
|
||
|
this.listenUri = listenUri;
|
||
|
this.channelBuilder = channelBuilder;
|
||
|
this.listenerFilter = listenerFilter;
|
||
|
}
|
||
|
|
||
|
protected override ServiceDescription CreateDescription(out IDictionary<string, ContractDescription> implementedContracts)
|
||
|
{
|
||
|
implementedContracts = null;
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
protected override void InitializeRuntime()
|
||
|
{
|
||
|
MessageFilter contractFilter = this.listenerFilter;
|
||
|
int filterPriority = 10;
|
||
|
Type[] endpointChannelTypes = new Type[] { typeof(IReplyChannel),
|
||
|
typeof(IDuplexChannel),
|
||
|
typeof(IReplySessionChannel),
|
||
|
typeof(IDuplexSessionChannel) };
|
||
|
IChannelListener listener = null;
|
||
|
BindingParameterCollection parameters = new BindingParameterCollection(this.channelBuilder.BindingParameters);
|
||
|
Binding binding = this.channelBuilder.Binding;
|
||
|
binding.ReceiveTimeout = this.authenticator.NegotiationTimeout;
|
||
|
parameters.Add(new ChannelDemuxerFilter(contractFilter, filterPriority));
|
||
|
DispatcherBuilder.MaybeCreateListener(true, endpointChannelTypes, binding, parameters,
|
||
|
this.listenUri, "", ListenUriMode.Explicit, this.ServiceThrottle, out listener);
|
||
|
if (listener == null)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.CannotCreateTwoWayListenerForNegotiation)));
|
||
|
}
|
||
|
ChannelDispatcher channelDispatcher = new ChannelDispatcher(listener, null, binding);
|
||
|
channelDispatcher.MessageVersion = binding.MessageVersion;
|
||
|
channelDispatcher.ManualAddressing = true;
|
||
|
channelDispatcher.ServiceThrottle = new ServiceThrottle(this);
|
||
|
channelDispatcher.ServiceThrottle.MaxConcurrentCalls = this.authenticator.MaximumConcurrentNegotiations;
|
||
|
channelDispatcher.ServiceThrottle.MaxConcurrentSessions = this.authenticator.MaximumConcurrentNegotiations;
|
||
|
|
||
|
EndpointDispatcher endpointDispatcher = new EndpointDispatcher(new EndpointAddress(this.listenUri), "SecurityNegotiationContract", NamingHelper.DefaultNamespace, true);
|
||
|
endpointDispatcher.DispatchRuntime.SingletonInstanceContext = new InstanceContext(null, this.authenticator, false);
|
||
|
endpointDispatcher.DispatchRuntime.ConcurrencyMode = ConcurrencyMode.Multiple;
|
||
|
endpointDispatcher.AddressFilter = new MatchAllMessageFilter();
|
||
|
endpointDispatcher.ContractFilter = contractFilter;
|
||
|
endpointDispatcher.FilterPriority = filterPriority;
|
||
|
endpointDispatcher.DispatchRuntime.PrincipalPermissionMode = PrincipalPermissionMode.None;
|
||
|
endpointDispatcher.DispatchRuntime.InstanceContextProvider = new SingletonInstanceContextProvider(endpointDispatcher.DispatchRuntime);
|
||
|
endpointDispatcher.DispatchRuntime.SynchronizationContext = null;
|
||
|
|
||
|
DispatchOperation operation = new DispatchOperation(endpointDispatcher.DispatchRuntime, "*", MessageHeaders.WildcardAction, MessageHeaders.WildcardAction);
|
||
|
operation.Formatter = new MessageOperationFormatter();
|
||
|
operation.Invoker = new NegotiationSyncInvoker(this.authenticator);
|
||
|
|
||
|
endpointDispatcher.DispatchRuntime.UnhandledDispatchOperation = operation;
|
||
|
channelDispatcher.Endpoints.Add(endpointDispatcher);
|
||
|
this.ChannelDispatchers.Add(channelDispatcher);
|
||
|
}
|
||
|
|
||
|
class NegotiationSyncInvoker : IOperationInvoker
|
||
|
{
|
||
|
NegotiationTokenAuthenticator<T> parent;
|
||
|
|
||
|
internal NegotiationSyncInvoker(NegotiationTokenAuthenticator<T> parent)
|
||
|
{
|
||
|
this.parent = parent;
|
||
|
}
|
||
|
|
||
|
public bool IsSynchronous { get { return true; } }
|
||
|
|
||
|
public object[] AllocateInputs()
|
||
|
{
|
||
|
return EmptyArray<object>.Allocate(1);
|
||
|
}
|
||
|
|
||
|
public object Invoke(object instance, object[] inputs, out object[] outputs)
|
||
|
{
|
||
|
Message request = (Message)inputs[0];
|
||
|
outputs = EmptyArray<object>.Allocate(0);
|
||
|
try
|
||
|
{
|
||
|
return parent.ProcessRequestCore(request);
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
if (Fx.IsFatal(e))
|
||
|
{
|
||
|
throw;
|
||
|
}
|
||
|
return parent.HandleNegotiationException(request, e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
||
|
}
|
||
|
|
||
|
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
|
||
|
{
|
||
|
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotSupportedException());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|