private async Task <ICredential> GetOAuthCredentialsViaInteractiveBrowserFlow(Uri remoteUri) { var refreshTokenService = GetRefreshTokenServiceName(remoteUri); // We failed to use the refresh token either because it didn't exist, or because the refresh token is no // longer valid. Either way we must now try authenticating using OAuth interactively. // Start OAuth authentication flow _context.Trace.WriteLine("Starting OAuth authentication flow..."); OAuth2TokenResult oauthResult = await _bitbucketAuth.CreateOAuthCredentialsAsync(remoteUri); // Resolve the username _context.Trace.WriteLine("Resolving username for OAuth credential..."); string newUserName = await ResolveOAuthUserNameAsync(oauthResult.AccessToken); _context.Trace.WriteLine($"Username for OAuth credential is '{newUserName}'"); // Store the new RT _context.Trace.WriteLine("Storing new refresh token..."); _context.CredentialStore.AddOrUpdate(refreshTokenService, newUserName, oauthResult.RefreshToken); _context.Trace.WriteLine("Refresh token was successfully stored."); // Return the new AT as the credential return(new GitCredential(newUserName, oauthResult.AccessToken)); }
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)); }
public async Task OAuth2Client_E2E_DeviceFlowAndRefresh() { const string expectedUserCode = "736998"; const string expectedDeviceCode = "db6558b2a1d649758394ac3c2d9e00b1"; const string expectedAccessToken1 = "LET_ME_IN-1"; const string expectedAccessToken2 = "LET_ME_IN-2"; const string expectedRefreshToken1 = "REFRESH_ME-1"; const string expectedRefreshToken2 = "REFRESH_ME-2"; var baseUri = new Uri("https://example.com"); OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri); var httpHandler = new TestHttpMessageHandler { ThrowOnUnexpectedRequest = true }; string[] expectedScopes = { "read", "write", "delete" }; OAuth2Application app = CreateTestApplication(); var server = new TestOAuth2Server(endpoints); server.RegisterApplication(app); server.Bind(httpHandler); server.TokenGenerator.UserCodes.Add(expectedUserCode); server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode); server.TokenGenerator.AccessTokens.Add(expectedAccessToken1); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken1); OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2DeviceCodeResult deviceResult = await client.GetDeviceCodeAsync(expectedScopes, CancellationToken.None); // Simulate the user taking some time to sign in with the user code Thread.Sleep(1000); server.SignInDeviceWithUserCode(deviceResult.UserCode); OAuth2TokenResult result1 = await client.GetTokenByDeviceCodeAsync(deviceResult, CancellationToken.None); Assert.NotNull(result1); Assert.Equal(expectedScopes, result1.Scopes); Assert.Equal(expectedAccessToken1, result1.AccessToken); Assert.Equal(expectedRefreshToken1, result1.RefreshToken); server.TokenGenerator.AccessTokens.Add(expectedAccessToken2); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken2); OAuth2TokenResult result2 = await client.GetTokenByRefreshTokenAsync(result1.RefreshToken, CancellationToken.None); Assert.NotNull(result2); Assert.Equal(expectedScopes, result2.Scopes); Assert.Equal(expectedAccessToken2, result2.AccessToken); Assert.Equal(expectedRefreshToken2, result2.RefreshToken); }
public async Task OAuth2Client_E2E_InteractiveWebFlowAndRefresh() { const string expectedAuthCode = "e78a711d11"; const string expectedAccessToken1 = "LET_ME_IN-1"; const string expectedAccessToken2 = "LET_ME_IN-2"; const string expectedRefreshToken1 = "REFRESH_ME-1"; const string expectedRefreshToken2 = "REFRESH_ME-2"; var baseUri = new Uri("https://example.com"); OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri); var httpHandler = new TestHttpMessageHandler { ThrowOnUnexpectedRequest = true }; string[] expectedScopes = { "read", "write", "delete" }; OAuth2Application app = CreateTestApplication(); var server = new TestOAuth2Server(endpoints); server.RegisterApplication(app); server.Bind(httpHandler); server.TokenGenerator.AuthCodes.Add(expectedAuthCode); server.TokenGenerator.AccessTokens.Add(expectedAccessToken1); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken1); IOAuth2WebBrowser browser = new TestOAuth2WebBrowser(httpHandler); OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2AuthorizationCodeResult authCodeResult = await client.GetAuthorizationCodeAsync( expectedScopes, browser, CancellationToken.None); OAuth2TokenResult result1 = await client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None); Assert.NotNull(result1); Assert.Equal(expectedScopes, result1.Scopes); Assert.Equal(expectedAccessToken1, result1.AccessToken); Assert.Equal(expectedRefreshToken1, result1.RefreshToken); server.TokenGenerator.AccessTokens.Add(expectedAccessToken2); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken2); OAuth2TokenResult result2 = await client.GetTokenByRefreshTokenAsync(result1.RefreshToken, CancellationToken.None); Assert.NotNull(result2); Assert.Equal(expectedScopes, result2.Scopes); Assert.Equal(expectedAccessToken2, result2.AccessToken); Assert.Equal(expectedRefreshToken2, result2.RefreshToken); }
public async Task GitHubHostProvider_GenerateCredentialAsync_OAuth_ReturnsCredential() { var input = new InputArguments(new Dictionary <string, string> { ["protocol"] = "https", ["host"] = "github.com", }); var expectedTargetUri = new Uri("https://github.com/"); IEnumerable <string> expectedOAuthScopes = new[] { GitHubConstants.OAuthScopes.Repo, GitHubConstants.OAuthScopes.Gist, GitHubConstants.OAuthScopes.Workflow, }; var expectedUserName = "******"; var tokenValue = "OAUTH-TOKEN"; var response = new OAuth2TokenResult(tokenValue, "bearer"); var context = new TestCommandContext(); var ghAuthMock = new Mock <IGitHubAuthentication>(MockBehavior.Strict); ghAuthMock.Setup(x => x.GetAuthenticationAsync(expectedTargetUri, null, It.IsAny <AuthenticationModes>())) .ReturnsAsync(new AuthenticationPromptResult(AuthenticationModes.OAuth)); ghAuthMock.Setup(x => x.GetOAuthTokenAsync(expectedTargetUri, It.IsAny <IEnumerable <string> >())) .ReturnsAsync(response); var ghApiMock = new Mock <IGitHubRestApi>(MockBehavior.Strict); ghApiMock.Setup(x => x.GetUserInfoAsync(expectedTargetUri, tokenValue)) .ReturnsAsync(new GitHubUserInfo { Login = expectedUserName }); var provider = new GitHubHostProvider(context, ghApiMock.Object, ghAuthMock.Object); ICredential credential = await provider.GenerateCredentialAsync(input); Assert.NotNull(credential); Assert.Equal(expectedUserName, credential.Account); Assert.Equal(tokenValue, credential.Password); ghAuthMock.Verify( x => x.GetOAuthTokenAsync( expectedTargetUri, expectedOAuthScopes), Times.Once); }
protected override bool TryCreateTokenEndpointResult(string json, out OAuth2TokenResult result) { // We override the token endpoint response parsing because the Bitbucket authority returns // the non-standard 'scopes' property for the list of scopes, rather than the (optional) // 'scope' (note the singular vs plural) property as outlined in the standard. if (TryDeserializeJson(json, out BitbucketTokenEndpointResponseJson jsonObj)) { result = jsonObj.ToResult(); return(true); } result = null; return(false); }
private void VerifyOAuth2TokenResult(OAuth2TokenResult result) { Assert.NotNull(result); IEnumerable <char> access_token = null; Assert.Equal(access_token, result.AccessToken); IEnumerable <char> refresh_token = null; Assert.Equal(refresh_token, result.RefreshToken); IEnumerable <char> tokenType = null; Assert.Equal(tokenType, result.TokenType); Assert.Null(result.Scopes); }
public async Task OAuth2Client_GetTokenByDeviceCodeAsync() { const string expectedUserCode = "342728"; const string expectedDeviceCode = "ad6498533bf54f4db53e49612a4acfb0"; const string expectedAccessToken = "LET_ME_IN"; const string expectedRefreshToken = "REFRESH_ME"; var baseUri = new Uri("https://example.com"); OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri); var httpHandler = new TestHttpMessageHandler { ThrowOnUnexpectedRequest = true }; string[] expectedScopes = { "read", "write", "delete" }; var grant = new OAuth2Application.DeviceCodeGrant(expectedUserCode, expectedDeviceCode, expectedScopes); OAuth2Application app = CreateTestApplication(); app.DeviceGrants.Add(grant); var server = new TestOAuth2Server(endpoints); server.RegisterApplication(app); server.Bind(httpHandler); server.TokenGenerator.UserCodes.Add(expectedUserCode); server.TokenGenerator.DeviceCodes.Add(expectedDeviceCode); server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); OAuth2Client client = CreateClient(httpHandler, endpoints); var deviceCodeResult = new OAuth2DeviceCodeResult(expectedDeviceCode, expectedUserCode, null, null); Task <OAuth2TokenResult> resultTask = client.GetTokenByDeviceCodeAsync(deviceCodeResult, CancellationToken.None); // Simulate the user taking some time to sign in with the user code Thread.Sleep(1000); server.SignInDeviceWithUserCode(expectedUserCode); OAuth2TokenResult result = await resultTask; Assert.NotNull(result); Assert.Equal(expectedScopes, result.Scopes); Assert.Equal(expectedAccessToken, result.AccessToken); Assert.Equal(expectedRefreshToken, result.RefreshToken); }
private async Task <ICredential> GetOAuthCredentialsViaRefreshFlow(InputArguments input, string refreshTokenService, ICredential refreshToken) { _context.Trace.WriteLine("Refreshing OAuth credentials using refresh token..."); OAuth2TokenResult refreshResult = await _bitbucketAuth.RefreshOAuthCredentialsAsync(refreshToken.Password); // Resolve the username _context.Trace.WriteLine("Resolving username for refreshed OAuth credential..."); string refreshUserName = await ResolveOAuthUserNameAsync(refreshResult.AccessToken); _context.Trace.WriteLine($"Username for refreshed OAuth credential is '{refreshUserName}'"); // Store the refreshed RT _context.Trace.WriteLine("Storing new refresh token..."); _context.CredentialStore.AddOrUpdate(refreshTokenService, input.UserName, refreshResult.RefreshToken); // Return new access token return(new GitCredential(refreshUserName, refreshResult.AccessToken)); }
public async Task OAuth2Client_GetTokenByRefreshTokenAsync() { const string oldAccessToken = "OLD_LET_ME_IN"; const string oldRefreshToken = "OLD_REFRESH_ME"; const string expectedAccessToken = "NEW_LET_ME_IN"; const string expectedRefreshToken = "NEW_REFRESH_ME"; var baseUri = new Uri("https://example.com"); OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri); var httpHandler = new TestHttpMessageHandler { ThrowOnUnexpectedRequest = true }; string[] expectedScopes = { "read", "write", "delete" }; // Setup an existing access and refresh token OAuth2Application app = CreateTestApplication(); app.AccessTokens[oldAccessToken] = oldRefreshToken; app.RefreshTokens[oldRefreshToken] = expectedScopes; var server = new TestOAuth2Server(endpoints); server.RegisterApplication(app); server.Bind(httpHandler); server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); OAuth2Client client = CreateClient(httpHandler, endpoints); OAuth2TokenResult result = await client.GetTokenByRefreshTokenAsync(oldRefreshToken, CancellationToken.None); Assert.NotNull(result); Assert.Equal(expectedScopes, result.Scopes); Assert.Equal(expectedAccessToken, result.AccessToken); Assert.Equal(expectedRefreshToken, result.RefreshToken); }
public async Task OAuth2Client_GetTokenByAuthorizationCodeAsync() { const string authCode = "a63ef59691"; const string expectedAccessToken = "LET_ME_IN"; const string expectedRefreshToken = "REFRESH_ME"; var baseUri = new Uri("https://example.com"); OAuth2ServerEndpoints endpoints = CreateEndpoints(baseUri); var httpHandler = new TestHttpMessageHandler { ThrowOnUnexpectedRequest = true }; string[] expectedScopes = { "read", "write", "delete" }; OAuth2Application app = CreateTestApplication(); app.AuthGrants.Add(new OAuth2Application.AuthCodeGrant(authCode, expectedScopes)); var server = new TestOAuth2Server(endpoints); server.RegisterApplication(app); server.Bind(httpHandler); server.TokenGenerator.AccessTokens.Add(expectedAccessToken); server.TokenGenerator.RefreshTokens.Add(expectedRefreshToken); OAuth2Client client = CreateClient(httpHandler, endpoints); var authCodeResult = new OAuth2AuthorizationCodeResult(authCode, TestRedirectUri); OAuth2TokenResult result = await client.GetTokenByAuthorizationCodeAsync(authCodeResult, CancellationToken.None); Assert.NotNull(result); Assert.Equal(expectedScopes, result.Scopes); Assert.Equal(expectedAccessToken, result.AccessToken); Assert.Equal(expectedRefreshToken, result.RefreshToken); }
private async Task <ICredential> GenerateOAuthCredentialAsync(Uri targetUri) { OAuth2TokenResult result = await _gitHubAuth.GetOAuthTokenAsync(targetUri, GitHubOAuthScopes); return(new GitCredential(Constants.OAuthTokenUserName, result.AccessToken)); }
public async Task <ICredential> GetCredentialAsync(InputArguments input) { // Compute the target URI Uri targetUri = GetTargetUri(input); // We should not allow unencrypted communication and should inform the user if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") && !IsBitbucketServer(targetUri)) { throw new Exception("Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS."); } // Try and get the username specified in the remote URL if any string targetUriUser = targetUri.GetUserName(); // Check for presence of refresh_token entry in credential store string refreshKey = GetRefreshTokenKey(input); _context.Trace.WriteLine($"Checking for refresh token with key '{refreshKey}'..."); ICredential refreshToken = _context.CredentialStore.Get(refreshKey); if (refreshToken is null) { // There is no refresh token either because this is a non-2FA enabled account (where OAuth is not // required), or because we previously erased the RT. // Check for the presence of a credential in the store string credentialKey = GetCredentialKey(input); _context.Trace.WriteLine($"Checking for credentials with key '{credentialKey}'..."); ICredential credential = _context.CredentialStore.Get(credentialKey); if (credential is null) { // We don't have any credentials to use at all! Start with the assumption of no 2FA requirement // and capture username and password via an interactive prompt. credential = await _bitbucketAuth.GetBasicCredentialsAsync(targetUri, targetUriUser); if (credential is null) { throw new Exception("User cancelled authentication prompt."); } } // Either we have an existing credential (user/pass OR some form of token [PAT or AT]), // or we have a freshly captured user/pass. Regardless, we must check if these credentials // pass and two-factor requirement on the account. _context.Trace.WriteLine("Checking if two-factor requirements for stored credentials..."); bool requires2Fa = await RequiresTwoFactorAuthenticationAsync(credential, targetUri); if (!requires2Fa) { _context.Trace.WriteLine("Two-factor requirement passed with stored credentials"); // Return the valid credential return(credential); } _context.Trace.WriteLine("Two-factor authentication is required - prompting for auth via OAuth..."); // Show the 2FA/OAuth authentication required prompt bool @continue = await _bitbucketAuth.ShowOAuthRequiredPromptAsync(); if (!@continue) { throw new Exception("User cancelled OAuth authentication."); } // Fall through to the start of the interactive OAuth authentication flow } else { // TODO: should we try and compute if the AT has expired and use it? // This needs support from the credential store to record the expiry time! // It's very likely that any access token expired between the last time we used/stored it. // To ensure the AT is as 'fresh' as it can be, always first try to use the refresh token // (which lives longer) to create a new AT (and possibly also a new RT). try { _context.Trace.WriteLine("Refreshing OAuth credentials using refresh token..."); OAuth2TokenResult refreshResult = await _bitbucketAuth.RefreshOAuthCredentialsAsync(refreshToken.Password); // Resolve the username _context.Trace.WriteLine("Resolving username for refreshed OAuth credential..."); string refreshUserName = await ResolveOAuthUserNameAsync(refreshResult.AccessToken); _context.Trace.WriteLine($"Username for refreshed OAuth credential is '{refreshUserName}'"); // Store the refreshed RT _context.Trace.WriteLine($"Storing new refresh token with key '{refreshKey}'..."); _context.CredentialStore.AddOrUpdate(refreshKey, new GitCredential(refreshUserName, refreshResult.RefreshToken)); // Return new access token return(new GitCredential(refreshUserName, refreshResult.AccessToken)); } catch (OAuth2Exception ex) { _context.Trace.WriteLine("Failed to refresh existing OAuth credential using refresh token"); _context.Trace.WriteException(ex); // We failed to refresh the AT using the RT; log the refresh failure and fall through to restart // the OAuth authentication flow } } // We failed to use the refresh token either because it didn't exist, or because the refresh token is no // longer valid. Either way we must now try authenticating using OAuth interactively. // Start OAuth authentication flow _context.Trace.WriteLine("Starting OAuth authentication flow..."); OAuth2TokenResult oauthResult = await _bitbucketAuth.CreateOAuthCredentialsAsync(targetUri); // Resolve the username _context.Trace.WriteLine("Resolving username for OAuth credential..."); string newUserName = await ResolveOAuthUserNameAsync(oauthResult.AccessToken); _context.Trace.WriteLine($"Username for OAuth credential is '{newUserName}'"); // Store the new RT _context.Trace.WriteLine($"Storing new refresh token with key '{refreshKey}'..."); _context.CredentialStore.AddOrUpdate(refreshKey, new GitCredential(newUserName, oauthResult.RefreshToken)); _context.Trace.WriteLine("Refresh token was successfully stored."); // Return the new AT as the credential return(new GitCredential(newUserName, oauthResult.AccessToken)); }
private async Task <OAuthCredential> RefreshOAuthCredentialAsync(InputArguments input, string refreshToken) { OAuth2TokenResult result = await _gitLabAuth.GetOAuthTokenViaRefresh(input.GetRemoteUri(), refreshToken); return(new OAuthCredential(result)); }
private async Task <OAuthCredential> GenerateOAuthCredentialAsync(InputArguments input) { OAuth2TokenResult result = await _gitLabAuth.GetOAuthTokenViaBrowserAsync(input.GetRemoteUri(), GitLabOAuthScopes); return(new OAuthCredential(result)); }
public OAuthCredential(OAuth2TokenResult oAuth2TokenResult) { AccessToken = oAuth2TokenResult.AccessToken; RefreshToken = oAuth2TokenResult.RefreshToken; }