Add NTLM detection to `BasicAuthentication`.
Add a new `WwwAuthenticateHelper` to detect authentication type support of hosts via HTTP headers, specifically the 'WWW-Authenticate' header. Add dynamic callback support to the `BasicAuthentication` class, such that modal or basic prompts can be chosen when the class is created and invoked when `AcquireCredentials` is called. This, correctly, moves the logic for deciding what to call to the configuration stage and the the execution logic to the execution state. Add NTLM detection to `BasicAuthentication.AcquireCredentials` method, which wholly independent from the `GetCredentials` method now.
This commit is contained in:
Родитель
0cb7c009ff
Коммит
14d1c5a1da
|
@ -70,7 +70,7 @@ namespace Microsoft.Alm.Authentication.Test
|
|||
{
|
||||
ICredentialStore credentialStore = new SecretCache(@namespace);
|
||||
|
||||
return new BasicAuthentication(credentialStore);
|
||||
return new BasicAuthentication(credentialStore, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,4 +57,31 @@ namespace Microsoft.Alm.Authentication
|
|||
/// <param name="credentials">The value to be stored.</param>
|
||||
public abstract void SetCredentials(TargetUri targetUri, Credential credentials);
|
||||
}
|
||||
|
||||
public enum AcquireCredentialResult
|
||||
{
|
||||
Unknown,
|
||||
|
||||
Failed,
|
||||
Suceeded,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for interactively acquiring credentials.
|
||||
/// </summary>
|
||||
/// <param name="targetUri">
|
||||
/// The uniform resource indicator used to uniquely identify the credentials.
|
||||
/// </param>
|
||||
/// <returns>If successful a <see cref="Credential"/> object from the authentication object,
|
||||
/// authority or storage; otherwise <see langword="null"/>.</returns>
|
||||
public delegate Credential AcquireCredentialsDelegate(TargetUri targetUri);
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for interactivity related to credential acquisition.
|
||||
/// </summary>
|
||||
/// <param name="targetUri">
|
||||
/// The uniform resource indicator used to uniquely identify the credentials.
|
||||
/// </param>
|
||||
/// <param name="result">Result of previous attempt to acquire credentials.</param>
|
||||
public delegate void AcquireResultDelegate(TargetUri targetUri, AcquireCredentialResult result);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@
|
|||
**/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Alm.Authentication
|
||||
{
|
||||
|
@ -33,21 +35,98 @@ namespace Microsoft.Alm.Authentication
|
|||
/// </summary>
|
||||
public sealed class BasicAuthentication : BaseAuthentication, IAuthentication
|
||||
{
|
||||
public static readonly Credential NtlmCredentials = WwwAuthenticateHelper.Credentials;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BasicAuthentication"/> object with an underlying credential store.
|
||||
/// </summary>
|
||||
/// <param name="credentialStore">
|
||||
/// The <see cref="ICredentialStore"/> to delegate to.
|
||||
/// </param>
|
||||
public BasicAuthentication(ICredentialStore credentialStore)
|
||||
/// <param name="acquireCredentialsCallback">
|
||||
/// (optional) delegate for acquiring credentials.
|
||||
/// </param>
|
||||
/// <param name="acquireResultCallback">
|
||||
/// (optional) delegate for notification of acquisition results.
|
||||
/// </param>
|
||||
public BasicAuthentication(
|
||||
ICredentialStore credentialStore,
|
||||
AcquireCredentialsDelegate acquireCredentialsCallback,
|
||||
AcquireResultDelegate acquireResultCallback)
|
||||
{
|
||||
if (credentialStore == null)
|
||||
throw new ArgumentNullException(nameof(credentialStore));
|
||||
|
||||
this.CredentialStore = credentialStore;
|
||||
_acquireCredentials = acquireCredentialsCallback;
|
||||
_acquireResult = acquireResultCallback;
|
||||
_credentialStore = credentialStore;
|
||||
}
|
||||
|
||||
internal ICredentialStore CredentialStore { get; set; }
|
||||
public BasicAuthentication(ICredentialStore credentialStore)
|
||||
: this(credentialStore, null, null)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="BasicAuthentication"/> object with an underlying credential store.
|
||||
/// </summary>
|
||||
/// <param name="credentialStore">
|
||||
/// The <see cref="ICredentialStore"/> to delegate to.
|
||||
/// </param>
|
||||
internal ICredentialStore CredentialStore
|
||||
{
|
||||
get { return _credentialStore; }
|
||||
}
|
||||
|
||||
private readonly AcquireCredentialsDelegate _acquireCredentials;
|
||||
private readonly AcquireResultDelegate _acquireResult;
|
||||
private readonly ICredentialStore _credentialStore;
|
||||
private AuthenticationHeaderValue[] _httpAuthenticateOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Acquires credentials via the registered callbacks.
|
||||
/// </summary>
|
||||
/// <param name="targetUri">
|
||||
/// The uniform resource indicator used to uniquely identify the credentials.
|
||||
/// </param>
|
||||
/// <returns>If successful a <see cref="Credential"/> object from the authentication object,
|
||||
/// authority or storage; otherwise <see langword="null"/>.</returns>
|
||||
public async Task<Credential> AcquireCredentials(TargetUri targetUri)
|
||||
{
|
||||
BaseSecureStore.ValidateTargetUri(targetUri);
|
||||
|
||||
// get the WWW-Authenticate headers (if any)
|
||||
if (_httpAuthenticateOptions == null)
|
||||
{
|
||||
_httpAuthenticateOptions = await WwwAuthenticateHelper.GetHeaderValues(targetUri);
|
||||
}
|
||||
|
||||
// if the headers contain NTML as an option, then fall back to NTLM
|
||||
if (_httpAuthenticateOptions.Any(x=> WwwAuthenticateHelper.IsNtlm(x)))
|
||||
{
|
||||
Git.Trace.WriteLine($"'{targetUri}' supports NTLM, sending NTLM credentials instead");
|
||||
|
||||
return NtlmCredentials;
|
||||
}
|
||||
|
||||
Credential credentials = null;
|
||||
if (_acquireCredentials != null)
|
||||
{
|
||||
Git.Trace.WriteLine($"prompting user for credentials for '{targetUri}'.");
|
||||
|
||||
credentials = _acquireCredentials(targetUri);
|
||||
|
||||
if (_acquireResult != null)
|
||||
{
|
||||
AcquireCredentialResult result = (credentials == null)
|
||||
? AcquireCredentialResult.Failed
|
||||
: AcquireCredentialResult.Suceeded;
|
||||
|
||||
_acquireResult(targetUri, result);
|
||||
}
|
||||
}
|
||||
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <see cref="Credential"/> from the storage used by the authentication object.
|
||||
|
|
|
@ -65,6 +65,7 @@
|
|||
<Compile Include="Global.cs" />
|
||||
<Compile Include="IGitHubAuthentication.cs" />
|
||||
<Compile Include="IGitHubAuthority.cs" />
|
||||
<Compile Include="WwwAuthenticateHelper.cs" />
|
||||
<Compile Include="VstsLocationServiceException.cs" />
|
||||
<Compile Include="SecretCache.cs" />
|
||||
<Compile Include="TokenScope.cs" />
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/**** Git Credential Manager for Windows ****
|
||||
*
|
||||
* Copyright (c) Microsoft Corporation
|
||||
* All rights reserved.
|
||||
*
|
||||
* MIT License
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the """"Software""""), to deal
|
||||
* in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE."
|
||||
**/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Alm.Authentication
|
||||
{
|
||||
internal class WwwAuthenticateHelper
|
||||
{
|
||||
public static readonly Credential Credentials = new Credential(String.Empty, String.Empty);
|
||||
public static readonly AuthenticationHeaderValue NtlmHeader = new AuthenticationHeaderValue("NTLM");
|
||||
public static readonly AuthenticationHeaderValue NegotiateHeader = new AuthenticationHeaderValue("Negotiate");
|
||||
|
||||
private static readonly AuthenticationHeaderValue[] NullResult = new AuthenticationHeaderValue[0];
|
||||
|
||||
public static async Task<AuthenticationHeaderValue[]> GetHeaderValues(TargetUri targetUri)
|
||||
{
|
||||
BaseSecureStore.ValidateTargetUri(targetUri);
|
||||
|
||||
if (targetUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.Ordinal)
|
||||
|| targetUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.Ordinal))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Try the query uri
|
||||
string queryUrl = targetUri.QueryUri.ToString();
|
||||
// Try the actual uri
|
||||
string actualUrl = targetUri.ActualUri.ToString();
|
||||
|
||||
// Send off the HTTP requests and wait for them to complete
|
||||
var results = await Task.WhenAll(RequestAuthenticate(queryUrl, targetUri.HttpClientHandler),
|
||||
RequestAuthenticate(actualUrl, targetUri.HttpClientHandler));
|
||||
|
||||
// If any of then returned true, then we're looking at a TFS server
|
||||
HashSet<AuthenticationHeaderValue> set = new HashSet<AuthenticationHeaderValue>();
|
||||
|
||||
// combine the results into a unique set
|
||||
foreach (var result in results)
|
||||
{
|
||||
foreach (var item in result)
|
||||
{
|
||||
set.Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
return set.ToArray();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Git.Trace.WriteLine("error testing targetUri for NTML: " + exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return NullResult;
|
||||
}
|
||||
|
||||
public static bool IsNtlm(AuthenticationHeaderValue value)
|
||||
{
|
||||
return value?.Scheme != null
|
||||
&& value.Scheme.Equals(NtlmHeader.Scheme, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static async Task<AuthenticationHeaderValue[]> RequestAuthenticate(string targetUrl, HttpClientHandler httpClientHandler)
|
||||
{
|
||||
// configure the http client handler to not choose an authentication strategy for us
|
||||
// because we want to deliver the complete payload to the caller
|
||||
httpClientHandler.AllowAutoRedirect = false;
|
||||
httpClientHandler.PreAuthenticate = false;
|
||||
httpClientHandler.UseDefaultCredentials = false;
|
||||
|
||||
using (HttpClient client = new HttpClient(httpClientHandler))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent);
|
||||
client.Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout);
|
||||
|
||||
using (HttpResponseMessage response = await client.GetAsync(targetUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
{
|
||||
// Check for a WWW-Authenticate header with NTLM protocol specified
|
||||
return response.Headers.Contains("WWW-Authenticate")
|
||||
? response.Headers.WwwAuthenticate.ToArray()
|
||||
: NullResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче