1 //------------------------------------------------------------------------------
2 // <copyright file="CredentialCache.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
8 using System.Net.Sockets;
9 using System.Collections;
10 using System.Runtime.InteropServices;
11 using System.Security.Permissions;
12 using System.Globalization;
14 // More sophisticated password cache that stores multiple
15 // name-password pairs and associates these with host/realm
17 /// <para>Provides storage for multiple credentials.</para>
19 public class CredentialCache : ICredentials, ICredentialsByHost, IEnumerable {
23 private Hashtable cache = new Hashtable();
24 private Hashtable cacheForHosts = new Hashtable();
25 internal int m_version;
27 private int m_NumbDefaultCredInCache = 0;
29 // [thread token optimization] The resulting counter of default credential resided in the cache.
30 internal bool IsDefaultInCache {
32 return m_NumbDefaultCredInCache != 0;
40 /// Initializes a new instance of the <see cref='System.Net.CredentialCache'/> class.
43 public CredentialCache() {
51 /// <para>Adds a <see cref='System.Net.NetworkCredential'/>
52 /// instance to the credential cache.</para>
55 public void Add(Uri uriPrefix, string authType, NetworkCredential cred) {
57 // parameter validation
59 if (uriPrefix==null) {
60 throw new ArgumentNullException("uriPrefix");
63 throw new ArgumentNullException("authType");
65 if ((cred is SystemNetworkCredential)
67 && !((string.Compare(authType, NtlmClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)
68 || (DigestClient.WDigestAvailable && (string.Compare(authType, DigestClient.AuthType, StringComparison.OrdinalIgnoreCase)==0))
69 || (string.Compare(authType, KerberosClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)
70 || (string.Compare(authType, NegotiateClient.AuthType, StringComparison.OrdinalIgnoreCase)==0))
73 throw new ArgumentException(SR.GetString(SR.net_nodefaultcreds, authType), "authType");
78 CredentialKey key = new CredentialKey(uriPrefix, authType);
80 GlobalLog.Print("CredentialCache::Add() Adding key:[" + key.ToString() + "], cred:[" + cred.Domain + "],[" + cred.UserName + "]");
83 if (cred is SystemNetworkCredential) {
84 ++m_NumbDefaultCredInCache;
89 public void Add(string host, int port, string authenticationType, NetworkCredential credential) {
91 // parameter validation
94 throw new ArgumentNullException("host");
97 if (authenticationType==null) {
98 throw new ArgumentNullException("authenticationType");
101 if (host.Length == 0) {
102 throw new ArgumentException(SR.GetString(SR.net_emptystringcall,"host"));
106 throw new ArgumentOutOfRangeException("port");
108 if ((credential is SystemNetworkCredential)
110 && !((string.Compare(authenticationType, NtlmClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)
111 || (DigestClient.WDigestAvailable && (string.Compare(authenticationType, DigestClient.AuthType, StringComparison.OrdinalIgnoreCase)==0))
112 || (string.Compare(authenticationType, KerberosClient.AuthType, StringComparison.OrdinalIgnoreCase)==0)
113 || (string.Compare(authenticationType, NegotiateClient.AuthType, StringComparison.OrdinalIgnoreCase)==0))
116 throw new ArgumentException(SR.GetString(SR.net_nodefaultcreds, authenticationType), "authenticationType");
121 CredentialHostKey key = new CredentialHostKey(host,port, authenticationType);
123 GlobalLog.Print("CredentialCache::Add() Adding key:[" + key.ToString() + "], cred:[" + credential.Domain + "],[" + credential.UserName + "]");
125 cacheForHosts.Add(key, credential);
126 if (credential is SystemNetworkCredential) {
127 ++m_NumbDefaultCredInCache;
133 /// <para>Removes a <see cref='System.Net.NetworkCredential'/>
134 /// instance from the credential cache.</para>
136 public void Remove(Uri uriPrefix, string authType) {
137 if (uriPrefix==null || authType==null) {
138 // these couldn't possibly have been inserted into
139 // the cache because of the test in Add()
145 CredentialKey key = new CredentialKey(uriPrefix, authType);
147 GlobalLog.Print("CredentialCache::Remove() Removing key:[" + key.ToString() + "]");
149 if (cache[key] is SystemNetworkCredential) {
150 --m_NumbDefaultCredInCache;
156 public void Remove(string host, int port, string authenticationType) {
157 if (host==null || authenticationType==null) {
158 // these couldn't possibly have been inserted into
159 // the cache because of the test in Add()
170 CredentialHostKey key = new CredentialHostKey(host, port, authenticationType);
172 GlobalLog.Print("CredentialCache::Remove() Removing key:[" + key.ToString() + "]");
174 if (cacheForHosts[key] is SystemNetworkCredential) {
175 --m_NumbDefaultCredInCache;
177 cacheForHosts.Remove(key);
182 /// Returns the <see cref='System.Net.NetworkCredential'/>
183 /// instance associated with the supplied Uri and
184 /// authentication type.
187 public NetworkCredential GetCredential(Uri uriPrefix, string authType) {
189 throw new ArgumentNullException("uriPrefix");
191 throw new ArgumentNullException("authType");
193 GlobalLog.Print("CredentialCache::GetCredential(uriPrefix=\"" + uriPrefix + "\", authType=\"" + authType + "\")");
195 int longestMatchPrefix = -1;
196 NetworkCredential mostSpecificMatch = null;
197 IDictionaryEnumerator credEnum = cache.GetEnumerator();
200 // Enumerate through every credential in the cache
203 while (credEnum.MoveNext()) {
205 CredentialKey key = (CredentialKey)credEnum.Key;
208 // Determine if this credential is applicable to the current Uri/AuthType
211 if (key.Match(uriPrefix, authType)) {
213 int prefixLen = key.UriPrefixLength;
216 // Check if the match is better than the current-most-specific match
219 if (prefixLen > longestMatchPrefix) {
222 // Yes-- update the information about currently preferred match
225 longestMatchPrefix = prefixLen;
226 mostSpecificMatch = (NetworkCredential)credEnum.Value;
231 GlobalLog.Print("CredentialCache::GetCredential returning " + ((mostSpecificMatch==null)?"null":"(" + mostSpecificMatch.UserName + ":" + mostSpecificMatch.Domain + ")"));
233 return mostSpecificMatch;
237 public NetworkCredential GetCredential(string host, int port, string authenticationType) {
239 throw new ArgumentNullException("host");
241 if (authenticationType==null){
242 throw new ArgumentNullException("authenticationType");
244 if (host.Length == 0) {
245 throw new ArgumentException(SR.GetString(SR.net_emptystringcall, "host"));
248 throw new ArgumentOutOfRangeException("port");
252 GlobalLog.Print("CredentialCache::GetCredential(host=\"" + host + ":" + port.ToString() +"\", authenticationType=\"" + authenticationType + "\")");
254 NetworkCredential match = null;
256 IDictionaryEnumerator credEnum = cacheForHosts.GetEnumerator();
259 // Enumerate through every credential in the cache
262 while (credEnum.MoveNext()) {
264 CredentialHostKey key = (CredentialHostKey)credEnum.Key;
267 // Determine if this credential is applicable to the current Uri/AuthType
270 if (key.Match(host, port, authenticationType)) {
272 match = (NetworkCredential)credEnum.Value;
276 GlobalLog.Print("CredentialCache::GetCredential returning " + ((match==null)?"null":"(" + match.UserName + ":" + match.Domain + ")"));
287 // IEnumerable interface
290 public IEnumerator GetEnumerator() {
291 return new CredentialEnumerator(this, cache ,cacheForHosts, m_version);
298 /// the default system credentials from the <see cref='System.Net.CredentialCache'/>.
301 public static ICredentials DefaultCredentials {
303 //This check will not allow to use local user credentials at will.
304 //Hence the username will not be exposed to the network
306 new EnvironmentPermission(EnvironmentPermissionAccess.Read, "USERNAME").Demand();
308 return SystemNetworkCredential.defaultCredential;
312 public static NetworkCredential DefaultNetworkCredentials {
314 //This check will not allow to use local user credentials at will.
315 //Hence the username will not be exposed to the network
317 new EnvironmentPermission(EnvironmentPermissionAccess.Read, "USERNAME").Demand();
319 return SystemNetworkCredential.defaultCredential;
323 private class CredentialEnumerator : IEnumerator {
327 private CredentialCache m_cache;
328 private ICredentials[] m_array;
329 private int m_index = -1;
330 private int m_version;
334 internal CredentialEnumerator(CredentialCache cache, Hashtable table, Hashtable hostTable, int version) {
336 m_array = new ICredentials[table.Count + hostTable.Count];
337 table.Values.CopyTo(m_array, 0);
338 hostTable.Values.CopyTo(m_array, table.Count);
342 // IEnumerator interface
346 object IEnumerator.Current {
348 if (m_index < 0 || m_index >= m_array.Length) {
349 throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumOpCantHappen));
351 if (m_version != m_cache.m_version) {
352 throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumFailedVersion));
354 return m_array[m_index];
360 bool IEnumerator.MoveNext() {
361 if (m_version != m_cache.m_version) {
362 throw new InvalidOperationException(SR.GetString(SR.InvalidOperation_EnumFailedVersion));
364 if (++m_index < m_array.Length) {
367 m_index = m_array.Length;
371 void IEnumerator.Reset() {
375 } // class CredentialEnumerator
378 } // class CredentialCache
382 // Abstraction for credentials in password-based
383 // authentication schemes (basic, digest, NTLM, Kerberos)
384 // Note this is not applicable to public-key based
385 // systems such as SSL client authentication
386 // "Password" here may be the clear text password or it
387 // could be a one-way hash that is sufficient to
388 // authenticate, as in HTTP/1.1 digest.
391 // Object representing default credentials
393 internal class SystemNetworkCredential : NetworkCredential {
394 internal static readonly SystemNetworkCredential defaultCredential = new SystemNetworkCredential();
396 // We want reference equality to work. Making this private is a good way to guarantee that.
397 private SystemNetworkCredential() :
398 base(string.Empty, string.Empty, string.Empty) {
403 internal class CredentialHostKey {
405 internal string Host;
406 internal string AuthenticationType;
409 internal CredentialHostKey(string host, int port, string authenticationType) {
412 AuthenticationType = authenticationType;
415 internal bool Match(string host, int port, string authenticationType) {
416 if (host==null || authenticationType==null) {
420 // If the protocols dont match this credential
421 // is not applicable for the given Uri
423 if (string.Compare(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase) != 0) {
426 if (string.Compare(Host, host, StringComparison.OrdinalIgnoreCase ) != 0) {
433 GlobalLog.Print("CredentialKey::Match(" + Host.ToString() + ":" + Port.ToString() +" & " + host.ToString() + ":" + port.ToString() + ")");
438 private int m_HashCode = 0;
439 private bool m_ComputedHashCode = false;
440 public override int GetHashCode() {
441 if (!m_ComputedHashCode) {
443 // compute HashCode on demand
446 m_HashCode = AuthenticationType.ToUpperInvariant().GetHashCode() + Host.ToUpperInvariant().GetHashCode() + Port.GetHashCode();
447 m_ComputedHashCode = true;
452 public override bool Equals(object comparand) {
453 CredentialHostKey comparedCredentialKey = comparand as CredentialHostKey;
455 if (comparand==null) {
457 // this covers also the compared==null case
463 (string.Compare(AuthenticationType, comparedCredentialKey.AuthenticationType, StringComparison.OrdinalIgnoreCase ) == 0) &&
464 (string.Compare(Host, comparedCredentialKey.Host, StringComparison.OrdinalIgnoreCase ) == 0) &&
465 Port == comparedCredentialKey.Port;
467 GlobalLog.Print("CredentialKey::Equals(" + ToString() + ", " + comparedCredentialKey.ToString() + ") returns " + equals.ToString());
472 public override string ToString() {
473 return "[" + Host.Length.ToString(NumberFormatInfo.InvariantInfo) + "]:" + Host + ":" + Port.ToString(NumberFormatInfo.InvariantInfo) + ":" + ValidationHelper.ToString(AuthenticationType);
476 } // class CredentialKey
479 internal class CredentialKey {
481 internal Uri UriPrefix;
482 internal int UriPrefixLength = -1;
483 internal string AuthenticationType;
485 internal CredentialKey(Uri uriPrefix, string authenticationType) {
486 UriPrefix = uriPrefix;
487 UriPrefixLength = UriPrefix.ToString().Length;
488 AuthenticationType = authenticationType;
491 internal bool Match(Uri uri, string authenticationType) {
492 if (uri==null || authenticationType==null) {
496 // If the protocols dont match this credential
497 // is not applicable for the given Uri
499 if (string.Compare(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase) != 0) {
503 GlobalLog.Print("CredentialKey::Match(" + UriPrefix.ToString() + " & " + uri.ToString() + ")");
505 return IsPrefix(uri, UriPrefix);
510 // Determines whether <prefixUri> is a prefix of this URI. A prefix
511 // match is defined as:
515 // + port match, if any
516 // + <prefix> path is a prefix of <URI> path, if any
519 // True if <prefixUri> is a prefix of this URI
521 internal bool IsPrefix(Uri uri, Uri prefixUri) {
523 if (prefixUri.Scheme != uri.Scheme || prefixUri.Host != uri.Host || prefixUri.Port != uri.Port)
526 int prefixLen = prefixUri.AbsolutePath.LastIndexOf('/');
527 if (prefixLen > uri.AbsolutePath.LastIndexOf('/'))
530 return String.Compare(uri.AbsolutePath, 0, prefixUri.AbsolutePath, 0, prefixLen, StringComparison.OrdinalIgnoreCase ) == 0;
533 private int m_HashCode = 0;
534 private bool m_ComputedHashCode = false;
535 public override int GetHashCode() {
536 if (!m_ComputedHashCode) {
538 // compute HashCode on demand
541 m_HashCode = AuthenticationType.ToUpperInvariant().GetHashCode() + UriPrefixLength + UriPrefix.GetHashCode();
542 m_ComputedHashCode = true;
547 public override bool Equals(object comparand) {
548 CredentialKey comparedCredentialKey = comparand as CredentialKey;
550 if (comparand==null) {
552 // this covers also the compared==null case
558 (string.Compare(AuthenticationType, comparedCredentialKey.AuthenticationType, StringComparison.OrdinalIgnoreCase ) == 0) &&
559 UriPrefix.Equals(comparedCredentialKey.UriPrefix);
561 GlobalLog.Print("CredentialKey::Equals(" + ToString() + ", " + comparedCredentialKey.ToString() + ") returns " + equals.ToString());
566 public override string ToString() {
567 return "[" + UriPrefixLength.ToString(NumberFormatInfo.InvariantInfo) + "]:" + ValidationHelper.ToString(UriPrefix) + ":" + ValidationHelper.ToString(AuthenticationType);
570 } // class CredentialKey
573 } // namespace System.Net