/// <summary>
        ///
        /// </summary>
        /// <param name="tokenScope"></param>
        /// <param name="personalAccessTokenStore"></param>
        public GitHubAuthentication(
            GitHubTokenScope tokenScope,
            ICredentialStore personalAccessTokenStore,
            AcquireCredentialsDelegate acquireCredentialsCallback,
            AcquireAuthenticationCodeDelegate acquireAuthenticationCodeCallback,
            AuthenticationResultDelegate authenticationResultCallback)
        {
            if (tokenScope == null)
            {
                throw new ArgumentNullException("tokenScope", "The parameter `tokenScope` is null or invalid.");
            }
            if (personalAccessTokenStore == null)
            {
                throw new ArgumentNullException("personalAccessTokenStore", "The parameter `personalAccessTokenStore` is null or invalid.");
            }
            if (acquireCredentialsCallback == null)
            {
                throw new ArgumentNullException("acquireCredentialsCallback", "The parameter `acquireCredentialsCallback` is null or invalid.");
            }
            if (acquireAuthenticationCodeCallback == null)
            {
                throw new ArgumentNullException("acquireAuthenticationCodeCallback", "The parameter `acquireAuthenticationCodeCallback` is null or invalid.");
            }

            TokenScope = tokenScope;

            PersonalAccessTokenStore = personalAccessTokenStore;
            GitHubAuthority          = new GitHubAuthority();

            AcquireCredentialsCallback        = acquireCredentialsCallback;
            AcquireAuthenticationCodeCallback = acquireAuthenticationCodeCallback;
            AuthenticationResultCallback      = authenticationResultCallback;
        }
        /// <summary>
        /// Gets a configured authentication object for 'github.com'.
        /// </summary>
        /// <param name="targetUri">The uniform resource indicator of the resource which requires
        /// authentication.</param>
        /// <param name="tokenScope">The desired scope of any personal access tokens acquired.</param>
        /// <param name="personalAccessTokenStore">A secure secret store for any personal access
        /// tokens acquired.</param>
        /// <param name="authentication">(out) The authentication object if successful.</param>
        /// <returns>True if success; otherwise false.</returns>
        public static bool GetAuthentication(
            TargetUri targetUri,
            GitHubTokenScope tokenScope,
            ICredentialStore personalAccessTokenStore,
            AcquireCredentialsDelegate acquireCredentialsCallback,
            AcquireAuthenticationCodeDelegate acquireAuthenticationCodeCallback,
            AuthenticationResultDelegate authenticationResultCallback,
            out BaseAuthentication authentication)
        {
            const string GitHubBaseUrlHost = "github.com";

            BaseSecureStore.ValidateTargetUri(targetUri);
            if (personalAccessTokenStore == null)
            {
                throw new ArgumentNullException("personalAccessTokenStore", "The `personalAccessTokenStore` is null or invalid.");
            }

            Trace.WriteLine("GitHubAuthentication::GetAuthentication");

            if (targetUri.ActualUri.DnsSafeHost.EndsWith(GitHubBaseUrlHost, StringComparison.OrdinalIgnoreCase))
            {
                authentication = new GitHubAuthentication(tokenScope, personalAccessTokenStore, acquireCredentialsCallback, acquireAuthenticationCodeCallback, authenticationResultCallback);
                Trace.WriteLine("   authentication for GitHub created");
            }
            else
            {
                authentication = null;
                Trace.WriteLine("   not github.com, authentication creation aborted");
            }

            return(authentication != null);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="tokenScope"></param>
        /// <param name="personalAccessTokenStore"></param>
        public GitHubAuthentication(
            GitHubTokenScope tokenScope,
            ICredentialStore personalAccessTokenStore,
            AcquireCredentialsDelegate acquireCredentialsCallback,
            AcquireAuthenticationCodeDelegate acquireAuthenticationCodeCallback,
            AuthenticationResultDelegate authenticationResultCallback)
        {
            if (tokenScope == null)
                throw new ArgumentNullException("tokenScope", "The parameter `tokenScope` is null or invalid.");
            if (personalAccessTokenStore == null)
                throw new ArgumentNullException("personalAccessTokenStore", "The parameter `personalAccessTokenStore` is null or invalid.");
            if (acquireCredentialsCallback == null)
                throw new ArgumentNullException("acquireCredentialsCallback", "The parameter `acquireCredentialsCallback` is null or invalid.");
            if (acquireAuthenticationCodeCallback == null)
                throw new ArgumentNullException("acquireAuthenticationCodeCallback", "The parameter `acquireAuthenticationCodeCallback` is null or invalid.");

            TokenScope = tokenScope;

            PersonalAccessTokenStore = personalAccessTokenStore;
            GitHubAuthority = new GitHubAuthority();

            AcquireCredentialsCallback = acquireCredentialsCallback;
            AcquireAuthenticationCodeCallback = acquireAuthenticationCodeCallback;
            AuthenticationResultCallback = authenticationResultCallback;
        }
Example #4
0
        public async Task <GitHubAuthenticationResult> AcquireToken(
            TargetUri targetUri,
            string username,
            string password,
            string authenticationCode,
            GitHubTokenScope scope)
        {
            const string GitHubOptHeader = "X-GitHub-OTP";

            Trace.WriteLine("GitHubAuthority::AcquireToken");

            Token token = null;

            using (HttpClientHandler handler = new HttpClientHandler()
            {
                MaxAutomaticRedirections = 2,
                UseDefaultCredentials = true
            })
                using (HttpClient httpClient = new HttpClient(handler)
                {
                    Timeout = TimeSpan.FromMilliseconds(RequestTimeout)
                })
                {
                    httpClient.DefaultRequestHeaders.Add("User-Agent", Global.GetUserAgent());
                    httpClient.DefaultRequestHeaders.Add("Accept", GitHubApiAcceptsHeaderValue);

                    string basicAuthValue = String.Format("{0}:{1}", username, password);
                    byte[] authBytes      = Encoding.UTF8.GetBytes(basicAuthValue);
                    basicAuthValue = Convert.ToBase64String(authBytes);

                    httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + basicAuthValue);

                    if (!String.IsNullOrWhiteSpace(authenticationCode))
                    {
                        httpClient.DefaultRequestHeaders.Add(GitHubOptHeader, authenticationCode);
                    }

                    const string HttpJsonContentType = "application/x-www-form-urlencoded";
                    const string JsonContentFormat   = @"{{ ""scopes"": {0}, ""note"": ""git: {1} on {2} at {3:dd-MMM-yyyy HH:mm}"" }}";

                    StringBuilder scopesBuilder = new StringBuilder();
                    scopesBuilder.Append('[');

                    foreach (var item in scope.ToString().Split(' '))
                    {
                        scopesBuilder.Append("\"")
                        .Append(item)
                        .Append("\"")
                        .Append(", ");
                    }

                    // remove trailing ", "
                    if (scopesBuilder.Length > 0)
                    {
                        scopesBuilder.Remove(scopesBuilder.Length - 2, 2);
                    }

                    scopesBuilder.Append(']');

                    string jsonContent = String.Format(JsonContentFormat, scopesBuilder, targetUri, Environment.MachineName, DateTime.Now);

                    using (StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType))
                        using (HttpResponseMessage response = await httpClient.PostAsync(_authorityUrl, content))
                        {
                            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.Compiled | 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 failure");
                                    return(new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure));
                                }
                                else
                                {
                                    Trace.WriteLine("   authentication success: new personal acces token created.");
                                    return(new GitHubAuthenticationResult(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");
                                        return(new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorApp));
                                    }
                                    else
                                    {
                                        Trace.WriteLine("   two-factor sms authentication code required");
                                        return(new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorSms));
                                    }
                                }
                                else
                                {
                                    Trace.WriteLine("   authentication failed");
                                    return(new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure));
                                }
                            }

                            default:
                                Trace.WriteLine("   authentication failed");
                                return(new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure));
                            }
                        }
                }
        }
        /// <summary>
        /// Gets a configured authentication object for 'github.com'.
        /// </summary>
        /// <param name="targetUri">The uniform resource indicator of the resource which requires
        /// authentication.</param>
        /// <param name="tokenScope">The desired scope of any personal access tokens acquired.</param>
        /// <param name="personalAccessTokenStore">A secure secret store for any personal access
        /// tokens acquired.</param>
        /// <param name="authentication">(out) The authentication object if successful.</param>
        /// <returns>True if success; otherwise false.</returns>
        public static BaseAuthentication GetAuthentication(
            TargetUri targetUri,
            GitHubTokenScope tokenScope,
            ICredentialStore personalAccessTokenStore,
            AcquireCredentialsDelegate acquireCredentialsCallback,
            AcquireAuthenticationCodeDelegate acquireAuthenticationCodeCallback,
            AuthenticationResultDelegate authenticationResultCallback)
        {
            const string GitHubBaseUrlHost = "github.com";

            BaseAuthentication authentication = null;

            BaseSecureStore.ValidateTargetUri(targetUri);
            if (personalAccessTokenStore == null)
                throw new ArgumentNullException("personalAccessTokenStore", "The `personalAccessTokenStore` is null or invalid.");

            if (targetUri.DnsSafeHost.EndsWith(GitHubBaseUrlHost, StringComparison.OrdinalIgnoreCase))
            {
                authentication = new GitHubAuthentication(tokenScope, personalAccessTokenStore, acquireCredentialsCallback, acquireAuthenticationCodeCallback, authenticationResultCallback);
                Git.Trace.WriteLine($"created GitHub authentication for '{targetUri}'.");
            }
            else
            {
                authentication = null;
                Git.Trace.WriteLine($"not github.com, authentication creation aborted.");
            }

            return authentication;
        }
        public async Task<GitHubAuthenticationResult> AcquireToken(
            TargetUri targetUri,
            string username,
            string password,
            string authenticationCode,
            GitHubTokenScope scope)
        {
            const string GitHubOptHeader = "X-GitHub-OTP";

            Token token = null;

            using (HttpClientHandler handler = targetUri.HttpClientHandler)
            using (HttpClient httpClient = new HttpClient(handler)
            {
                Timeout = TimeSpan.FromMilliseconds(RequestTimeout)
            })
            {
                httpClient.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent);
                httpClient.DefaultRequestHeaders.Add("Accept", GitHubApiAcceptsHeaderValue);

                string basicAuthValue = String.Format("{0}:{1}", username, password);
                byte[] authBytes = Encoding.UTF8.GetBytes(basicAuthValue);
                basicAuthValue = Convert.ToBase64String(authBytes);

                httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + basicAuthValue);

                if (!String.IsNullOrWhiteSpace(authenticationCode))
                {
                    httpClient.DefaultRequestHeaders.Add(GitHubOptHeader, authenticationCode);
                }

                const string HttpJsonContentType = "application/x-www-form-urlencoded";
                const string JsonContentFormat = @"{{ ""scopes"": {0}, ""note"": ""git: {1} on {2} at {3:dd-MMM-yyyy HH:mm}"" }}";

                StringBuilder scopesBuilder = new StringBuilder();
                scopesBuilder.Append('[');

                foreach (var item in scope.ToString().Split(' '))
                {
                    scopesBuilder.Append("\"")
                                 .Append(item)
                                 .Append("\"")
                                 .Append(", ");
                }

                // remove trailing ", "
                if (scopesBuilder.Length > 0)
                {
                    scopesBuilder.Remove(scopesBuilder.Length - 2, 2);
                }

                scopesBuilder.Append(']');

                string jsonContent = String.Format(JsonContentFormat, scopesBuilder, targetUri, Environment.MachineName, DateTime.Now);

                using (StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType))
                using (HttpResponseMessage response = await httpClient.PostAsync(_authorityUrl, content))
                {
                    Git.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.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success
                                    && tokenMatch.Groups.Count > 1)
                                {
                                    string tokenText = tokenMatch.Groups[1].Value;
                                    token = new Token(tokenText, TokenType.Personal);
                                }

                                if (token == null)
                                {
                                    Git.Trace.WriteLine($"authentication for '{targetUri}' failed.");
                                    return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
                                }
                                else
                                {
                                    Git.Trace.WriteLine($"authentication success: new personal acces token for '{targetUri}' created.");
                                    return new GitHubAuthenticationResult(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"))
                                    {
                                        Git.Trace.WriteLine($"two-factor app authentication code required for '{targetUri}'.");
                                        return new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorApp);
                                    }
                                    else
                                    {
                                        Git.Trace.WriteLine($"two-factor sms authentication code required for '{targetUri}'.");
                                        return new GitHubAuthenticationResult(GitHubAuthenticationResultType.TwoFactorSms);
                                    }
                                }
                                else
                                {
                                    Git.Trace.WriteLine($"authentication failed for '{targetUri}'.");
                                    return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
                                }
                            }

                        default:
                            Git.Trace.WriteLine($"authentication failed for '{targetUri}'.");
                            return new GitHubAuthenticationResult(GitHubAuthenticationResultType.Failure);
                    }
                }
            }
        }