//------------------------------------------------------------------------------ // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IdentityModel; using System.IdentityModel.Selectors; using System.IdentityModel.Tokens; using System.ServiceModel.Security.Tokens; using SystemUniqueId = System.Xml.UniqueId; using SR = System.ServiceModel.SR; namespace System.ServiceModel.Security { /// /// The purpose of this class is to provide an ISecurityContextSecurityTokenCache contract over a SecurityTokenCache. /// This allows for a consistent interface for the SecurityContextSecurityTokenHandler and a SessionSecurityTokenHandler. /// The SecurityTokenCache can be passed to the SecurityContextSecurityTokenHandler and wrapped to expose an ISecurityContextSecurityTokenCache /// that can be set to the be the token cache for WCF context tokens /// class WrappedTokenCache : SecurityTokenResolver, ISecurityContextSecurityTokenCache { SessionSecurityTokenCache _tokenCache; SctClaimsHandler _claimsHandler; public WrappedTokenCache(SessionSecurityTokenCache tokenCache, SctClaimsHandler sctClaimsHandler) { if (tokenCache == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("tokenCache"); } if (sctClaimsHandler == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("sctClaimsHandler"); } _tokenCache = tokenCache; _claimsHandler = sctClaimsHandler; } #region ISecurityContextSecurityTokenCache Members public void AddContext(SecurityContextSecurityToken token) { // // WCF will cache the token first before calling the WrappedSessionSecurityTokenHandler.OnTokenIssued. // We need to map the claims here so we will be caching the correct token with Geneva Claims substitued // in place of the WCF claims. // _claimsHandler.SetPrincipalBootstrapTokensAndBindIdfxAuthPolicy(token); SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(_claimsHandler.EndpointId, token.ContextId, token.KeyGeneration); SessionSecurityToken sessionToken = SecurityContextSecurityTokenHelper.ConvertSctToSessionToken(token, SecureConversationVersion.Default); DateTime expiryTime = DateTimeUtil.Add(sessionToken.ValidTo, _claimsHandler.SecurityTokenHandlerCollection.Configuration.MaxClockSkew); _tokenCache.AddOrUpdate(key, sessionToken, expiryTime); } public void ClearContexts() { _tokenCache.RemoveAll(_claimsHandler.EndpointId); } /// /// Called to retrieve all tokens that match a particular contextId. WCF will call this /// /// /// public Collection GetAllContexts(System.Xml.UniqueId contextId) { Collection tokens = new Collection(); IEnumerable cachedTokens = _tokenCache.GetAll(_claimsHandler.EndpointId, contextId); if (cachedTokens != null) { foreach (SessionSecurityToken sessionSct in cachedTokens) { if (sessionSct != null && sessionSct.IsSecurityContextSecurityTokenWrapper) { SecurityContextSecurityToken sctToken = SecurityContextSecurityTokenHelper.ConvertSessionTokenToSecurityContextSecurityToken(sessionSct); tokens.Add(sctToken); } } } return tokens; } public SecurityContextSecurityToken GetContext(System.Xml.UniqueId contextId, System.Xml.UniqueId generation) { SessionSecurityToken token = null; SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(_claimsHandler.EndpointId, contextId, generation); token = _tokenCache.Get(key); SecurityContextSecurityToken sctToken = null; if (token != null && token.IsSecurityContextSecurityTokenWrapper) { sctToken = SecurityContextSecurityTokenHelper.ConvertSessionTokenToSecurityContextSecurityToken(token); } return sctToken; } /// /// Removes all the tokens that match the contextId. /// /// The context id. /// /// When WCF renews a token, its context id is the same as the issuedToken. The only /// difference is in the generationId. When WCF closes the session channel, all the tokens that /// were issued need to be removed that match the contextId. /// public void RemoveAllContexts(System.Xml.UniqueId contextId) { _tokenCache.RemoveAll(_claimsHandler.EndpointId, contextId); } public void RemoveContext(System.Xml.UniqueId contextId, System.Xml.UniqueId generation) { SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(_claimsHandler.EndpointId, contextId, generation); _tokenCache.Remove(key); } public bool TryAddContext(SecurityContextSecurityToken token) { // // WCF will cache the token first before calling the WrappedSessionSecurityTokenHandler.OnTokenIssued. // We need to map the claims here so we will be caching the correct token with Geneva Claims substitued // in place of the WCF claims. // _claimsHandler.SetPrincipalBootstrapTokensAndBindIdfxAuthPolicy(token); SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(_claimsHandler.EndpointId, token.ContextId, token.KeyGeneration); SessionSecurityToken sessionToken = SecurityContextSecurityTokenHelper.ConvertSctToSessionToken(token, SecureConversationVersion.Default); DateTime expiryTime = DateTimeUtil.Add(token.ValidTo, _claimsHandler.SecurityTokenHandlerCollection.Configuration.MaxClockSkew); _tokenCache.AddOrUpdate(key, sessionToken, expiryTime); return true; } public void UpdateContextCachingTime(SecurityContextSecurityToken token, DateTime expirationTime) { if (token.ValidTo <= expirationTime.ToUniversalTime()) { return; } SessionSecurityTokenCacheKey key = new SessionSecurityTokenCacheKey(_claimsHandler.EndpointId, token.ContextId, token.KeyGeneration); SessionSecurityToken sessionToken = SecurityContextSecurityTokenHelper.ConvertSctToSessionToken(token, SecureConversationVersion.Default); DateTime expiryTime = DateTimeUtil.Add(sessionToken.ValidTo, _claimsHandler.SecurityTokenHandlerCollection.Configuration.MaxClockSkew); if (_tokenCache.Get(key) == null) { throw DiagnosticUtility.ExceptionUtility.ThrowHelperInvalidOperation(SR.GetString(SR.ID4285, sessionToken.ContextId.ToString())); } _tokenCache.AddOrUpdate(key, sessionToken, expiryTime); } #endregion // these are not needed as this will never be used as an SecurityTokenResolver. protected override bool TryResolveSecurityKeyCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityKey key) { SecurityToken sct; if (TryResolveTokenCore(keyIdentifierClause, out sct)) { key = ((SecurityContextSecurityToken)sct).SecurityKeys[0]; return true; } else { key = null; return false; } } protected override bool TryResolveTokenCore(SecurityKeyIdentifierClause keyIdentifierClause, out SecurityToken token) { SecurityContextKeyIdentifierClause sctSkiClause = keyIdentifierClause as SecurityContextKeyIdentifierClause; if (sctSkiClause != null) { token = GetContext(sctSkiClause.ContextId, sctSkiClause.Generation) as SecurityToken; } else { token = null; } return (token != null); } protected override bool TryResolveTokenCore(SecurityKeyIdentifier keyIdentifier, out SecurityToken token) { SecurityContextKeyIdentifierClause sctSkiClause; if (keyIdentifier.TryFind(out sctSkiClause)) { return TryResolveTokenCore(sctSkiClause, out token); } else { token = null; return false; } } } }