//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // //------------------------------------------------------------------------------ namespace System.Net { using System.Net.Sockets; using System.Collections; using System.Runtime.InteropServices; using System.Security.Permissions; using System.Globalization; // More sophisticated password cache that stores multiple // name-password pairs and associates these with host/realm /// /// Provides storage for multiple credentials. /// public class CredentialCache : ICredentials, ICredentialsByHost, IEnumerable { // fields private Hashtable cache = new Hashtable(); private Hashtable cacheForHosts = new Hashtable(); internal int m_version; private int m_NumbDefaultCredInCache = 0; // [thread token optimization] The resulting counter of default credential resided in the cache. internal bool IsDefaultInCache { get { return m_NumbDefaultCredInCache != 0; } } // constructors /// /// /// Initializes a new instance of the class. /// /// public CredentialCache() { } // properties // methods /// /// Adds a /// instance to the credential cache. /// // UEUE public void Add(Uri uriPrefix, string authType, NetworkCredential cred) { // // parameter validation // if (uriPrefix==null) { throw new ArgumentNullException("uriPrefix"); } if (authType==null) { throw new ArgumentNullException("authType"); } if ((cred is SystemNetworkCredential) #if !FEATURE_PAL && !((string.Compare(authType, NtlmClient.AuthType, StringComparison.OrdinalIgnoreCase)==0) || (DigestClient.WDigestAvailable && (string.Compare(authType, DigestClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)) || (string.Compare(authType, KerberosClient.AuthType, StringComparison.OrdinalIgnoreCase)==0) || (string.Compare(authType, NegotiateClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)) #endif ) { throw new ArgumentException(SR.GetString(SR.net_nodefaultcreds, authType), "authType"); } ++m_version; CredentialKey key = new CredentialKey(uriPrefix, authType); GlobalLog.Print("CredentialCache::Add() Adding key:[" + key.ToString() + "], cred:[" + cred.Domain + "],[" + cred.UserName + "]"); cache.Add(key, cred); if (cred is SystemNetworkCredential) { ++m_NumbDefaultCredInCache; } } public void Add(string host, int port, string authenticationType, NetworkCredential credential) { // // parameter validation // if (host==null) { throw new ArgumentNullException("host"); } if (authenticationType==null) { throw new ArgumentNullException("authenticationType"); } if (host.Length == 0) { throw new ArgumentException(SR.GetString(SR.net_emptystringcall,"host")); } if (port < 0) { throw new ArgumentOutOfRangeException("port"); } if ((credential is SystemNetworkCredential) #if !FEATURE_PAL && !((string.Compare(authenticationType, NtlmClient.AuthType, StringComparison.OrdinalIgnoreCase)==0) || (DigestClient.WDigestAvailable && (string.Compare(authenticationType, DigestClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)) || (string.Compare(authenticationType, KerberosClient.AuthType, StringComparison.OrdinalIgnoreCase)==0) || (string.Compare(authenticationType, NegotiateClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)) #endif ) { throw new ArgumentException(SR.GetString(SR.net_nodefaultcreds, authenticationType), "authenticationType"); } ++m_version; CredentialHostKey key = new CredentialHostKey(host,port, authenticationType); GlobalLog.Print("CredentialCache::Add() Adding key:[" + key.ToString() + "], cred:[" + credential.Domain + "],[" + credential.UserName + "]"); cacheForHosts.Add(key, credential); if (credential is SystemNetworkCredential) { ++m_NumbDefaultCredInCache; } } /// /// Removes a /// instance from the credential cache. /// public void Remove(Uri uriPrefix, string authType) { if (uriPrefix==null || authType==null) { // these couldn't possibly have been inserted into // the cache because of the test in Add() return; } ++m_version; CredentialKey key = new CredentialKey(uriPrefix, authType); GlobalLog.Print("CredentialCache::Remove() Removing key:[" + key.ToString() + "]"); if (cache[key] is SystemNetworkCredential) { --m_NumbDefaultCredInCache; } cache.Remove(key); } public void Remove(string host, int port, string authenticationType) { if (host==null || authenticationType==null) { // these couldn't possibly have been inserted into // the cache because of the test in Add() return; } if (port < 0) { return; } ++m_version; CredentialHostKey key = new CredentialHostKey(host, port, authenticationType); GlobalLog.Print("CredentialCache::Remove() Removing key:[" + key.ToString() + "]"); if (cacheForHosts[key] is SystemNetworkCredential) { --m_NumbDefaultCredInCache; } cacheForHosts.Remove(key); } /// /// /// Returns the /// instance associated with the supplied Uri and /// authentication type. /// /// public NetworkCredential GetCredential(Uri uriPrefix, string authType) { if (uriPrefix==null) throw new ArgumentNullException("uriPrefix"); if (authType==null) throw new ArgumentNullException("authType"); GlobalLog.Print("CredentialCache::GetCredential(uriPrefix=\"" + uriPrefix + "\", authType=\"" + authType + "\")"); int longestMatchPrefix = -1; NetworkCredential mostSpecificMatch = null; IDictionaryEnumerator credEnum = cache.GetEnumerator(); // // Enumerate through every credential in the cache // while (credEnum.MoveNext()) { CredentialKey key = (CredentialKey)credEnum.Key; // // Determine if this credential is applicable to the current Uri/AuthType // if (key.Match(uriPrefix, authType)) { int prefixLen = key.UriPrefixLength; // // Check if the match is better than the current-most-specific match // if (prefixLen > longestMatchPrefix) { // // Yes-- update the information about currently preferred match // longestMatchPrefix = prefixLen; mostSpecificMatch = (NetworkCredential)credEnum.Value; } } } GlobalLog.Print("CredentialCache::GetCredential returning " + ((mostSpecificMatch==null)?"null":"(" + mostSpecificMatch.UserName + ":" + mostSpecificMatch.Domain + ")")); return mostSpecificMatch; } public NetworkCredential GetCredential(string host, int port, string authenticationType) { if (host==null) { throw new ArgumentNullException("host"); } if (authenticationType==null){ throw new ArgumentNullException("authenticationType"); } if (host.Length == 0) { throw new ArgumentException(SR.GetString(SR.net_emptystringcall, "host")); } if (port < 0) { throw new ArgumentOutOfRangeException("port"); } GlobalLog.Print("CredentialCache::GetCredential(host=\"" + host + ":" + port.ToString() +"\", authenticationType=\"" + authenticationType + "\")"); NetworkCredential match = null; IDictionaryEnumerator credEnum = cacheForHosts.GetEnumerator(); // // Enumerate through every credential in the cache // while (credEnum.MoveNext()) { CredentialHostKey key = (CredentialHostKey)credEnum.Key; // // Determine if this credential is applicable to the current Uri/AuthType // if (key.Match(host, port, authenticationType)) { match = (NetworkCredential)credEnum.Value; } } GlobalLog.Print("CredentialCache::GetCredential returning " + ((match==null)?"null":"(" + match.UserName + ":" + match.Domain + ")")); return match; } /// /// [To be supplied] /// // // IEnumerable interface // public IEnumerator GetEnumerator() { return new CredentialEnumerator(this, cache ,cacheForHosts, m_version); } /// /// /// Gets /// the default system credentials from the . /// /// public static ICredentials DefaultCredentials { get { //This check will not allow to use local user credentials at will. //Hence the username will not be exposed to the network #if !DISABLE_CAS_USE new EnvironmentPermission(EnvironmentPermissionAccess.Read, "USERNAME").Demand(); #endif return SystemNetworkCredential.defaultCredential; } } public static NetworkCredential DefaultNetworkCredentials { get { //This check will not allow to use local user credentials at will. //Hence the username will not be exposed to the network #if !DISABLE_CAS_USE new EnvironmentPermission(EnvironmentPermissionAccess.Read, "USERNAME").Demand(); #endif return SystemNetworkCredential.defaultCredential; } } private class CredentialEnumerator : IEnumerator { // fields private CredentialCache m_cache; private ICredentials[] m_array; private int m_index = -1; private int m_version; // constructors internal CredentialEnumerator(CredentialCache cache, Hashtable table, Hashtable hostTable, int version) { m_cache = cache; m_array = new ICredentials[table.Count + hostTable.Count]; table.Values.CopyTo(m_array, 0); hostTable.Values.CopyTo(m_array, table.Count); m_version = version; } // IEnumerator interface // properties object IEnumerator.Current { get { if (m_index < 0 || m_index >= m_array.Length) { throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumOpCantHappen)); } if (m_version != m_cache.m_version) { throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumFailedVersion)); } return m_array[m_index]; } } // methods bool IEnumerator.MoveNext() { if (m_version != m_cache.m_version) { throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumFailedVersion)); } if (++m_index < m_array.Length) { return true; } m_index = m_array.Length; return false; } void IEnumerator.Reset() { m_index = -1; } } // class CredentialEnumerator } // class CredentialCache // Abstraction for credentials in password-based // authentication schemes (basic, digest, NTLM, Kerberos) // Note this is not applicable to public-key based // systems such as SSL client authentication // "Password" here may be the clear text password or it // could be a one-way hash that is sufficient to // authenticate, as in HTTP/1.1 digest. // // Object representing default credentials // internal class SystemNetworkCredential : NetworkCredential { internal static readonly SystemNetworkCredential defaultCredential = new SystemNetworkCredential(); // We want reference equality to work. Making this private is a good way to guarantee that. private SystemNetworkCredential() : base(string.Empty, string.Empty, string.Empty) { } } internal class CredentialHostKey { internal string Host; internal string AuthenticationType; internal int Port; internal CredentialHostKey(string host, int port, string authenticationType) { Host = host; Port = port; AuthenticationType = authenticationType; } internal bool Match(string host, int port, string authenticationType) { if (host==null || authenticationType==null) { return false; } // // If the protocols dont match this credential // is not applicable for the given Uri // if (string.Compare(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase) != 0) { return false; } if (string.Compare(Host, host, StringComparison.OrdinalIgnoreCase ) != 0) { return false; } if (port != Port) { return false; } GlobalLog.Print("CredentialKey::Match(" + Host.ToString() + ":" + Port.ToString() +" & " + host.ToString() + ":" + port.ToString() + ")"); return true; } private int m_HashCode = 0; private bool m_ComputedHashCode = false; public override int GetHashCode() { if (!m_ComputedHashCode) { // // compute HashCode on demand // m_HashCode = AuthenticationType.ToUpperInvariant().GetHashCode() + Host.ToUpperInvariant().GetHashCode() + Port.GetHashCode(); m_ComputedHashCode = true; } return m_HashCode; } public override bool Equals(object comparand) { CredentialHostKey comparedCredentialKey = comparand as CredentialHostKey; if (comparand==null) { // // this covers also the compared==null case // return false; } bool equals = (string.Compare(AuthenticationType, comparedCredentialKey.AuthenticationType, StringComparison.OrdinalIgnoreCase ) == 0) && (string.Compare(Host, comparedCredentialKey.Host, StringComparison.OrdinalIgnoreCase ) == 0) && Port == comparedCredentialKey.Port; GlobalLog.Print("CredentialKey::Equals(" + ToString() + ", " + comparedCredentialKey.ToString() + ") returns " + equals.ToString()); return equals; } public override string ToString() { return "[" + Host.Length.ToString(NumberFormatInfo.InvariantInfo) + "]:" + Host + ":" + Port.ToString(NumberFormatInfo.InvariantInfo) + ":" + ValidationHelper.ToString(AuthenticationType); } } // class CredentialKey internal class CredentialKey { internal Uri UriPrefix; internal int UriPrefixLength = -1; internal string AuthenticationType; internal CredentialKey(Uri uriPrefix, string authenticationType) { UriPrefix = uriPrefix; UriPrefixLength = UriPrefix.ToString().Length; AuthenticationType = authenticationType; } internal bool Match(Uri uri, string authenticationType) { if (uri==null || authenticationType==null) { return false; } // // If the protocols dont match this credential // is not applicable for the given Uri // if (string.Compare(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase) != 0) { return false; } GlobalLog.Print("CredentialKey::Match(" + UriPrefix.ToString() + " & " + uri.ToString() + ")"); return IsPrefix(uri, UriPrefix); } // // IsPrefix (Uri) // // Determines whether is a prefix of this URI. A prefix // match is defined as: // // scheme match // + host match // + port match, if any // + path is a prefix of path, if any // // Returns: // True if is a prefix of this URI // internal bool IsPrefix(Uri uri, Uri prefixUri) { if (prefixUri.Scheme != uri.Scheme || prefixUri.Host != uri.Host || prefixUri.Port != uri.Port) return false; int prefixLen = prefixUri.AbsolutePath.LastIndexOf('/'); if (prefixLen > uri.AbsolutePath.LastIndexOf('/')) return false; return String.Compare(uri.AbsolutePath, 0, prefixUri.AbsolutePath, 0, prefixLen, StringComparison.OrdinalIgnoreCase ) == 0; } private int m_HashCode = 0; private bool m_ComputedHashCode = false; public override int GetHashCode() { if (!m_ComputedHashCode) { // // compute HashCode on demand // m_HashCode = AuthenticationType.ToUpperInvariant().GetHashCode() + UriPrefixLength + UriPrefix.GetHashCode(); m_ComputedHashCode = true; } return m_HashCode; } public override bool Equals(object comparand) { CredentialKey comparedCredentialKey = comparand as CredentialKey; if (comparand==null) { // // this covers also the compared==null case // return false; } bool equals = (string.Compare(AuthenticationType, comparedCredentialKey.AuthenticationType, StringComparison.OrdinalIgnoreCase ) == 0) && UriPrefix.Equals(comparedCredentialKey.UriPrefix); GlobalLog.Print("CredentialKey::Equals(" + ToString() + ", " + comparedCredentialKey.ToString() + ") returns " + equals.ToString()); return equals; } public override string ToString() { return "[" + UriPrefixLength.ToString(NumberFormatInfo.InvariantInfo) + "]:" + ValidationHelper.ToString(UriPrefix) + ":" + ValidationHelper.ToString(AuthenticationType); } } // class CredentialKey } // namespace System.Net