Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

devops: Remove HEAD request when not required #790 #795

Merged
merged 3 commits into from
Oct 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
206 changes: 103 additions & 103 deletions AzureDevOps.Authentication/Src/Authentication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,148 +177,148 @@ public override async Task<bool> DeleteCredentials(TargetUri targetUri)
if (targetUri is null)
throw new ArgumentNullException(nameof(targetUri));

// Assume Azure DevOps using Azure "common tenant" (empty GUID).
var tenantId = Guid.Empty;

// Compose the request Uri, by default it is the target Uri.
var requestUri = targetUri;

// Override the request Uri, when actual Uri exists, with actual Uri.
if (targetUri.ActualUri != null)
if (IsAzureDevOpsUrl(targetUri))
{
requestUri = targetUri.CreateWith(queryUri: targetUri.ActualUri);
}
// Assume Azure DevOps using Azure "common tenant" (empty GUID).
var tenantId = Guid.Empty;

// If the protocol (aka scheme) being used isn't HTTP based, there's no point in
// querying the server, so skip that work.
if (OrdinalIgnoreCase.Equals(requestUri.Scheme, Uri.UriSchemeHttp)
|| OrdinalIgnoreCase.Equals(requestUri.Scheme, Uri.UriSchemeHttps))
{
var requestUrl = GetTargetUrl(requestUri, false);
// Compose the request Uri, by default it is the target Uri.
var requestUri = targetUri;

// Read the cache from disk.
var cache = await DeserializeTenantCache(context);
// Override the request Uri, when actual Uri exists, with actual Uri.
if (targetUri.ActualUri != null)
{
requestUri = targetUri.CreateWith(queryUri: targetUri.ActualUri);
}

// Check the cache for an existing value.
if (cache.TryGetValue(requestUrl, out tenantId))
// If the protocol (aka scheme) being used isn't HTTP based, there's no point in
// querying the server, so skip that work.
if (OrdinalIgnoreCase.Equals(requestUri.Scheme, Uri.UriSchemeHttp)
|| OrdinalIgnoreCase.Equals(requestUri.Scheme, Uri.UriSchemeHttps))
{
context.Trace.WriteLine($"'{requestUrl}' is Azure DevOps, tenant resource is {{{tenantId.ToString("N")}}}.");
var requestUrl = GetTargetUrl(requestUri, false);

return tenantId;
}
// Read the cache from disk.
var cache = await DeserializeTenantCache(context);

// Use the properly formatted URL
requestUri = requestUri.CreateWith(queryUrl: requestUrl);
// Check the cache for an existing value.
if (cache.TryGetValue(requestUrl, out tenantId))
{
context.Trace.WriteLine($"'{requestUrl}' is Azure DevOps, tenant resource is {{{tenantId.ToString("N")}}}.");

var options = new NetworkRequestOptions(false)
{
Flags = NetworkRequestOptionFlags.UseProxy,
Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout),
};
return tenantId;
}

try
{
// Query the host use the response headers to determine if the host is Azure DevOps or not.
using (var response = await context.Network.HttpHeadAsync(requestUri, options))
// Use the properly formatted URL
requestUri = requestUri.CreateWith(queryUrl: requestUrl);

var options = new NetworkRequestOptions(false)
{
if (response.Headers != null)
Flags = NetworkRequestOptionFlags.UseProxy,
Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout),
};

try
{
// Query the host use the response headers to determine if the host is Azure DevOps or not.
using (var response = await context.Network.HttpHeadAsync(requestUri, options))
{
// If the "X-VSS-ResourceTenant" was returned, then it is Azure DevOps and we'll need it's value.
if (response.Headers.TryGetValues(XvssResourceTenantHeader, out IEnumerable<string> values))
if (response.Headers != null)
{
context.Trace.WriteLine($"detected '{requestUrl}' as Azure DevOps from GET response.");

// The "Www-Authenticate" is a more reliable header, because it indicates the
// authentication scheme that should be used to access the requested entity.
if (response.Headers.WwwAuthenticate != null)
// If the "X-VSS-ResourceTenant" was returned, then it is Azure DevOps and we'll need it's value.
if (response.Headers.TryGetValues(XvssResourceTenantHeader, out IEnumerable<string> values))
{
foreach (var header in response.Headers.WwwAuthenticate)
{
const string AuthorizationUriPrefix = "authorization_uri=";

var value = header.Parameter;
context.Trace.WriteLine($"detected '{requestUrl}' as Azure DevOps from GET response.");

if (value?.Length >= AuthorizationUriPrefix.Length + AuthorityHostUrlBase.Length + GuidStringLength)
// The "Www-Authenticate" is a more reliable header, because it indicates the
// authentication scheme that should be used to access the requested entity.
if (response.Headers.WwwAuthenticate != null)
{
foreach (var header in response.Headers.WwwAuthenticate)
{
// The header parameter will look something like "authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47"
// and all we want is the portion after the '=' and before the last '/'.
int index1 = value.IndexOf('=', AuthorizationUriPrefix.Length - 1);
int index2 = value.LastIndexOf('/');
const string AuthorizationUriPrefix = "authorization_uri=";

// Parse the header value if the necessary characters exist...
if (index1 > 0 && index2 > index1)
var value = header.Parameter;

if (value?.Length >= AuthorizationUriPrefix.Length + AuthorityHostUrlBase.Length + GuidStringLength)
{
var authorityUrl = value.Substring(index1 + 1, index2 - index1 - 1);
var guidString = value.Substring(index2 + 1, GuidStringLength);
// The header parameter will look something like "authorization_uri=https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47"
// and all we want is the portion after the '=' and before the last '/'.
int index1 = value.IndexOf('=', AuthorizationUriPrefix.Length - 1);
int index2 = value.LastIndexOf('/');

// If the authority URL is as expected, attempt to parse the tenant resource identity.
if (OrdinalIgnoreCase.Equals(authorityUrl, AuthorityHostUrlBase)
&& Guid.TryParse(guidString, out tenantId))
// Parse the header value if the necessary characters exist...
if (index1 > 0 && index2 > index1)
{
// Update the cache.
cache[requestUrl] = tenantId;

// Write the cache to disk.
await SerializeTenantCache(context, cache);

// Since we found a value, break the loop (likely a loop of one item anyways).
break;
var authorityUrl = value.Substring(index1 + 1, index2 - index1 - 1);
var guidString = value.Substring(index2 + 1, GuidStringLength);

// If the authority URL is as expected, attempt to parse the tenant resource identity.
if (OrdinalIgnoreCase.Equals(authorityUrl, AuthorityHostUrlBase)
&& Guid.TryParse(guidString, out tenantId))
{
// Update the cache.
cache[requestUrl] = tenantId;

// Write the cache to disk.
await SerializeTenantCache(context, cache);

// Since we found a value, break the loop (likely a loop of one item anyways).
break;
}
}
}
}
}
}
else
{
// Since there wasn't a "Www-Authenticate" header returned
// iterate through the values, taking the first non-zero value.
foreach (string value in values)
else
{
// Try to find a value for the resource-tenant identity.
// Given that some projects will return multiple tenant identities,
if (!string.IsNullOrWhiteSpace(value)
&& Guid.TryParse(value, out tenantId))
// Since there wasn't a "Www-Authenticate" header returned
// iterate through the values, taking the first non-zero value.
foreach (string value in values)
{
// Update the cache.
cache[requestUrl] = tenantId;
// Try to find a value for the resource-tenant identity.
// Given that some projects will return multiple tenant identities,
if (!string.IsNullOrWhiteSpace(value)
&& Guid.TryParse(value, out tenantId))
{
// Update the cache.
cache[requestUrl] = tenantId;

// Write the cache to disk.
await SerializeTenantCache(context, cache);
// Write the cache to disk.
await SerializeTenantCache(context, cache);

// Break the loop if a non-zero value has been detected.
if (tenantId != Guid.Empty)
{
break;
// Break the loop if a non-zero value has been detected.
if (tenantId != Guid.Empty)
{
break;
}
}
}
}
}

context.Trace.WriteLine($"tenant resource for '{requestUrl}' is {{{tenantId.ToString("N")}}}.");
context.Trace.WriteLine($"tenant resource for '{requestUrl}' is {{{tenantId.ToString("N")}}}.");

// Return the tenant identity to the caller because this is Azure DevOps.
return tenantId;
// Return the tenant identity to the caller because this is Azure DevOps.
return tenantId;
}
}
else
{
context.Trace.WriteLine($"unable to get response from '{requestUri}' [{(int)response.StatusCode} {response.StatusCode}].");
}
}
else
{
context.Trace.WriteLine($"unable to get response from '{requestUri}' [{(int)response.StatusCode} {response.StatusCode}].");
}
}
catch (HttpRequestException exception)
{
context.Trace.WriteLine($"unable to get response from '{requestUri}', an error occurred before the server could respond.");
context.Trace.WriteException(exception);
}
}
catch (HttpRequestException exception)
else
{
context.Trace.WriteLine($"unable to get response from '{requestUri}', an error occurred before the server could respond.");
context.Trace.WriteException(exception);
context.Trace.WriteLine($"detected non-http(s) based protocol: '{requestUri.Scheme}'.");
}
}
else
{
context.Trace.WriteLine($"detected non-http(s) based protocol: '{requestUri.Scheme}'.");
}

if (OrdinalIgnoreCase.Equals(VstsBaseUrlHost, requestUri.Host))
return Guid.Empty;

// Fallback to basic authentication.
return null;
Expand Down