public override async Task <ICredential> GenerateCredentialAsync(InputArguments input) { ThrowIfDisposed(); // We should not allow unencrypted communication and should inform the user if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http")) { throw new Exception("Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS."); } Uri remoteUri = input.GetRemoteUri(); string service = GetServiceName(input); AuthenticationModes authModes = await GetSupportedAuthenticationModesAsync(remoteUri); AuthenticationPromptResult promptResult = await _gitHubAuth.GetAuthenticationAsync(remoteUri, input.UserName, authModes); switch (promptResult.AuthenticationMode) { case AuthenticationModes.Basic: GitCredential patCredential = await GeneratePersonalAccessTokenAsync(remoteUri, promptResult.Credential); // HACK: Store the PAT immediately in case this PAT is not valid for SSO. // We don't know if this PAT is valid for SAML SSO and if it's not Git will fail // with a 403 and call neither 'store' or 'erase'. The user is expected to fiddle with // the PAT permissions manually on the web and then retry the Git operation. // We must store the PAT now so they can resume/repeat the operation with the same, // now SSO authorized, PAT. // See: https://github.com/GitCredentialManager/git-credential-manager/issues/133 Context.CredentialStore.AddOrUpdate(service, patCredential.Account, patCredential.Password); return(patCredential); case AuthenticationModes.Browser: return(await GenerateOAuthCredentialAsync(remoteUri, useBrowser : true)); case AuthenticationModes.Device: return(await GenerateOAuthCredentialAsync(remoteUri, useBrowser : false)); case AuthenticationModes.Pat: // The token returned by the user should be good to use directly as the password for Git string token = promptResult.Credential.Password; // Resolve the GitHub user handle if we don't have a specific username already from the // initial request. The reason for this is GitHub requires a (any?) value for the username // when Git makes calls to GitHub. string userName = promptResult.Credential.Account; if (userName is null) { GitHubUserInfo userInfo = await _gitHubApi.GetUserInfoAsync(remoteUri, token); userName = userInfo.Login; } return(new GitCredential(userName, token)); default: throw new ArgumentOutOfRangeException(nameof(promptResult)); } }
private async Task <GitCredential> GenerateOAuthCredentialAsync(Uri targetUri) { OAuth2TokenResult result = await _gitHubAuth.GetOAuthTokenAsync(targetUri, GitHubOAuthScopes); // Resolve the GitHub user handle GitHubUserInfo userInfo = await _gitHubApi.GetUserInfoAsync(targetUri, result.AccessToken); return(new GitCredential(userInfo.Login, result.AccessToken)); }
private async Task <GitCredential> GeneratePersonalAccessTokenAsync(Uri targetUri, ICredential credentials) { AuthenticationResult result = await _gitHubApi.CreatePersonalTokenAsync( targetUri, credentials, null, GitHubCredentialScopes); string token = null; if (result.Type == GitHubAuthenticationResultType.Success) { Context.Trace.WriteLine($"Token acquisition for '{targetUri}' succeeded"); token = result.Token; } else if (result.Type == GitHubAuthenticationResultType.TwoFactorApp || result.Type == GitHubAuthenticationResultType.TwoFactorSms) { bool isSms = result.Type == GitHubAuthenticationResultType.TwoFactorSms; string authCode = await _gitHubAuth.GetTwoFactorCodeAsync(targetUri, isSms); result = await _gitHubApi.CreatePersonalTokenAsync(targetUri, credentials, authCode, GitHubCredentialScopes); if (result.Type == GitHubAuthenticationResultType.Success) { Context.Trace.WriteLine($"Token acquisition for '{targetUri}' succeeded."); token = result.Token; } } if (token != null) { // Resolve the GitHub user handle GitHubUserInfo userInfo = await _gitHubApi.GetUserInfoAsync(targetUri, token); return(new GitCredential(userInfo.Login, token)); } throw new Exception($"Interactive logon for '{targetUri}' failed."); }