forked from dotnet/runtime
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refresh cached credentials after PreAuthenticate fails (dotnet#101053)
* Support refreshing credentials in pre-auth cache * Fix minor bug in CredentialCache * Add unit test * Fix tests * Fix tests attempt 2 * Merge two lock statements. * Fix build
- Loading branch information
1 parent
39f7495
commit 000c7c8
Showing
10 changed files
with
363 additions
and
130 deletions.
There are no files selected for viewing
133 changes: 133 additions & 0 deletions
133
src/libraries/Common/src/System/Net/CredentialCacheKey.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
|
||
namespace System.Net | ||
{ | ||
internal sealed class CredentialCacheKey : IEquatable<CredentialCacheKey?> | ||
{ | ||
public readonly Uri UriPrefix; | ||
public readonly int UriPrefixLength = -1; | ||
public readonly string AuthenticationType; | ||
|
||
internal CredentialCacheKey(Uri uriPrefix, string authenticationType) | ||
{ | ||
Debug.Assert(uriPrefix != null); | ||
Debug.Assert(authenticationType != null); | ||
|
||
UriPrefix = uriPrefix; | ||
UriPrefixLength = UriPrefix.AbsolutePath.LastIndexOf('/'); | ||
AuthenticationType = authenticationType; | ||
} | ||
|
||
internal bool Match(Uri uri, string authenticationType) | ||
{ | ||
if (uri == null || authenticationType == null) | ||
{ | ||
return false; | ||
} | ||
|
||
// If the protocols don't match, this credential is not applicable for the given Uri. | ||
if (!string.Equals(authenticationType, AuthenticationType, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return false; | ||
} | ||
|
||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Match({UriPrefix} & {uri})"); | ||
|
||
return IsPrefix(uri, UriPrefix); | ||
} | ||
|
||
// IsPrefix (Uri) | ||
// | ||
// Determines whether <prefixUri> is a prefix of this URI. A prefix | ||
// match is defined as: | ||
// | ||
// scheme match | ||
// + host match | ||
// + port match, if any | ||
// + <prefix> path is a prefix of <URI> path, if any | ||
// | ||
// Returns: | ||
// True if <prefixUri> is a prefix of this URI | ||
private static bool IsPrefix(Uri uri, Uri prefixUri) | ||
{ | ||
Debug.Assert(uri != null); | ||
Debug.Assert(prefixUri != null); | ||
|
||
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; | ||
} | ||
|
||
public override int GetHashCode() => | ||
StringComparer.OrdinalIgnoreCase.GetHashCode(AuthenticationType) ^ | ||
UriPrefix.GetHashCode(); | ||
|
||
public bool Equals([NotNullWhen(true)] CredentialCacheKey? other) | ||
{ | ||
if (other == null) | ||
{ | ||
return false; | ||
} | ||
|
||
bool equals = | ||
string.Equals(AuthenticationType, other.AuthenticationType, StringComparison.OrdinalIgnoreCase) && | ||
UriPrefix.Equals(other.UriPrefix); | ||
|
||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Equals({this},{other}) returns {equals}"); | ||
|
||
return equals; | ||
} | ||
|
||
public override bool Equals([NotNullWhen(true)] object? obj) => Equals(obj as CredentialCacheKey); | ||
|
||
public override string ToString() => | ||
string.Create(CultureInfo.InvariantCulture, $"[{UriPrefixLength}]:{UriPrefix}:{AuthenticationType}"); | ||
} | ||
|
||
internal static class CredentialCacheHelper | ||
{ | ||
public static bool TryGetCredential(Dictionary<CredentialCacheKey, NetworkCredential> cache, Uri uriPrefix, string authType, [NotNullWhen(true)] out Uri? mostSpecificMatchUri, [NotNullWhen(true)] out NetworkCredential? mostSpecificMatch) | ||
{ | ||
int longestMatchPrefix = -1; | ||
mostSpecificMatch = null; | ||
mostSpecificMatchUri = null; | ||
|
||
// Enumerate through every credential in the cache | ||
foreach ((CredentialCacheKey key, NetworkCredential value) in cache) | ||
{ | ||
// 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 = value; | ||
mostSpecificMatchUri = key.UriPrefix; | ||
} | ||
} | ||
} | ||
|
||
return mostSpecificMatch != null; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
...ibraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/PreAuthCredentialCache.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Collections; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
|
||
namespace System.Net.Http | ||
{ | ||
internal sealed class PreAuthCredentialCache | ||
{ | ||
private Dictionary<CredentialCacheKey, NetworkCredential>? _cache; | ||
|
||
public void Add(Uri uriPrefix, string authType, NetworkCredential cred) | ||
{ | ||
Debug.Assert(uriPrefix != null); | ||
Debug.Assert(authType != null); | ||
|
||
var key = new CredentialCacheKey(uriPrefix, authType); | ||
|
||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Adding key:[{key}], cred:[{cred.Domain}],[{cred.UserName}]"); | ||
|
||
_cache ??= new Dictionary<CredentialCacheKey, NetworkCredential>(); | ||
_cache.Add(key, cred); | ||
} | ||
|
||
public void Remove(Uri uriPrefix, string authType) | ||
{ | ||
Debug.Assert(uriPrefix != null); | ||
Debug.Assert(authType != null); | ||
|
||
if (_cache == null) | ||
{ | ||
return; | ||
} | ||
|
||
var key = new CredentialCacheKey(uriPrefix, authType); | ||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Removing key:[{key}]"); | ||
_cache.Remove(key); | ||
} | ||
|
||
public (Uri uriPrefix, NetworkCredential credential)? GetCredential(Uri uriPrefix, string authType) | ||
{ | ||
Debug.Assert(uriPrefix != null); | ||
Debug.Assert(authType != null); | ||
|
||
if (_cache == null) | ||
{ | ||
return null; | ||
} | ||
|
||
CredentialCacheHelper.TryGetCredential(_cache, uriPrefix, authType, out Uri? mostSpecificMatchUri, out NetworkCredential? mostSpecificMatch); | ||
|
||
if (NetEventSource.Log.IsEnabled()) NetEventSource.Info(this, $"Returning {(mostSpecificMatch == null ? "null" : "(" + mostSpecificMatch.UserName + ":" + mostSpecificMatch.Domain + ")")}"); | ||
|
||
return mostSpecificMatch == null ? null : (mostSpecificMatchUri!, mostSpecificMatch!); | ||
} | ||
} | ||
} |
Oops, something went wrong.