/// <inheritdoc/> public override Credential GetCredentials(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Credential credentials = null; if ((credentials = PersonalAccessTokenStore.ReadCredentials(targetUri)) != null) { Trace.WriteLine("successfully retrieved stored credentials, updating credential cache"); return(credentials); } // try for a refresh token var refreshCredentials = PersonalAccessTokenStore.ReadCredentials(GetRefreshTokenTargetUri(targetUri)); if (refreshCredentials == null) { // no refresh token return null return(credentials); } Credential refreshedCredentials = Task.Run(() => RefreshCredentials(targetUri, refreshCredentials.Password, null)).Result; if (refreshedCredentials == null) { // refresh failed return null return(credentials); } else { credentials = refreshedCredentials; } return(credentials); }
/// <inheritdoc/> public override void DeleteCredentials(TargetUri targetUri, string username) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("BitbucketAuthentication::DeleteCredentials"); Credential credentials = null; if ((credentials = PersonalAccessTokenStore.ReadCredentials(targetUri)) != null) { // try to delete the credentials for the explicit target uri first PersonalAccessTokenStore.DeleteCredentials(targetUri); Trace.WriteLine($"host credentials deleted for {targetUri.ActualUri}"); } // tidy up and refresh tokens var refreshTargetUri = GetRefreshTokenTargetUri(targetUri); if ((credentials = PersonalAccessTokenStore.ReadCredentials(refreshTargetUri)) != null) { // try to delete the credentials for the explicit target uri first PersonalAccessTokenStore.DeleteCredentials(refreshTargetUri); Trace.WriteLine($"host refresh credentials deleted for {refreshTargetUri.ActualUri}"); } }
private async Task <AuthenticationResult> MakeOAuthAdminRequest(HttpClient httpClient, string url, MultipartFormDataContent content) { using (HttpResponseMessage response = await httpClient.PostAsync(url, content)) { 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)); } } }
/// <summary> /// Use locally stored refresh_token to attempt to retrieve a new access_token. /// </summary> /// <param name="targetUri"></param> /// <param name="refreshToken"></param> /// <param name="username"></param> /// <returns> /// A <see cref="Credential"/> containing the new access_token if successful, null otherwise /// </returns> private async Task <Credential> RefreshCredentials(TargetUri targetUri, string refreshToken, string username) { Credential credentials = null; AuthenticationResult result; if ((result = await BitbucketAuthority.RefreshToken(targetUri, refreshToken)) == true) { Trace.WriteLine("token refresh succeeded"); var tempCredentials = GenerateCredentials(targetUri, username, ref result); if (!await BitbucketAuthority.ValidateCredentials(targetUri, username, tempCredentials)) { // oddly our new access_token failed to work, maybe we've been revoked in the // last millisecond? return(credentials); } // the new access_token is good, so store it and store the refresh_token used to get it. SetCredentials(targetUri, tempCredentials, null); var newRefreshCredentials = GenerateRefreshCredentials(targetUri, username, ref result); SetCredentials(GetRefreshTokenTargetUri(targetUri), newRefreshCredentials, username); credentials = tempCredentials; } return(credentials); }
/// <inheritdoc/> public async Task <AuthenticationResult> AcquireToken(TargetUri targetUri, string username, string password, AuthenticationResultType resultType, TokenScope scope) { if (resultType == AuthenticationResultType.TwoFactor) { // a previous attempt to aquire a token failed in a way that suggests the user has // Bitbucket 2FA turned on. so attempt to run the OAuth dance... OAuth.OAuthAuthenticator oauth = new OAuth.OAuthAuthenticator(); try { var result = await oauth.GetAuthAsync(targetUri, scope, CancellationToken.None); if (!result.IsSuccess) { Trace.WriteLine($"oauth authentication failed"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } // we got a toke but lets check to see the usernames match var restRootUri = new Uri(_restRootUrl); var authHeader = GetBearerHeaderAuthHeader(result.Token.Value); var userResult = await RestClient.TryGetUser(targetUri, RequestTimeout, restRootUri, authHeader); if (!userResult.IsSuccess) { Trace.WriteLine($"oauth user check failed"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } if (!string.IsNullOrWhiteSpace(userResult.RemoteUsername) && !username.Equals(userResult.RemoteUsername)) { Trace.WriteLine($"Remote username [{userResult.RemoteUsername}] != [{username}] supplied username"); // make sure the 'real' username is returned return(new AuthenticationResult(AuthenticationResultType.Success, result.Token, result.RefreshToken, userResult.RemoteUsername)); } // everything is hunky dory return(result); } catch (Exception ex) { Trace.WriteLine($"oauth authentication failed [{ex.Message}]"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } } else { BasicAuthAuthenticator basicauth = new BasicAuthAuthenticator(); try { var restRootUri = new Uri(_restRootUrl); return(await basicauth.GetAuthAsync(targetUri, scope, RequestTimeout, restRootUri, username, password)); } catch (Exception ex) { Trace.WriteLine($"basic auth authentication failed [{ex.Message}]"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } } }
/// <summary> /// Opens a modal UI prompting the user to run the OAuth dance. /// </summary> /// <param name="title"></param> /// <param name="targetUri">contains the URL etc of the Authority</param> /// <param name="resultType"></param> /// <param name="username"></param> /// <returns> /// returns true if the user successfully completes the OAuth dance and the returned /// access_token is validated, false otherwise /// </returns> public static bool AuthenticationOAuthModalPrompt(string title, TargetUri targetUri, AuthenticationResultType resultType, string username) { var oauthViewModel = new OAuthViewModel(resultType == AuthenticationResultType.TwoFactor); Trace.WriteLine("prompting user for authentication code."); bool useOAuth = ShowViewModel(oauthViewModel, () => new OAuthWindow()); return(useOAuth); }
/// <inheritdoc/> public async Task <Credential> InteractiveLogon(TargetUri targetUri) { Credential credentials = null; string username; string password; // Ask the user for Basic Auth credentials if (AcquireCredentialsCallback("Please enter your Bitbucket credentials for ", targetUri, out username, out password)) { AuthenticationResult result; if (result = await BitbucketAuthority.AcquireToken(targetUri, username, password, AuthenticationResultType.None, TokenScope)) { Trace.WriteLine("token acquisition succeeded"); credentials = GenerateCredentials(targetUri, username, ref result); SetCredentials(targetUri, credentials, username); // if a result callback was registered, call it if (AuthenticationResultCallback != null) { AuthenticationResultCallback(targetUri, result); } return(credentials); } else if (result == AuthenticationResultType.TwoFactor) { // Basic Auth attempt returned a result indicating the user has 2FA on so prompt // the user to run the OAuth dance. if (AcquireAuthenticationOAuthCallback("", targetUri, result, username)) { if (result = await BitbucketAuthority.AcquireToken(targetUri, username, password, AuthenticationResultType.TwoFactor, TokenScope)) { Trace.WriteLine("token acquisition succeeded"); credentials = GenerateCredentials(targetUri, username, ref result); SetCredentials(targetUri, credentials, username); SetCredentials(GetRefreshTokenTargetUri(targetUri), new Credential(result.RefreshToken.Type.ToString(), result.RefreshToken.Value), username); // if a result callback was registered, call it if (AuthenticationResultCallback != null) { AuthenticationResultCallback(targetUri, result); } return(credentials); } } } } Trace.WriteLine("interactive logon failed"); return(credentials); }
/// <summary> /// Opens a Modal UI prompting the user for Basic Auth credentials. /// </summary> /// <param name="title"></param> /// <param name="targetUri">contains the URL etc of the Authority</param> /// <param name="username">the username entered by the user</param> /// <param name="password">the password entered by the user</param> /// <returns> /// returns true if the user provides credentials which are then successfully validated, /// false otherwise /// </returns> public static bool CredentialModalPrompt(string title, TargetUri targetUri, out string username, out string password) { // if there is a user in the remote URL then prepopulate the UI with it. var credentialViewModel = new CredentialsViewModel(GetUserFromTargetUri(targetUri)); Trace.WriteLine("prompting user for credentials."); bool credentialValid = ShowViewModel(credentialViewModel, () => new CredentialsWindow()); username = credentialViewModel.Login; password = credentialViewModel.Password; return(credentialValid); }
/// <inheritdoc/> public async Task <AuthenticationResult> RefreshToken(TargetUri targetUri, string refreshToken) { // Refreshing is only an OAuth concept so use the OAuth tools OAuth.OAuthAuthenticator oauth = new OAuth.OAuthAuthenticator(); try { return(await oauth.RefreshAuthAsync(targetUri, refreshToken, CancellationToken.None)); } catch (Exception ex) { Trace.WriteLine($"oauth refresh failed [{ex.Message}]"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } }
/// <summary> /// Validate the provided credentials, made up of the username and the contents if the /// authHeader, by making a request to a known Bitbucket REST API resource. A 200/Success /// response indicates the credentials are valid. Any other response indicates they are not. /// </summary> /// <param name="targetUri"> /// Contains the <see cref="HttpClientHandler"/> used when making the REST API request /// </param> /// <param name="username">the username to validate</param> /// <param name="authHeader"> /// the HTTP auth header containing the password/access_token to validate /// </param> /// <returns>true if the credentials are valid, false otherwise.</returns> private async Task <bool> ValidateCredentials(TargetUri targetUri, string username, string authHeader) { const string ValidationUrl = "https://api.bitbucket.org/2.0/user"; BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine($"Auth Type = {authHeader.Substring(0, 5)}"); // craft the request header for the Bitbucket v2 API w/ credentials 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("Authorization", authHeader); using (HttpResponseMessage response = await httpClient.GetAsync(ValidationUrl)) { switch (response.StatusCode) { case HttpStatusCode.OK: case HttpStatusCode.Created: { Trace.WriteLine("credential validation succeeded"); return(true); } case HttpStatusCode.Forbidden: { Trace.WriteLine("credential validation failed: Forbidden"); return(false); } case HttpStatusCode.Unauthorized: { Trace.WriteLine("credential validation failed: Unauthorized"); return(false); } default: { Trace.WriteLine("credential validation failed"); return(false); } } } } }
private AuthenticationResult GetAuthenticationResult(Token token, Token refreshToken) { // Bitbucket should always return both if (token == null || refreshToken == null) { Trace.WriteLine("authentication failure"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } else { Trace.WriteLine("authentication success: new personal access token created."); return(new AuthenticationResult(AuthenticationResultType.Success, token, refreshToken)); } }
/// <inheritdoc/> public void SetCredentials(TargetUri targetUri, Credential credentials, string username) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateCredential(credentials); Trace.WriteLine($"{credentials.Username} at {targetUri.ActualUri.AbsoluteUri}"); // if the url doesn't contain a username then save with an explicit username. if (!TargetUriContainsUsername(targetUri) && !string.IsNullOrWhiteSpace(username)) { Credential tempCredentials = new Credential(username, credentials.Password); SetCredentials(GetPerUserTargetUri(targetUri, username), tempCredentials, null); } PersonalAccessTokenStore.WriteCredentials(targetUri, credentials); }
/// <summary> /// Validate the provided credentials, made up of the username and the contents if the /// authHeader, by making a request to a known Bitbucket REST API resource. A 200/Success /// response indicates the credentials are valid. Any other response indicates they are not. /// </summary> /// <param name="targetUri"> /// Contains the <see cref="HttpClientHandler"/> used when making the REST API request /// </param> /// <param name="authHeader"> /// the HTTP auth header containing the password/access_token to validate /// </param> /// <returns>true if the credentials are valid, false otherwise.</returns> private async Task <bool> ValidateCredentials(TargetUri targetUri, string authHeader) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine($"Auth Type = {authHeader.Substring(0, 5)}"); var restRootUrl = new Uri(_restRootUrl); var result = await RestClient.TryGetUser(targetUri, RequestTimeout, restRootUrl, authHeader); if (result.Type.Equals(AuthenticationResultType.Success)) { Trace.WriteLine("credential validation succeeded"); return(true); } Trace.WriteLine("credential validation failed"); return(false); }
/// <inheritdoc/> public override void DeleteCredentials(TargetUri targetUri, string username) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine($"Deleting Bitbucket Credentials for {targetUri.ActualUri}"); Credential credentials = null; if ((credentials = PersonalAccessTokenStore.ReadCredentials(targetUri)) != null) { // try to delete the credentials for the explicit target uri first PersonalAccessTokenStore.DeleteCredentials(targetUri); Trace.WriteLine($"host credentials deleted for {targetUri.ActualUri}"); } // tidy up and delete any related refresh tokens var refreshTargetUri = GetRefreshTokenTargetUri(targetUri); if ((credentials = PersonalAccessTokenStore.ReadCredentials(refreshTargetUri)) != null) { // try to delete the credentials for the explicit target uri first PersonalAccessTokenStore.DeleteCredentials(refreshTargetUri); Trace.WriteLine($"host refresh credentials deleted for {refreshTargetUri.ActualUri}"); } // if we deleted per user then we shoudl try and delete the host level credentials too if // they match the username if (targetUri.TargetUriContainsUsername) { var hostTargetUri = targetUri.GetHostTargetUri(); var hostCredentials = GetCredentials(hostTargetUri); var encodedUsername = Uri.EscapeDataString(targetUri.TargetUriUsername); if (encodedUsername != username) { Trace.WriteLine($"username {username} != targetUri userInfo {encodedUsername}"); } if (hostCredentials != null && hostCredentials.Username.Equals(encodedUsername)) { DeleteCredentials(targetUri.GetHostTargetUri(), username); } } }
/// <inheritdoc/> public override void SetCredentials(TargetUri targetUri, Credential credentials) { // this is only called from the store() method so only applies to default host entries // calling this from elsewhere may have unintended consequences, use // SetCredentials(targetUri, credentials, username) instead // only store the credentials as received if they match the uri and user of the existing // default entry var currentCredentials = GetCredentials(targetUri); if (currentCredentials != null && currentCredentials.Username != null && !currentCredentials.Username.Equals(credentials.Username)) { // do nothing as the default is for another username and we don't want to overwrite it Trace.WriteLine($"skipping for {targetUri.ActualUri} new username {currentCredentials.Username} != {credentials.Username}"); return; } SetCredentials(targetUri, credentials, null); // Store() will not call with a username url if (targetUri.TargetUriContainsUsername) { return; } // see if there is a matching personal refresh token var username = credentials.Username; var userSpecificTargetUri = targetUri.GetPerUserTargetUri(username); var userCredentials = GetCredentials(userSpecificTargetUri, username); if (userCredentials != null && userCredentials.Password.Equals(credentials.Password)) { var userRefreshCredentials = GetCredentials(GetRefreshTokenTargetUri(userSpecificTargetUri), username); if (userRefreshCredentials != null) { Trace.WriteLine("OAuth RefreshToken"); var hostRefreshCredentials = new Credential(credentials.Username, userRefreshCredentials.Password); SetCredentials(GetRefreshTokenTargetUri(targetUri), hostRefreshCredentials, null); } } }
/// <summary> /// Identify the Hosting service from the the targetUri. /// </summary> /// <param name="targetUri"></param> /// <returns> /// A <see cref="BaseAuthentication"/> instance if the targetUri represents Bitbucket, null otherwise. /// </returns> public static BaseAuthentication GetAuthentication(TargetUri targetUri, ICredentialStore personalAccessTokenStore, AcquireCredentialsDelegate acquireCredentialsCallback, AcquireAuthenticationOAuthDelegate acquireAuthenticationOAuthCallback) { BaseAuthentication authentication = null; BaseSecureStore.ValidateTargetUri(targetUri); if (personalAccessTokenStore == null) { throw new ArgumentNullException(nameof(personalAccessTokenStore), $"The `{nameof(personalAccessTokenStore)}` is null or invalid."); } if (targetUri.ActualUri.DnsSafeHost.EndsWith(BitbucketBaseUrlHost, StringComparison.OrdinalIgnoreCase)) { authentication = new Authentication(personalAccessTokenStore, acquireCredentialsCallback, acquireAuthenticationOAuthCallback); Trace.WriteLine("authentication for Bitbucket created"); } else { authentication = null; } return(authentication); }
/// <inheritdoc/> public async Task <AuthenticationResult> AcquireToken(TargetUri targetUri, string username, string password, AuthenticationResultType resultType, TokenScope scope) { if (resultType == AuthenticationResultType.TwoFactor) { // a previous attempt to aquire a token failed in a way that suggests the user has // Bitbucket 2FA turned on. so attempt to run the OAuth dance... OAuth.OAuthAuthenticator oauth = new OAuth.OAuthAuthenticator(); try { return(await oauth.GetAuthAsync(targetUri, scope, CancellationToken.None)); } catch (Exception ex) { Trace.WriteLine($"oauth authentication failed [{ex.Message}]"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } } else { // use the provided username and password and attempt a Basic Auth request to a known // REST API resource. Token token = null; using (HttpClientHandler handler = targetUri.HttpClientHandler) { using (HttpClient httpClient = new HttpClient(handler) { Timeout = TimeSpan.FromMilliseconds(RequestTimeout) }) { string basicAuthValue = String.Format("{0}:{1}", username, password); byte[] authBytes = Encoding.UTF8.GetBytes(basicAuthValue); basicAuthValue = Convert.ToBase64String(authBytes); httpClient.DefaultRequestHeaders.Add("Authorization", "Basic " + basicAuthValue); var url = new Uri(new Uri(_restRootUrl), UserUrl).AbsoluteUri; using (HttpResponseMessage response = await httpClient.GetAsync(url)) { Trace.WriteLine($"server responded with {response.StatusCode}."); switch (response.StatusCode) { case HttpStatusCode.OK: case HttpStatusCode.Created: { // Success with username/passord indicates 2FA is not on so // the 'token' is actually the password if we had a // successful call then the password is good. token = new Token(password, TokenType.Personal); Trace.WriteLine("authentication success: new password token created."); return(new AuthenticationResult(AuthenticationResultType.Success, token)); } 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 failed"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } default: // any unexpected result can be treated as a failure. Trace.WriteLine("authentication failed"); return(new AuthenticationResult(AuthenticationResultType.Failure)); } } } } } }