| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Collections; |
| | | 5 | | using System.Collections.Generic; |
| | | 6 | | using System.Diagnostics; |
| | | 7 | | using System.Diagnostics.CodeAnalysis; |
| | | 8 | | using System.Globalization; |
| | | 9 | | |
| | | 10 | | namespace System.Net |
| | | 11 | | { |
| | | 12 | | // More sophisticated password cache that stores multiple |
| | | 13 | | // name-password pairs and associates these with host/realm. |
| | | 14 | | public class CredentialCache : ICredentials, ICredentialsByHost, IEnumerable |
| | | 15 | | { |
| | | 16 | | private Dictionary<CredentialCacheKey, NetworkCredential>? _cache; |
| | | 17 | | private Dictionary<CredentialHostKey, NetworkCredential>? _cacheForHosts; |
| | | 18 | | private int _version; |
| | | 19 | | |
| | 0 | 20 | | public CredentialCache() |
| | 0 | 21 | | { |
| | 0 | 22 | | } |
| | | 23 | | |
| | | 24 | | public void Add(Uri uriPrefix, string authType, NetworkCredential cred) |
| | 0 | 25 | | { |
| | 0 | 26 | | ArgumentNullException.ThrowIfNull(uriPrefix); |
| | 0 | 27 | | ArgumentNullException.ThrowIfNull(authType); |
| | | 28 | | |
| | 0 | 29 | | if ((cred is SystemNetworkCredential) |
| | 0 | 30 | | && !((string.Equals(authType, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase)) |
| | 0 | 31 | | || (string.Equals(authType, NegotiationInfoClass.Kerberos, StringComparison.OrdinalIgnoreCase)) |
| | 0 | 32 | | || (string.Equals(authType, NegotiationInfoClass.Negotiate, StringComparison.OrdinalIgnoreCase))) |
| | 0 | 33 | | ) |
| | 0 | 34 | | { |
| | 0 | 35 | | throw new ArgumentException(SR.Format(SR.net_nodefaultcreds, authType), nameof(authType)); |
| | | 36 | | } |
| | | 37 | | |
| | 0 | 38 | | ++_version; |
| | | 39 | | |
| | 0 | 40 | | var key = new CredentialCacheKey(uriPrefix, authType); |
| | | 41 | | |
| | 0 | 42 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Adding key:[{key}], cred:[{cred.Domain}],[{c |
| | | 43 | | |
| | 0 | 44 | | _cache ??= new Dictionary<CredentialCacheKey, NetworkCredential>(); |
| | 0 | 45 | | _cache.Add(key, cred); |
| | 0 | 46 | | } |
| | | 47 | | |
| | | 48 | | public void Add(string host, int port, string authenticationType, NetworkCredential credential) |
| | 0 | 49 | | { |
| | 0 | 50 | | ArgumentException.ThrowIfNullOrEmpty(host); |
| | 0 | 51 | | ArgumentNullException.ThrowIfNull(authenticationType); |
| | | 52 | | |
| | 0 | 53 | | ArgumentOutOfRangeException.ThrowIfNegative(port); |
| | | 54 | | |
| | 0 | 55 | | if ((credential is SystemNetworkCredential) |
| | 0 | 56 | | && !((string.Equals(authenticationType, NegotiationInfoClass.NTLM, StringComparison.OrdinalIgnoreCase)) |
| | 0 | 57 | | || (string.Equals(authenticationType, NegotiationInfoClass.Kerberos, StringComparison.OrdinalIgnore |
| | 0 | 58 | | || (string.Equals(authenticationType, NegotiationInfoClass.Negotiate, StringComparison.OrdinalIgnor |
| | 0 | 59 | | ) |
| | 0 | 60 | | { |
| | 0 | 61 | | throw new ArgumentException(SR.Format(SR.net_nodefaultcreds, authenticationType), nameof(authenticationT |
| | | 62 | | } |
| | | 63 | | |
| | 0 | 64 | | ++_version; |
| | | 65 | | |
| | 0 | 66 | | var key = new CredentialHostKey(host, port, authenticationType); |
| | | 67 | | |
| | 0 | 68 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Adding key:[{key}], cred:[{credential.Domain |
| | | 69 | | |
| | 0 | 70 | | _cacheForHosts ??= new Dictionary<CredentialHostKey, NetworkCredential>(); |
| | 0 | 71 | | _cacheForHosts.Add(key, credential); |
| | 0 | 72 | | } |
| | | 73 | | |
| | | 74 | | public void Remove(Uri? uriPrefix, string? authType) |
| | 0 | 75 | | { |
| | 0 | 76 | | if (uriPrefix == null || authType == null) |
| | 0 | 77 | | { |
| | | 78 | | // These couldn't possibly have been inserted into |
| | | 79 | | // the cache because of the test in Add(). |
| | 0 | 80 | | return; |
| | | 81 | | } |
| | | 82 | | |
| | 0 | 83 | | if (_cache == null) |
| | 0 | 84 | | { |
| | 0 | 85 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Short-circuiting because the dictionary i |
| | 0 | 86 | | return; |
| | | 87 | | } |
| | | 88 | | |
| | 0 | 89 | | ++_version; |
| | | 90 | | |
| | 0 | 91 | | var key = new CredentialCacheKey(uriPrefix, authType); |
| | | 92 | | |
| | 0 | 93 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Removing key:[{key}]"); |
| | | 94 | | |
| | 0 | 95 | | _cache.Remove(key); |
| | 0 | 96 | | } |
| | | 97 | | |
| | | 98 | | public void Remove(string? host, int port, string? authenticationType) |
| | 0 | 99 | | { |
| | 0 | 100 | | if (host == null || authenticationType == null) |
| | 0 | 101 | | { |
| | | 102 | | // These couldn't possibly have been inserted into |
| | | 103 | | // the cache because of the test in Add(). |
| | 0 | 104 | | return; |
| | | 105 | | } |
| | | 106 | | |
| | 0 | 107 | | if (port < 0) |
| | 0 | 108 | | { |
| | 0 | 109 | | return; |
| | | 110 | | } |
| | | 111 | | |
| | 0 | 112 | | if (_cacheForHosts == null) |
| | 0 | 113 | | { |
| | 0 | 114 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "Short-circuiting because the dictionary i |
| | 0 | 115 | | return; |
| | | 116 | | } |
| | | 117 | | |
| | 0 | 118 | | ++_version; |
| | | 119 | | |
| | 0 | 120 | | var key = new CredentialHostKey(host, port, authenticationType); |
| | | 121 | | |
| | 0 | 122 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Removing key:[{key}]"); |
| | | 123 | | |
| | 0 | 124 | | _cacheForHosts.Remove(key); |
| | 0 | 125 | | } |
| | | 126 | | |
| | | 127 | | public NetworkCredential? GetCredential(Uri uriPrefix, string authType) |
| | 0 | 128 | | { |
| | 0 | 129 | | ArgumentNullException.ThrowIfNull(uriPrefix); |
| | 0 | 130 | | ArgumentNullException.ThrowIfNull(authType); |
| | | 131 | | |
| | 0 | 132 | | if (_cache == null) |
| | 0 | 133 | | { |
| | 0 | 134 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "CredentialCache::GetCredential short-circ |
| | 0 | 135 | | return null; |
| | | 136 | | } |
| | | 137 | | |
| | 0 | 138 | | CredentialCacheHelper.TryGetCredential(_cache, uriPrefix, authType, out _ /*uri*/, out NetworkCredential? mo |
| | | 139 | | |
| | 0 | 140 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Returning {(mostSpecificMatch == null ? "nul |
| | | 141 | | |
| | 0 | 142 | | return mostSpecificMatch; |
| | 0 | 143 | | } |
| | | 144 | | |
| | | 145 | | public NetworkCredential? GetCredential(string host, int port, string authenticationType) |
| | 0 | 146 | | { |
| | 0 | 147 | | ArgumentException.ThrowIfNullOrEmpty(host); |
| | 0 | 148 | | ArgumentNullException.ThrowIfNull(authenticationType); |
| | 0 | 149 | | ArgumentOutOfRangeException.ThrowIfNegative(port); |
| | | 150 | | |
| | 0 | 151 | | if (_cacheForHosts == null) |
| | 0 | 152 | | { |
| | 0 | 153 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, "CredentialCache::GetCredential short-circ |
| | 0 | 154 | | return null; |
| | | 155 | | } |
| | | 156 | | |
| | 0 | 157 | | var key = new CredentialHostKey(host, port, authenticationType); |
| | | 158 | | |
| | | 159 | | NetworkCredential? match; |
| | 0 | 160 | | _cacheForHosts.TryGetValue(key, out match); |
| | | 161 | | |
| | 0 | 162 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Returning {((match == null) ? "null" : "(" + |
| | | 163 | | |
| | 0 | 164 | | return match; |
| | 0 | 165 | | } |
| | | 166 | | |
| | 0 | 167 | | public IEnumerator GetEnumerator() => CredentialEnumerator.Create(this); |
| | | 168 | | |
| | 0 | 169 | | public static ICredentials DefaultCredentials => SystemNetworkCredential.s_defaultCredential; |
| | | 170 | | |
| | 0 | 171 | | public static NetworkCredential DefaultNetworkCredentials => SystemNetworkCredential.s_defaultCredential; |
| | | 172 | | |
| | | 173 | | private class CredentialEnumerator : IEnumerator |
| | | 174 | | { |
| | | 175 | | internal static CredentialEnumerator Create(CredentialCache cache) |
| | 0 | 176 | | { |
| | 0 | 177 | | Debug.Assert(cache != null); |
| | | 178 | | |
| | 0 | 179 | | if (cache._cache != null) |
| | 0 | 180 | | { |
| | 0 | 181 | | return cache._cacheForHosts != null ? |
| | 0 | 182 | | new DoubleTableCredentialEnumerator(cache) : |
| | 0 | 183 | | new SingleTableCredentialEnumerator<CredentialCacheKey>(cache, cache._cache); |
| | | 184 | | } |
| | | 185 | | else |
| | 0 | 186 | | { |
| | 0 | 187 | | return cache._cacheForHosts != null ? |
| | 0 | 188 | | new SingleTableCredentialEnumerator<CredentialHostKey>(cache, cache._cacheForHosts) : |
| | 0 | 189 | | new CredentialEnumerator(cache); |
| | | 190 | | } |
| | 0 | 191 | | } |
| | | 192 | | |
| | | 193 | | private readonly CredentialCache _cache; |
| | | 194 | | private readonly int _version; |
| | | 195 | | private bool _enumerating; |
| | | 196 | | private NetworkCredential? _current; |
| | | 197 | | |
| | 0 | 198 | | private CredentialEnumerator(CredentialCache cache) |
| | 0 | 199 | | { |
| | 0 | 200 | | Debug.Assert(cache != null); |
| | | 201 | | |
| | 0 | 202 | | _cache = cache; |
| | 0 | 203 | | _version = cache._version; |
| | 0 | 204 | | } |
| | | 205 | | |
| | | 206 | | public object Current |
| | | 207 | | { |
| | | 208 | | get |
| | 0 | 209 | | { |
| | 0 | 210 | | if (!_enumerating) |
| | 0 | 211 | | { |
| | 0 | 212 | | throw new InvalidOperationException(SR.InvalidOperation_EnumOpCantHappen); |
| | | 213 | | } |
| | 0 | 214 | | if (_version != _cache._version) |
| | 0 | 215 | | { |
| | 0 | 216 | | throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); |
| | | 217 | | } |
| | | 218 | | |
| | 0 | 219 | | return _current!; |
| | 0 | 220 | | } |
| | | 221 | | } |
| | | 222 | | |
| | | 223 | | public bool MoveNext() |
| | 0 | 224 | | { |
| | 0 | 225 | | if (_version != _cache._version) |
| | 0 | 226 | | { |
| | 0 | 227 | | throw new InvalidOperationException(SR.InvalidOperation_EnumFailedVersion); |
| | | 228 | | } |
| | | 229 | | |
| | 0 | 230 | | return _enumerating = MoveNext(out _current); |
| | 0 | 231 | | } |
| | | 232 | | |
| | | 233 | | protected virtual bool MoveNext(out NetworkCredential? current) |
| | 0 | 234 | | { |
| | 0 | 235 | | current = null; |
| | 0 | 236 | | return false; |
| | 0 | 237 | | } |
| | | 238 | | |
| | | 239 | | public virtual void Reset() |
| | 0 | 240 | | { |
| | 0 | 241 | | _enumerating = false; |
| | 0 | 242 | | } |
| | | 243 | | |
| | | 244 | | private class SingleTableCredentialEnumerator<TKey> : CredentialEnumerator where TKey : notnull |
| | | 245 | | { |
| | | 246 | | private Dictionary<TKey, NetworkCredential>.ValueCollection.Enumerator _enumerator; // mutable struct fi |
| | | 247 | | |
| | 0 | 248 | | public SingleTableCredentialEnumerator(CredentialCache cache, Dictionary<TKey, NetworkCredential> table) |
| | 0 | 249 | | { |
| | 0 | 250 | | Debug.Assert(table != null); |
| | | 251 | | |
| | | 252 | | // Despite the ValueCollection allocation, ValueCollection's enumerator is faster |
| | | 253 | | // than Dictionary's enumerator for enumerating the values because it avoids |
| | | 254 | | // KeyValuePair copying. |
| | 0 | 255 | | _enumerator = table.Values.GetEnumerator(); |
| | 0 | 256 | | } |
| | | 257 | | |
| | | 258 | | protected override bool MoveNext(out NetworkCredential current) => |
| | 0 | 259 | | DictionaryEnumeratorHelper.MoveNext(ref _enumerator, out current); |
| | | 260 | | |
| | | 261 | | public override void Reset() |
| | 0 | 262 | | { |
| | 0 | 263 | | DictionaryEnumeratorHelper.Reset(ref _enumerator); |
| | 0 | 264 | | base.Reset(); |
| | 0 | 265 | | } |
| | | 266 | | } |
| | | 267 | | |
| | | 268 | | private sealed class DoubleTableCredentialEnumerator : SingleTableCredentialEnumerator<CredentialCacheKey> |
| | | 269 | | { |
| | | 270 | | private Dictionary<CredentialHostKey, NetworkCredential>.ValueCollection.Enumerator _enumerator; // muta |
| | | 271 | | private bool _onThisEnumerator; |
| | | 272 | | |
| | 0 | 273 | | public DoubleTableCredentialEnumerator(CredentialCache cache) : base(cache, cache._cache!) |
| | 0 | 274 | | { |
| | 0 | 275 | | Debug.Assert(cache._cacheForHosts != null); |
| | | 276 | | |
| | | 277 | | // Despite the ValueCollection allocation, ValueCollection's enumerator is faster |
| | | 278 | | // than Dictionary's enumerator for enumerating the values because it avoids |
| | | 279 | | // KeyValuePair copying. |
| | 0 | 280 | | _enumerator = cache._cacheForHosts.Values.GetEnumerator(); |
| | 0 | 281 | | } |
| | | 282 | | |
| | | 283 | | protected override bool MoveNext(out NetworkCredential current) |
| | 0 | 284 | | { |
| | 0 | 285 | | if (!_onThisEnumerator) |
| | 0 | 286 | | { |
| | 0 | 287 | | if (base.MoveNext(out current)) |
| | 0 | 288 | | { |
| | 0 | 289 | | return true; |
| | | 290 | | } |
| | | 291 | | else |
| | 0 | 292 | | { |
| | 0 | 293 | | _onThisEnumerator = true; |
| | 0 | 294 | | } |
| | 0 | 295 | | } |
| | | 296 | | |
| | 0 | 297 | | return DictionaryEnumeratorHelper.MoveNext(ref _enumerator, out current); |
| | 0 | 298 | | } |
| | | 299 | | |
| | | 300 | | public override void Reset() |
| | 0 | 301 | | { |
| | 0 | 302 | | _onThisEnumerator = false; |
| | 0 | 303 | | DictionaryEnumeratorHelper.Reset(ref _enumerator); |
| | 0 | 304 | | base.Reset(); |
| | 0 | 305 | | } |
| | | 306 | | } |
| | | 307 | | |
| | | 308 | | private static class DictionaryEnumeratorHelper |
| | | 309 | | { |
| | | 310 | | internal static bool MoveNext<TKey, TValue>(ref Dictionary<TKey, TValue>.ValueCollection.Enumerator enum |
| | 0 | 311 | | { |
| | 0 | 312 | | bool result = enumerator.MoveNext(); |
| | 0 | 313 | | current = enumerator.Current; |
| | 0 | 314 | | return result; |
| | 0 | 315 | | } |
| | | 316 | | |
| | | 317 | | // Allows calling Reset on Dictionary's struct enumerator without a box allocation. |
| | | 318 | | internal static void Reset<TEnumerator>(ref TEnumerator enumerator) where TEnumerator : IEnumerator |
| | 0 | 319 | | { |
| | | 320 | | // The Dictionary enumerator's Reset method throws if the Dictionary has changed, but |
| | | 321 | | // CredentialCache.Reset should not throw, so we catch and swallow the exception. |
| | 0 | 322 | | try { enumerator.Reset(); } catch (InvalidOperationException) { } |
| | 0 | 323 | | } |
| | | 324 | | } |
| | | 325 | | } |
| | | 326 | | } |
| | | 327 | | |
| | | 328 | | // Abstraction for credentials in password-based |
| | | 329 | | // authentication schemes (basic, digest, NTLM, Kerberos). |
| | | 330 | | // |
| | | 331 | | // Note that this is not applicable to public-key based |
| | | 332 | | // systems such as SSL client authentication. |
| | | 333 | | // |
| | | 334 | | // "Password" here may be the clear text password or it |
| | | 335 | | // could be a one-way hash that is sufficient to |
| | | 336 | | // authenticate, as in HTTP/1.1 digest. |
| | | 337 | | internal sealed class SystemNetworkCredential : NetworkCredential |
| | | 338 | | { |
| | | 339 | | internal static readonly SystemNetworkCredential s_defaultCredential = new SystemNetworkCredential(); |
| | | 340 | | |
| | | 341 | | // We want reference equality to work. Making this private is a good way to guarantee that. |
| | | 342 | | private SystemNetworkCredential() : |
| | | 343 | | base(string.Empty, string.Empty, string.Empty) |
| | | 344 | | { |
| | | 345 | | } |
| | | 346 | | } |
| | | 347 | | |
| | | 348 | | internal readonly struct CredentialHostKey : IEquatable<CredentialHostKey> |
| | | 349 | | { |
| | | 350 | | public readonly string Host; |
| | | 351 | | public readonly string AuthenticationType; |
| | | 352 | | public readonly int Port; |
| | | 353 | | |
| | | 354 | | internal CredentialHostKey(string host, int port, string authenticationType) |
| | | 355 | | { |
| | | 356 | | Debug.Assert(!string.IsNullOrEmpty(host)); |
| | | 357 | | Debug.Assert(port >= 0); |
| | | 358 | | Debug.Assert(authenticationType != null); |
| | | 359 | | |
| | | 360 | | Host = host; |
| | | 361 | | Port = port; |
| | | 362 | | AuthenticationType = authenticationType; |
| | | 363 | | } |
| | | 364 | | |
| | | 365 | | public override int GetHashCode() => |
| | | 366 | | StringComparer.OrdinalIgnoreCase.GetHashCode(AuthenticationType) ^ |
| | | 367 | | StringComparer.OrdinalIgnoreCase.GetHashCode(Host) ^ |
| | | 368 | | Port.GetHashCode(); |
| | | 369 | | |
| | | 370 | | public bool Equals(CredentialHostKey other) |
| | | 371 | | { |
| | | 372 | | bool equals = |
| | | 373 | | string.Equals(AuthenticationType, other.AuthenticationType, StringComparison.OrdinalIgnoreCase) && |
| | | 374 | | string.Equals(Host, other.Host, StringComparison.OrdinalIgnoreCase) && |
| | | 375 | | Port == other.Port; |
| | | 376 | | |
| | | 377 | | if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Equals({this},{other}) returns {equals}"); |
| | | 378 | | |
| | | 379 | | return equals; |
| | | 380 | | } |
| | | 381 | | |
| | | 382 | | public override bool Equals([NotNullWhen(true)] object? obj) => |
| | | 383 | | obj is CredentialHostKey && Equals((CredentialHostKey)obj); |
| | | 384 | | |
| | | 385 | | public override string ToString() => |
| | | 386 | | string.Create(CultureInfo.InvariantCulture, $"{Host}:{Port}:{AuthenticationType}"); |
| | | 387 | | } |
| | | 388 | | } |
| | | 389 | | |