Skip to content

Commit 0eb7b94

Browse files
author
Dylan Lerch
committed
GetResponseWithRedirects only adds credentials to cache for matching schemes
The previous implementation of `ManagedHttpSmartSubtransportStream.GetResponseWithRedirects` was not checking that the scheme matched the provided credentials before adding to the credential cache (instead defaulting to the scheme of the first value in the WWW-Authenticate header). That could cause issues in some cases where the first challenge contained an unsupported scheme (such as Bearer). In this case, the old implemenation would add the credentials to the cache with Bearer scheme - regardless of the credential type - and all additional (potentially valid) schemes in the header would be discarded. Credentials are now added to the cache for every supported scheme that matches the given credential type. The new implementation copies the one used in the libgit2 library: Default credentials are used for the Negotiate scheme, username/password credentials are used for the NTLM and Basic schemes, and all others are ignored. The error in the current implementation can be reproduced when attempting to connect to a repository in an Azure AD-backed Azure DevOps organisation.
1 parent 7c78387 commit 0eb7b94

File tree

1 file changed

+43
-12
lines changed

1 file changed

+43
-12
lines changed

LibGit2Sharp/Core/ManagedHttpSmartSubtransport.cs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using System.Net;
@@ -47,6 +48,13 @@ private class ManagedHttpSmartSubtransportStream : SmartSubtransportStream
4748
{
4849
private static int MAX_REDIRECTS = 7;
4950

51+
private static readonly IReadOnlyDictionary<Type, string[]> SUPPORTED_SCHEMES_FOR_CREDENTIALS =
52+
new Dictionary<Type, string[]>
53+
{
54+
{ typeof(DefaultCredentials), new[] { "Negotiate" } },
55+
{ typeof(UsernamePasswordCredentials), new[] { "NTLM", "Basic" } }
56+
};
57+
5058
#if NETCOREAPP
5159
private static readonly SocketsHttpHandler httpHandler;
5260
#else
@@ -189,20 +197,14 @@ private HttpResponseMessage GetResponseWithRedirects()
189197
throw new InvalidOperationException("authentication cancelled");
190198
}
191199

192-
var scheme = response.Headers.WwwAuthenticate.First().Scheme;
193-
194-
if (cred is DefaultCredentials)
195-
{
196-
lock (credentialCache)
197-
{
198-
credentialCache.Add(url, scheme, CredentialCache.DefaultNetworkCredentials);
199-
}
200-
}
201-
else if (cred is UsernamePasswordCredentials userpass)
200+
foreach (var authenticationHeader in response.Headers.WwwAuthenticate)
202201
{
203-
lock (credentialCache)
202+
if (CredentialsAreValidForScheme(cred, authenticationHeader.Scheme, out var networkCredential))
204203
{
205-
credentialCache.Add(url, scheme, new NetworkCredential(userpass.Username, userpass.Password));
204+
lock (credentialCache)
205+
{
206+
credentialCache.Add(url, authenticationHeader.Scheme, networkCredential);
207+
}
206208
}
207209
}
208210

@@ -221,6 +223,35 @@ private HttpResponseMessage GetResponseWithRedirects()
221223
throw new Exception("too many redirects or authentication replays");
222224
}
223225

226+
bool IsSupportedAuth<T>(Credentials credentials, string scheme, out T typedCredentials) where T : Credentials
227+
{
228+
if (credentials is T innerTypedCredentials && SUPPORTED_SCHEMES_FOR_CREDENTIALS.TryGetValue(typeof(T), out var schemes) &&
229+
schemes.Contains(scheme, StringComparer.InvariantCultureIgnoreCase))
230+
{
231+
typedCredentials = innerTypedCredentials;
232+
return true;
233+
}
234+
235+
typedCredentials = null;
236+
return false;
237+
}
238+
239+
bool CredentialsAreValidForScheme(Credentials credentials, string scheme, out NetworkCredential networkCredential)
240+
{
241+
networkCredential = null;
242+
243+
if (IsSupportedAuth<DefaultCredentials>(credentials, scheme, out _))
244+
{
245+
networkCredential = CredentialCache.DefaultNetworkCredentials;
246+
}
247+
else if (IsSupportedAuth<UsernamePasswordCredentials>(credentials, scheme, out var usernamePasswordCredentials))
248+
{
249+
networkCredential = new NetworkCredential(usernamePasswordCredentials.Username, usernamePasswordCredentials.Password);
250+
}
251+
252+
return networkCredential != null;
253+
}
254+
224255
public override int Read(Stream dataStream, long length, out long readTotal)
225256
{
226257
byte[] buffer = new byte[4096];

0 commit comments

Comments
 (0)