1
0
Форкнуть 0

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:
J Wyman 2016-10-26 11:54:01 -04:00
Родитель 0cb7c009ff
Коммит 14d1c5a1da
5 изменённых файлов: 225 добавлений и 5 удалений

Просмотреть файл

@ -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;
}
}
}
}
}