示例#1
0
        public async Task <Token> GeneratePersonalAccessToken(TargetUri targetUri, Token authorization, TokenScope tokenScope, bool requireCompactToken, TimeSpan?tokenDuration)
        {
            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 = response.Content.AsString;

                            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}].");
                    }
            }
            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);
        }
        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          = GetTargetUrl(targetUri, false);
            var    locationServiceUrl = tenantUrl + LocationServiceUrlPathAndQuery;
            var    requestUri         = targetUri.CreateWith(queryUrl: locationServiceUrl);
            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(targetUri.CreateWith(idenitityServiceUri));
                            }
                        }
                    }

                    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 LocationServiceException($"Helper for `{targetUri}`.", exception);
            }

            return(null);
        }
示例#4
0
        internal async Task <bool> ValidateSecret(TargetUri targetUri, Secret secret)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }

            if (secret is null)
            {
                return(false);
            }

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

            try
            {
                // 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}].");

                    // Even if the service responded, if the issue isn't a 400 class response then the credentials were likely not rejected.
                    return((int)response.StatusCode < 400 ||
                           (int)response.StatusCode >= 500);
                }
            }
            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.WriteLine($"credential validation for '{targetUri}' failed.");
                Trace.WriteException(exception);
                return(false);
            }
        }
示例#5
0
        public async Task <AuthenticationResult> TryGetUser(TargetUri targetUri, int requestTimeout, Uri restRootUrl, Secret authorization)
        {
            var options = new NetworkRequestOptions(true)
            {
                Authorization = authorization,
                Timeout       = TimeSpan.FromMilliseconds(requestTimeout),
            };

            var apiUrl     = new Uri(restRootUrl, UserUrl);
            var requestUri = targetUri.CreateWith(apiUrl);

            using (var response = await Network.HttpGetAsync(requestUri, options))
            {
                Trace.WriteLine($"server responded with {response.StatusCode}.");

                switch (response.StatusCode)
                {
                case HttpStatusCode.OK:
                case HttpStatusCode.Created:
                {
                    Trace.WriteLine("authentication success: new password token created.");

                    // Get username to cross check against supplied one
                    var responseText = response.Content.AsString;
                    var username     = FindUsername(responseText);
                    return(new AuthenticationResult(AuthenticationResultType.Success, username));
                }

                case HttpStatusCode.Forbidden:
                {
                    // A 403/Forbidden response indicates the username/password are
                    // recognized and good but 2FA is on in which case we want to
                    // indicate that with the TwoFactor result
                    Trace.WriteLine("two-factor app authentication code required");
                    return(new AuthenticationResult(AuthenticationResultType.TwoFactor));
                }

                case HttpStatusCode.Unauthorized:
                {
                    // username or password are wrong.
                    Trace.WriteLine("authentication unauthorized");
                    return(new AuthenticationResult(AuthenticationResultType.Failure));
                }

                default:
                    // any unexpected result can be treated as a failure.
                    Trace.WriteLine("authentication failed");
                    return(new AuthenticationResult(AuthenticationResultType.Failure));
                }
            }
        }
示例#6
0
        /// <summary>
        /// Use a refresh_token to get a new access_token
        /// </summary>
        /// <param name="targetUri"></param>
        /// <param name="currentRefreshToken"></param>
        /// <returns></returns>
        private async Task <AuthenticationResult> RefreshAccessToken(TargetUri targetUri, string currentRefreshToken)
        {
            if (targetUri is null)
            {
                throw new ArgumentNullException(nameof(targetUri));
            }
            if (currentRefreshToken is null)
            {
                throw new ArgumentNullException(nameof(currentRefreshToken));
            }

            var refreshUri = GetRefreshUri();
            var requestUri = new TargetUri(refreshUri, targetUri.ProxyUri);
            var options    = new NetworkRequestOptions(true)
            {
                Timeout = TimeSpan.FromMilliseconds(RequestTimeout),
            };
            var content = GetRefreshRequestContent(currentRefreshToken);

            using (var response = await Network.HttpPostAsync(requestUri, content, options))
            {
                Trace.WriteLine($"server responded with {response.StatusCode}.");

                switch (response.StatusCode)
                {
                case HttpStatusCode.OK:
                case HttpStatusCode.Created:
                {
                    // the request was successful, look for the tokens in the response
                    string responseText = await response.Content.ReadAsStringAsync();

                    var token        = FindAccessToken(responseText);
                    var refreshToken = FindRefreshToken(responseText);
                    return(GetAuthenticationResult(token, refreshToken));
                }

                case HttpStatusCode.Unauthorized:
                {
                    // do something
                    return(new AuthenticationResult(AuthenticationResultType.Failure));
                }

                default:
                    Trace.WriteLine("authentication failed");
                    var error = response.Content.ReadAsStringAsync();
                    return(new AuthenticationResult(AuthenticationResultType.Failure));
                }
            }
        }
示例#7
0
        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}].");

                    // 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);
        }
示例#8
0
        public Task <INetworkResponseMessage> HttpHeadAsync(TargetUri targetUri, NetworkRequestOptions options)
        {
            if (!TryReadNext(nameof(HttpHeadAsync), targetUri, out CapturedNetworkQuery query))
            {
                throw new ReplayNotFoundException($"Failed to find replay data for \"{targetUri}\".");
            }
            if (query.Request.OptionFlags != (int)options.Flags)
            {
                throw new ReplayDataException($"Unexpected `{nameof(query.Request.OptionFlags)}`, expected {query.Request.OptionFlags} vs. actual {(int)options.Flags}.");
            }

            var headers = new NetworkResponseHeaders();

            headers.SetHeaders(query.Response.Headers);

            var response = new NetworkResponseMessage(null, headers, (HttpStatusCode)query.Response.StatusCode);

            return(Task.FromResult <INetworkResponseMessage>(response));
        }
        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));
            }

            // Allocate a network options object.
            var options = new NetworkRequestOptions(true)
            {
                Authorization = credentials,
                Timeout       = TimeSpan.FromMilliseconds(RequestTimeout),
            };

            // Create the validation Uri.
            var requestUri = new TargetUri(_validationUrl, targetUri.ProxyUri?.ToString());

            // Add Custom GitHub headers.
            options.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(GitHubApiAcceptsHeaderValue));

            using (var response = await Network.HttpGetAsync(requestUri, options))
            {
                if (response.IsSuccessStatusCode)
                {
                    Trace.WriteLine($"credential validation for '{targetUri}' succeeded.");
                    return(true);
                }
                else
                {
                    Trace.WriteLine($"credential validation for '{targetUri}' failed.");
                    return(false);
                }
            }
        }
        public static async Task <Guid?> DetectAuthority(RuntimeContext context, TargetUri targetUri)
        {
            const int    GuidStringLength         = 36;
            const string XvssResourceTenantHeader = "X-VSS-ResourceTenant";

            if (context is null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            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)
            {
                requestUri = targetUri.CreateWith(queryUri: targetUri.ActualUri);
            }

            // 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);

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

                // 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")}}}.");

                    return(tenantId);
                }

                var options = new NetworkRequestOptions(false)
                {
                    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 (response.Headers != 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))
                            {
                                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)
                                {
                                    foreach (var header in response.Headers.WwwAuthenticate)
                                    {
                                        const string AuthorizationUriPrefix = "authorization_uri=";

                                        var value = header.Parameter;

                                        if (value.Length >= AuthorizationUriPrefix.Length + AuthorityHostUrlBase.Length + 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('/');

                                            // Parse the header value if the necessary characters exist...
                                            if (index1 > 0 && index2 > index1)
                                            {
                                                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)
                                    {
                                        // 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);

                                            // 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")}}}.");

                                // 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}].");
                        }
                    }
                }
                catch (HttpRequestException exception)
                {
                    context.Trace.WriteLine($"unable to get response from '{requestUri}', an error occurred before the server could respond.");
                    context.Trace.WriteException(exception);
                }
            }
            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);
        }
示例#11
0
        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}].");

                            return(true);
                        }

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

                        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);
        }
示例#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 (IsVstsUrl(targetUri))
            {
                var tenantUrl = GetTargetUrl(targetUri, false);

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

                if (OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttp) ||
                    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 = targetUri.CreateWith(tenantUrl);

                        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);
        }
        public async Task <AuthenticationResult> AcquireToken(
            TargetUri targetUri,
            string username,
            string password,
            string authenticationCode,
            TokenScope scope)
        {
            const string GitHubOptHeader = "X-GitHub-OTP";

            Token token = null;

            var options = new NetworkRequestOptions(true)
            {
                Authorization = new Credential(username, password),
                Timeout       = TimeSpan.FromMilliseconds(RequestTimeout),
            };

            options.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(GitHubApiAcceptsHeaderValue));
            options.Headers.Add(GitHubOptHeader, authenticationCode);

            // Create the authority Uri.
            var requestUri = targetUri.CreateWith(_authorityUrl);

            using (HttpContent content = GetTokenJsonContent(targetUri, scope))
                using (var response = await Network.HttpPostAsync(requestUri, content, options))
                {
                    Trace.WriteLine($"server responded with {response.StatusCode}.");

                    switch (response.StatusCode)
                    {
                    case HttpStatusCode.OK:
                    case HttpStatusCode.Created:
                    {
                        string responseText = await response.Content.ReadAsStringAsync();

                        Match tokenMatch;
                        if ((tokenMatch = Regex.Match(responseText, @"\s*""token""\s*:\s*""([^""]+)""\s*", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success &&
                            tokenMatch.Groups.Count > 1)
                        {
                            string tokenText = tokenMatch.Groups[1].Value;
                            token = new Token(tokenText, TokenType.Personal);
                        }

                        if (token == null)
                        {
                            Trace.WriteLine($"authentication for '{targetUri}' failed.");
                            return(new AuthenticationResult(GitHubAuthenticationResultType.Failure));
                        }
                        else
                        {
                            Trace.WriteLine($"authentication success: new personal access token for '{targetUri}' created.");
                            return(new AuthenticationResult(GitHubAuthenticationResultType.Success, token));
                        }
                    }

                    case HttpStatusCode.Unauthorized:
                    {
                        if (string.IsNullOrWhiteSpace(authenticationCode) &&
                            response.Headers.Any(x => string.Equals(GitHubOptHeader, x.Key, StringComparison.OrdinalIgnoreCase)))
                        {
                            var mfakvp = response.Headers.First(x => string.Equals(GitHubOptHeader, x.Key, StringComparison.OrdinalIgnoreCase) && x.Value != null && x.Value.Count() > 0);

                            if (mfakvp.Value.First().Contains("app"))
                            {
                                Trace.WriteLine($"two-factor app authentication code required for '{targetUri}'.");
                                return(new AuthenticationResult(GitHubAuthenticationResultType.TwoFactorApp));
                            }
                            else
                            {
                                Trace.WriteLine($"two-factor sms authentication code required for '{targetUri}'.");
                                return(new AuthenticationResult(GitHubAuthenticationResultType.TwoFactorSms));
                            }
                        }
                        else
                        {
                            Trace.WriteLine($"authentication failed for '{targetUri}'.");
                            return(new AuthenticationResult(GitHubAuthenticationResultType.Failure));
                        }
                    }

                    case HttpStatusCode.Forbidden:
                        // This API only supports Basic authentication. If a valid OAuth token is supplied
                        // as the password, then a Forbidden response is returned instead of an Unauthorized.
                        // In that case, the supplied password is an OAuth token and is valid and we don't need
                        // to create a new personal access token.
                        var contentBody = await response.Content.ReadAsStringAsync();

                        if (contentBody.Contains("This API can only be accessed with username and password Basic Auth"))
                        {
                            Trace.WriteLine($"authentication success: user supplied personal access token for '{targetUri}'.");

                            return(new AuthenticationResult(GitHubAuthenticationResultType.Success, new Token(password, TokenType.Personal)));
                        }
                        Trace.WriteLine($"authentication failed for '{targetUri}'.");
                        return(new AuthenticationResult(GitHubAuthenticationResultType.Failure));

                    default:
                        Trace.WriteLine($"authentication failed for '{targetUri}'.");
                        return(new AuthenticationResult(GitHubAuthenticationResultType.Failure));
                    }
                }
        }
示例#14
0
        internal async Task <bool> ValidateSecret(TargetUri targetUri, Secret secret)
        {
            const string AnonymousUserPattern = @"""properties""\s*:\s*{\s*""Account""\s*:\s*{\s*""\$type""\s*:\s*""System.String""\s*,\s*""\$value""\s*:\s*""Anonymous""}\s*}";

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

            if (secret is null)
            {
                return(false);
            }

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

            try
            {
                // Send the request and wait for the response.
                using (var response = await Network.HttpGetAsync(requestUri, options))
                {
                    HttpStatusCode statusCode = response.StatusCode;
                    string         content    = response?.Content?.AsString;

                    // If the server responds with content, and said content matches the anonymous details the credentials are invalid.
                    if (content != null && Regex.IsMatch(content, AnonymousUserPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase))
                    {
                        Trace.WriteLine($"credential validation for '{requestUri?.QueryUri}' failed.");

                        return(false);
                    }

                    // If the service responded with a 2XX status code the credentials are valid.
                    if (statusCode >= HttpStatusCode.OK && statusCode < HttpStatusCode.Ambiguous)
                    {
                        return(true);
                    }

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

                    // Even if the service responded, if the issue isn't a 400 class response then the credentials were likely not rejected.
                    if (statusCode < HttpStatusCode.BadRequest || statusCode >= HttpStatusCode.InternalServerError)
                    {
                        return(true);
                    }
                }
            }
            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 '{requestUri?.QueryUri}', failure occurred before server could respond.");
                Trace.WriteException(exception);

                return(true);
            }
            catch (Exception exception)
            {
                Trace.WriteLine($"credential validation for '{requestUri?.QueryUri}' failed.");
                Trace.WriteException(exception);

                return(false);
            }

            Trace.WriteLine($"credential validation for '{requestUri?.QueryUri}' failed.");
            return(false);
        }