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));
        }
示例#2
0
        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);
        }
示例#5
0
        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);
        }
示例#6
0
        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));
        }
示例#13
0
        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;
 }