public Task <INetworkResponseMessage> HttpHeadAsync(TargetUri targetUri, NetworkRequestOptions options)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            return(Task.Run <INetworkResponseMessage>(async() =>
            {
                using (var httpMessageHandler = GetHttpMessageHandler(targetUri, options))
                    using (var httpClient = GetHttpClient(targetUri, httpMessageHandler, options))
                        using (var requestMessage = new HttpRequestMessage(HttpMethod.Head, targetUri))
                        {
                            // Copy the headers from the client into the message because the framework
                            // will not do this when using `SendAsync`.
                            foreach (var header in httpClient.DefaultRequestHeaders)
                            {
                                requestMessage.Headers.Add(header.Key, header.Value);
                            }

                            var httpMessage = await httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead);
                            var response = new NetworkResponseMessage(httpMessage);

                            if (httpMessage.Content != null)
                            {
                                await response.SetContent(httpMessage.Content);
                            }

                            return response;
                        }
            }));
        }
        internal async Task <TargetUri> GetIdentityServiceUri(TargetUri targetUri, Secret authorization)
        {
            const string LocationServiceUrlPathAndQuery = "_apis/ServiceDefinitions/LocationService2/951917AC-A960-4999-8464-E3F0AA25B381?api-version=1.0";

            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (authorization is null)
            {
                throw new ArgumentNullException(nameof(authorization));
            }

            string tenantUrl = targetUri.ToString(false, true, false);

            if (targetUri.TargetUriContainsUsername)
            {
                string escapedUserInfo = Uri.EscapeUriString(targetUri.TargetUriUsername);
                tenantUrl = tenantUrl + escapedUserInfo + "/";
            }

            var locationServiceUrl = tenantUrl + LocationServiceUrlPathAndQuery;
            var requestUri         = new TargetUri(locationServiceUrl, targetUri.ProxyUri?.ToString());
            var options            = new NetworkRequestOptions(true)
            {
                Authorization = authorization,
            };

            try
            {
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        using (HttpContent content = response.Content)
                        {
                            string responseText = await content.ReadAsStringAsync();

                            Match match;
                            if ((match = Regex.Match(responseText, @"\""location\""\:\""([^\""]+)\""", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success)
                            {
                                string identityServiceUrl  = match.Groups[1].Value;
                                var    idenitityServiceUri = new Uri(identityServiceUrl, UriKind.Absolute);

                                return(new TargetUri(idenitityServiceUri, targetUri.ProxyUri));
                            }
                        }
                    }

                    Trace.WriteLine($"failed to find Identity Service for '{targetUri}' via location service [{(int)response.StatusCode} {response.ReasonPhrase}].");
                }
            }
            catch (Exception exception)
            {
                Trace.WriteException(exception);
                throw new VstsLocationServiceException($"Failed to find Identity Service for `{targetUri}`.", exception);
            }

            return(null);
        }
        public async Task <Token> GeneratePersonalAccessToken(TargetUri targetUri, Token authorization, VstsTokenScope tokenScope, bool requireCompactToken, TimeSpan?tokenDuration = null)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (authorization is null)
            {
                throw new ArgumentNullException(nameof(authorization));
            }
            if (tokenScope is null)
            {
                throw new ArgumentNullException(nameof(tokenScope));
            }

            try
            {
                var requestUri = await CreatePersonalAccessTokenRequestUri(targetUri, authorization, requireCompactToken);

                var options = new NetworkRequestOptions(true)
                {
                    Authorization = authorization,
                };

                using (StringContent content = GetAccessTokenRequestBody(targetUri, tokenScope, tokenDuration))
                    using (var response = await Network.HttpPostAsync(requestUri, content, options))
                    {
                        if (response.IsSuccessStatusCode)
                        {
                            string responseText = await response.Content.ReadAsStringAsync();

                            if (!string.IsNullOrWhiteSpace(responseText))
                            {
                                // Find the 'token : <value>' portion of the result content, if any.
                                Match tokenMatch = null;
                                if ((tokenMatch = Regex.Match(responseText, @"\s*""token""\s*:\s*""([^\""]+)""\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success)
                                {
                                    string tokenValue = tokenMatch.Groups[1].Value;
                                    Token  token      = new Token(tokenValue, TokenType.Personal);

                                    Trace.WriteLine($"personal access token acquisition for '{targetUri}' succeeded.");

                                    return(token);
                                }
                            }
                        }

                        Trace.WriteLine($"failed to acquire personal access token for '{targetUri}' [{(int)response.StatusCode} {response.ReasonPhrase}].");
                    }
            }
            catch (Exception exception)
            {
                Trace.WriteException(exception);
            }

            Trace.WriteLine($"personal access token acquisition for '{targetUri}' failed.");

            return(null);
        }
        public async Task <bool> PopulateTokenTargetId(TargetUri targetUri, Token authorization)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (authorization is null)
            {
                throw new ArgumentNullException(nameof(authorization));
            }

            try
            {
                // Create an request to the VSTS deployment data end-point.
                var requestUri = GetConnectionDataUri(targetUri);
                var options    = new NetworkRequestOptions(true)
                {
                    Authorization = authorization,
                };

                // Send the request and wait for the response.
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        string content = await response.Content.ReadAsStringAsync();

                        Match match;

                        if ((match = Regex.Match(content, @"""instanceId""\s*\:\s*""([^""]+)""", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success &&
                            match.Groups.Count == 2)
                        {
                            string resultId = match.Groups[1].Value;

                            if (Guid.TryParse(resultId, out Guid instanceId))
                            {
                                Trace.WriteLine($"target identity is '{resultId}'.");
                                authorization.TargetIdentity = instanceId;

                                return(true);
                            }
                        }
                    }

                    Trace.WriteLine($"failed to acquire the token's target identity for `{targetUri}` [{(int)response.StatusCode} {response.ReasonPhrase}].");
                }
            }
            catch (HttpRequestException exception)
            {
                Trace.WriteLine("failed to acquire the token's target identity for `{targetUri}`, an error happened before the server could respond.");
                Trace.WriteException(exception);
            }

            return(false);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Validates that a `<see cref="Token"/>` is valid to grant access to the VSTS resource referenced by `<paramref name="targetUri"/>`.
        /// <para/>
        /// Returns `<see langword="true"/>` if successful; otherwise `<see langword="false"/>`.
        /// </summary>
        /// <param name="targetUri">URI of the VSTS resource.</param>
        /// <param name="token">`<see cref="Token"/>` expected to grant access to the VSTS resource.</param>
        public async Task <bool> ValidateToken(TargetUri targetUri, Token token)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (token is null)
            {
                throw new ArgumentNullException(nameof(token));
            }

            // Personal access tokens are effectively credentials, treat them as such.
            if (token.Type == TokenType.Personal)
            {
                return(await ValidateCredentials(targetUri, (Credential)token));
            }

            try
            {
                // Create an request to the VSTS deployment data end-point.
                var requestUri = GetConnectionDataUri(targetUri);
                var options    = new NetworkRequestOptions(true)
                {
                    Authorization = token,
                };

                // Send the request and wait for the response.
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    if (!response.IsSuccessStatusCode)
                    {
                        // Even if the service responded, if the issue isn't a 400 class response then
                        // the credentials were likely not rejected.
                        if ((int)response.StatusCode >= 400 && (int)response.StatusCode < 500)
                        {
                            return(false);
                        }

                        Trace.WriteLine($"unable to validate credentials due to '{response.StatusCode}'.");
                        return(true);
                    }
                }
            }
            catch (Exception exception)
            {
                Trace.WriteLine($"! error: '{exception.Message}'.");
            };


            Trace.WriteLine($"token validation for '{targetUri}' failed.");
            return(false);
        }
Ejemplo n.º 6
0
        public static async Task <AuthenticationHeaderValue[]> GetHeaderValues(RuntimeContext context, TargetUri targetUri)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            if (targetUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.Ordinal) ||
                targetUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.Ordinal))
            {
                try
                {
                    // Configure the HTTP request to not choose an authentication strategy for us
                    // because we want to deliver the complete payload to the caller.
                    var options = new NetworkRequestOptions(false)
                    {
                        Flags   = NetworkRequestOptionFlags.UseProxy,
                        Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout),
                    };
                    var requestUri = targetUri;

                    // Since we're more likely to get an accurate response from the actual-URL,
                    // use it if available, otherwise use `targetUri` as supplied; but only if
                    // the actual-URL differs from the query-URL.
                    if (targetUri.ActualUri != null &&
                        targetUri.ActualUri != targetUri.QueryUri)
                    {
                        requestUri = targetUri.CreateWith(queryUri: targetUri.ActualUri);
                    }

                    // Make the request and return the response.
                    using (var result = await context.Network.HttpHeadAsync(requestUri, options))
                    {
                        return(result.Headers?.WwwAuthenticate?.ToArray()
                               ?? NullResult);
                    }
                }
                catch (Exception exception)
                {
                    context.Trace.WriteLine("error testing targetUri for NTLM.");
                    context.Trace.WriteException(exception);
                }
            }

            return(NullResult);
        }
        public async Task <HttpResponseMessage> HttpGetAsync(TargetUri targetUri, NetworkRequestOptions options)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            // Craft the request header for the GitHub v3 API w/ credentials;
            using (var handler = GetHttpMessageHandler(targetUri, options))
                using (var httpClient = GetHttpClient(targetUri, handler, options))
                {
                    return(await httpClient.GetAsync(targetUri));
                }
        }
        public async Task <bool> ValidateCredentials(TargetUri targetUri, Credential credentials)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (credentials is null)
            {
                throw new ArgumentNullException(nameof(credentials));
            }

            try
            {
                // Create an request to the VSTS deployment data end-point.
                var requestUri = GetConnectionDataUri(targetUri);
                var options    = new NetworkRequestOptions(true)
                {
                    Authorization = credentials,
                };

                // Send the request and wait for the response.
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    if (response.IsSuccessStatusCode)
                    {
                        return(true);
                    }

                    Trace.WriteLine($"credential validation for '{targetUri}' failed [{(int)response.StatusCode} {response.ReasonPhrase}].");

                    // Even if the service responded, if the issue isn't a 400 class response then
                    // the credentials were likely not rejected.
                    if ((int)response.StatusCode < 400 && (int)response.StatusCode >= 500)
                    {
                        return(true);
                    }
                }
            }
            catch (Exception exception)
            {
                Trace.WriteException(exception);
            }

            Trace.WriteLine($"credential validation for '{targetUri}' failed.");
            return(false);
        }
Ejemplo n.º 9
0
        public static async Task <AuthenticationHeaderValue[]> GetHeaderValues(RuntimeContext context, TargetUri targetUri)
        {
            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            if (targetUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.Ordinal) ||
                targetUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.Ordinal))
            {
                try
                {
                    // Configure the HTTP request to not choose an authentication strategy for us
                    // because we want to deliver the complete payload to the caller.
                    var options = new NetworkRequestOptions(false)
                    {
                        Flags   = NetworkRequestOptionFlags.UseProxy,
                        Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout),
                    };

                    // Make the request and return the response.
                    using (var result = await context.Network.HttpHeadAsync(targetUri, options))
                    {
                        return(result.Headers?.WwwAuthenticate?.ToArray()
                               ?? NullResult);
                    }
                }
                catch (Exception exception)
                {
                    context.Trace.WriteLine("error testing targetUri for NTLM.");
                    context.Trace.WriteException(exception);
                }
            }

            return(NullResult);
        }
        public Task <INetworkResponseMessage> HttpGetAsync(TargetUri targetUri, NetworkRequestOptions options)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            return(Task.Run <INetworkResponseMessage>(async() =>
            {
                using (var handler = GetHttpMessageHandler(targetUri, options))
                    using (var httpClient = GetHttpClient(targetUri, handler, options))
                    {
                        var httpMessage = await httpClient.GetAsync(targetUri);
                        var response = new NetworkResponseMessage(httpMessage);

                        if (httpMessage.Content != null)
                        {
                            await response.SetContent(httpMessage.Content);
                        }

                        return response;
                    }
            }));
        }
        public async Task <bool> ValidateToken(TargetUri targetUri, Token token)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (token is null)
            {
                throw new ArgumentNullException(nameof(token));
            }

            // Personal access tokens are effectively credentials, treat them as such.
            if (token.Type == TokenType.Personal)
            {
                return(await ValidateCredentials(targetUri, (Credential)token));
            }

            try
            {
                // Create an request to the VSTS deployment data end-point.
                var requestUri = GetConnectionDataUri(targetUri);
                var options    = new NetworkRequestOptions(true)
                {
                    Authorization = token,
                };

                // Send the request and wait for the response.
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    if (!response.IsSuccessStatusCode)
                    {
                        // Even if the service responded, if the issue isn't a 400 class response then
                        // the credentials were likely not rejected.
                        if ((int)response.StatusCode < 400 && (int)response.StatusCode >= 500)
                        {
                            Trace.WriteLine($"unable to validate credentials for '{targetUri}', unexpected response [{(int)response.StatusCode} {response.ReasonPhrase}].");

                            return(true);
                        }

                        Trace.WriteLine($"credential validation for '{targetUri}' failed [{(int)response.StatusCode} {response.ReasonPhrase}].");

                        return(false);
                    }
                }
            }
            catch (HttpRequestException exception)
            {
                // Since we're unable to invalidate the credentials, return optimistic results.
                // This avoid credential invalidation due to network instability, etc.
                Trace.WriteLine($"unable to validate credentials for '{targetUri}', failure occurred before server could respond.");
                Trace.WriteException(exception);

                return(true);
            }
            catch (Exception exception)
            {
                Trace.WriteException(exception);
            };

            Trace.WriteLine($"token validation for '{targetUri}' failed.");
            return(false);
        }
Ejemplo n.º 12
0
        public static async Task <Guid?> DetectAuthority(RuntimeContext context, TargetUri targetUri)
        {
            const string VstsResourceTenantHeader = "X-VSS-ResourceTenant";

            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            var tenantId = Guid.Empty;

            if (VstsAzureAuthority.IsVstsUrl(targetUri))
            {
                var tenantUrl = VstsAzureAuthority.GetTargetUrl(targetUri);

                context.Trace.WriteLine($"'{targetUri}' is a member '{tenantUrl}', checking AAD vs MSA.");

                if (StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttp) ||
                    StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttps))
                {
                    // Read the cache from disk.
                    var cache = await DeserializeTenantCache(context);

                    // Check the cache for an existing value.
                    if (cache.TryGetValue(tenantUrl, out tenantId))
                    {
                        return(tenantId);
                    }

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

                    try
                    {
                        var tenantUri = new TargetUri(tenantUrl, targetUri.ProxyUri?.ToString());

                        using (var response = await context.Network.HttpHeadAsync(tenantUri, options))
                        {
                            if (response.Headers != null && response.Headers.TryGetValues(VstsResourceTenantHeader, out IEnumerable <string> values))
                            {
                                foreach (string value in values)
                                {
                                    // Try to find a non-empty value for the resource-tenant identity
                                    if (!string.IsNullOrWhiteSpace(value) &&
                                        Guid.TryParse(value, out tenantId) &&
                                        tenantId != Guid.Empty)
                                    {
                                        // Update the cache.
                                        cache[tenantUrl] = tenantId;

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

                                        // Success, notify the caller
                                        return(tenantId);
                                    }
                                }

                                // Since we did not find a better identity, fallback to the default (Guid.Empty).
                                return(tenantId);
                            }
                            else
                            {
                                context.Trace.WriteLine($"unable to get response from '{targetUri}' [{(int)response.StatusCode} {response.StatusCode}].");
                            }
                        }
                    }
                    catch (HttpRequestException exception)
                    {
                        context.Trace.WriteLine($"unable to get response from '{targetUri}', an error occurred before the server could respond.");
                        context.Trace.WriteException(exception);
                    }
                }
                else
                {
                    context.Trace.WriteLine($"detected non-http(s) based protocol: '{targetUri.Scheme}'.");
                }
            }

            if (StringComparer.OrdinalIgnoreCase.Equals(VstsBaseUrlHost, targetUri.Host))
            {
                return(Guid.Empty);
            }

            // Fallback to basic authentication.
            return(null);
        }
        private HttpClient GetHttpClient(TargetUri targetUri, HttpMessageHandler handler, NetworkRequestOptions options)
        {
            var httpClient = new HttpClient(handler);

            if (options != null)
            {
                if (options.Timeout > TimeSpan.Zero)
                {
                    httpClient.Timeout = options.Timeout;
                }

                if (options.Headers != null)
                {
                    foreach (var header in options.Headers)
                    {
                        httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
                    }
                }

                // Manually add the correct headers for the type of authentication that is happening because if
                // we rely on the framework to correctly write the headers neither GitHub nor VSTS authentication
                // works reliably.
                if (options.Authorization != null)
                {
                    switch (options.Authorization)
                    {
                    case Token token:
                    {
                        // Different types of tokens are packed differently.
                        switch (token.Type)
                        {
                        case TokenType.AzureAccess:
                        case TokenType.BitbucketAccess:
                        {
                            // ADAL access tokens are packed into the Authorization header.
                            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value);
                        }
                        break;

                        case TokenType.AzureFederated:
                        {
                            // Federated authentication tokens are sent as cookie(s).
                            httpClient.DefaultRequestHeaders.Add("Cookie", token.Value);
                        }
                        break;

                        case TokenType.Personal:
                        {
                            // Personal access tokens are designed to treated like credentials,
                            // so treat them like credentials.
                            var credentials = (Credential)token;

                            // Credentials are packed into the 'Authorization' header as a base64 encoded pair.
                            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials.ToBase64String());
                        }
                        break;

                        default:
                            Trace.WriteLine("! unsupported token type, not appending an authentication header to the request.");
                            break;
                        }
                    }
                    break;

                    case Credential credentials:
                    {
                        // Credentials are packed into the 'Authorization' header as a base64 encoded pair.
                        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials.ToBase64String());
                    }
                    break;
                    }
                }
            }

            // Ensure that the user-agent string is set.
            if (httpClient.DefaultRequestHeaders.UserAgent is null ||
                httpClient.DefaultRequestHeaders.UserAgent.Count == 0)
            {
                httpClient.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent);
            }

            return(httpClient);
        }
        private static HttpMessageHandler GetHttpMessageHandler(TargetUri targetUri, NetworkRequestOptions options)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            var handler = new HttpClientHandler();

            if (options != null)
            {
                if ((options.Flags & NetworkRequestOptionFlags.AllowRedirections) != 0 && options.MaxRedirections <= 0)
                {
                    var inner = new ArgumentOutOfRangeException(nameof(options.MaxRedirections));
                    throw new ArgumentException("`NetworkRequestOption.MaxRedirections` must be greater than zero with `NetworkRequestOptionFlags.AllowAutoRedirect`.", nameof(options), inner);
                }

                if (options.CookieContainer is null && (options.Flags & NetworkRequestOptionFlags.UseCookies) != 0)
                {
                    var inner = new ArgumentNullException(nameof(options.CookieContainer));
                    throw new ArgumentException("`NetworkRequestOption.CookieContainer` cannot be null with `NetworkRequestOptionFlags.UseCookies`.", nameof(options), inner);
                }

                if ((options.Flags & NetworkRequestOptionFlags.UseCredentials) != 0 && options.Authorization is null)
                {
                    var inner = new ArgumentNullException(nameof(options.Authorization));
                    throw new ArgumentException("`NetworkRequestOption.Authentication` cannot be null with `NetworkRequestOptionFlags.UseCredentials`.", nameof(options), inner);
                }

                if ((options.Flags & NetworkRequestOptionFlags.AllowRedirections) == 0 ||
                    options.MaxRedirections <= 0)
                {
                    handler.AllowAutoRedirect = false;
                }
                else
                {
                    handler.AllowAutoRedirect        = true;
                    handler.MaxAutomaticRedirections = options.MaxRedirections;
                }

                if ((options.Flags & NetworkRequestOptionFlags.UseCookies) != 0 &&
                    options.CookieContainer != null)
                {
                    handler.CookieContainer = options.CookieContainer;
                    handler.UseCookies      = true;
                }

                if ((options.Flags & NetworkRequestOptionFlags.UseCredentials) != 0 &&
                    options.Authorization != null)
                {
                    handler.UseDefaultCredentials = false;
                }

                handler.PreAuthenticate = (options.Flags & NetworkRequestOptionFlags.PreAuthenticate) != 0;

                if ((options.Flags & NetworkRequestOptionFlags.UseProxy) != 0 &&
                    targetUri.ProxyUri != null)
                {
                    var proxy = GetHttpWebProxy(targetUri);

                    if (proxy != null)
                    {
                        handler.Proxy    = proxy;
                        handler.UseProxy = true;
                    }
                }
            }

            return(handler);
        }
Ejemplo n.º 15
0
        public static async Task <Guid?> DetectAuthority(RuntimeContext context, TargetUri targetUri)
        {
            const string VstsBaseUrlHost          = "visualstudio.com";
            const string VstsResourceTenantHeader = "X-VSS-ResourceTenant";

            BaseSecureStore.ValidateTargetUri(targetUri);

            var tenantId = Guid.Empty;

            if (targetUri.Host.EndsWith(VstsBaseUrlHost, StringComparison.OrdinalIgnoreCase))
            {
                context.Trace.WriteLine($"'{targetUri}' is subdomain of '{VstsBaseUrlHost}', checking AAD vs MSA.");

                string tenant = null;

                if (StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttp) ||
                    StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttps))
                {
                    // Query the cache first.
                    string tenantUrl = targetUri.ToString();

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

                    // Check the cache for an existing value.
                    if (cache.TryGetValue(tenantUrl, out tenantId))
                    {
                        return(tenantId);
                    }

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

                    try
                    {
                        using (var response = await context.Network.HttpGetAsync(targetUri, options))
                        {
                            if (response.IsSuccessStatusCode)
                            {
                                if (response.Headers.TryGetValues(VstsResourceTenantHeader, out IEnumerable <string> values))
                                {
                                    tenant = System.Linq.Enumerable.First(values);

                                    if (!string.IsNullOrWhiteSpace(tenant) &&
                                        Guid.TryParse(tenant, out tenantId))
                                    {
                                        // Update the cache.
                                        cache[tenantUrl] = tenantId;

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

                                        // Success, notify the caller
                                        return(tenantId);
                                    }
                                }
                            }
                            else
                            {
                                context.Trace.WriteLine($"unable to get response from '{targetUri}', server responded with '{(int)response.StatusCode} {response.StatusCode}'.");
                            }
                        }
                    }
                    catch (HttpRequestException exception)
                    {
                        context.Trace.WriteLine($"unable to get response from '{targetUri}' due to '{exception.Message}'.");
                    }
                }
                else
                {
                    context.Trace.WriteLine($"detected non-https based protocol: '{targetUri.Scheme}'.");
                }
            }

            // Fallback to basic authentication.
            return(null);
        }
        public async Task <HttpResponseMessage> HttpPostAsync(TargetUri targetUri, HttpContent content, NetworkRequestOptions options)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (content is null)
            {
                throw new ArgumentNullException(nameof(content));
            }

            using (var handler = GetHttpMessageHandler(targetUri, options))
                using (var httpClient = GetHttpClient(targetUri, handler, options))
                {
                    return(await httpClient.PostAsync(targetUri, content));
                }
        }