/// <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 aqcuired.</param> /// <param name="personalAccessTokenStore">A secure secret store for any personal access /// tokens acquired.</param> /// <param name="authentication">(out) The authenitcation 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; }
internal static void ValidateTargetUri(TargetUri targetUri) { if (targetUri == null) throw new ArgumentNullException("targetUri"); if (!targetUri.IsAbsoluteUri || !targetUri.ActualUri.IsAbsoluteUri) throw new ArgumentException("The target must be an absolute URI.", "targetUri"); }
internal static void ValidateTargetUri(TargetUri targetUri) { if (ReferenceEquals(targetUri, null)) throw new ArgumentNullException(nameof(targetUri)); if (!targetUri.IsAbsoluteUri) throw new ArgumentException(nameof(targetUri.IsAbsoluteUri)); }
public static string UriToSimpleName(TargetUri targetUri, string @namespace) { const string TokenNameBaseFormat = "{0}:{1}://{2}"; const string TokenNamePortFormat = TokenNameBaseFormat + ":{3}"; Debug.Assert(targetUri != null, "The targetUri parameter is null"); Trace.WriteLine("Secret::UriToName"); string targetName = null; // trim any trailing slashes and/or whitespace for compat with git-credential-winstore string trimmedHostUrl = targetUri.Host .TrimEnd('/', '\\') .TrimEnd(); Uri resolvedUri = targetUri.ResolvedUri; if (resolvedUri.IsDefaultPort) { targetName = String.Format(TokenNameBaseFormat, @namespace, resolvedUri.Scheme, trimmedHostUrl); } else { targetName = String.Format(TokenNamePortFormat, @namespace, resolvedUri.Scheme, trimmedHostUrl, resolvedUri.Port); } Trace.WriteLine(" target name = " + targetName); return targetName; }
/// <summary> /// <para>Creates an interactive logon session, using ADAL secure browser GUI, which /// enables users to authenticate with the Azure tenant and acquire the necessary access /// tokens to exchange for a VSTS personal access token.</para> /// <para>Tokens acquired are stored in the secure secret stores provided during /// initialization.</para> /// </summary> /// <param name="targetUri">The unique identifier for the resource for which access is to /// be acquired.</param> /// <param name="requestCompactToken"> /// <para>Requests a compact format personal access token; otherwise requests a standard /// personal access token.</para> /// <para>Compact tokens are necessary for clients which have restrictions on the size of /// the basic authentication header which they can create (example: Git).</para> /// </param> /// <returns><see langword="true"/> if a authentication and personal access token acquisition was successful; otherwise <see langword="false"/>.</returns> public bool InteractiveLogon(TargetUri targetUri, bool requestCompactToken) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("VstsAadAuthentication::InteractiveLogon"); try { TokenPair tokens; if ((tokens = this.VstsAuthority.AcquireToken(targetUri, this.ClientId, this.Resource, new Uri(RedirectUrl), null)) != null) { Trace.WriteLine(" token aqusition succeeded."); this.StoreRefreshToken(targetUri, tokens.RefeshToken); return this.GeneratePersonalAccessToken(targetUri, tokens.AccessToken, requestCompactToken).Result; } } catch (AdalException) { Trace.WriteLine(" token aquisition failed."); } Trace.WriteLine(" interactive logon failed"); return false; }
/// <summary> /// Opens an interactive logon prompt to acquire acquire an authentication token from the /// Microsoft Live authentication and identity service. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator of the resource access tokens are being requested for. /// </param> /// <param name="requireCompactToken"> /// True if a compact access token is required; false if a standard token is acceptable. /// </param> /// <returns>True if successfull; otherwise false.</returns> public bool InteractiveLogon(TargetUri targetUri, bool requireCompactToken) { const string QueryParameters = "domain_hint=live.com&display=popup&site_id=501454&nux=1"; BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("VstsMsaAuthentication::InteractiveLogon"); try { TokenPair tokens; if ((tokens = this.VstsAuthority.AcquireToken(targetUri, this.ClientId, this.Resource, new Uri(RedirectUrl), QueryParameters)) != null) { Trace.WriteLine(" token successfully acquired."); this.StoreRefreshToken(targetUri, tokens.RefeshToken); return this.GeneratePersonalAccessToken(targetUri, tokens.AccessToken, requireCompactToken).Result; } } catch (AdalException exception) { Debug.Write(exception); } Trace.WriteLine(" failed to acquire token."); return false; }
/// <summary> /// acquires a <see cref="TokenPair"/> from the authority via an interactive user logon /// prompt. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator of the resource access tokens are being requested for. /// </param> /// <param name="clientId">Identifier of the client requesting the token.</param> /// <param name="resource"> /// Identifier of the target resource that is the recipient of the requested token. /// </param> /// <param name="redirectUri"> /// Address to return to upon receiving a response from the authority. /// </param> /// <param name="queryParameters"> /// Optional: appended as-is to the query string in the HTTP authentication request to the /// authority. /// </param> /// <returns>If successful a <see cref="TokenPair"/>; otherwise <see langword="null"/>.</returns> public TokenPair AcquireToken(TargetUri targetUri, string clientId, string resource, Uri redirectUri, string queryParameters = null) { Debug.Assert(targetUri != null && targetUri.IsAbsoluteUri, "The targetUri parameter is null"); Debug.Assert(!String.IsNullOrWhiteSpace(clientId), "The clientId parameter is null or empty"); Debug.Assert(!String.IsNullOrWhiteSpace(resource), "The resource parameter is null or empty"); Debug.Assert(redirectUri != null, "The redirectUri parameter is null"); Debug.Assert(redirectUri.IsAbsoluteUri, "The redirectUri parameter is not an absolute Uri"); Trace.WriteLine("AzureAuthority::AcquireToken"); TokenPair tokens = null; queryParameters = queryParameters ?? String.Empty; try { Trace.WriteLine(String.Format(" authority host url = '{0}'.", AuthorityHostUrl)); AuthenticationContext authCtx = new AuthenticationContext(AuthorityHostUrl, _adalTokenCache); AuthenticationResult authResult = authCtx.AcquireToken(resource, clientId, redirectUri, PromptBehavior.Always, UserIdentifier.AnyUser, queryParameters); tokens = new TokenPair(authResult); Trace.WriteLine(" token acquisition succeeded."); } catch (AdalException) { Trace.WriteLine(" token acquisition failed."); } return tokens; }
/// <summary> /// Deletes a <see cref="Credential"/> from the storage used by the authentication object. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator used to uniquely identify the credentials. /// </param> public override void DeleteCredentials(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("BasicAuthentication::DeleteCredentials"); this.CredentialStore.DeleteCredentials(targetUri); }
/// <summary> /// Gets a <see cref="Credential"/> from the storage used by the authentication object. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator used to uniquely identify the credentials. /// </param> /// <param name="credentials"> /// If successful a <see cref="Credential"/> object from the authentication object, /// authority or storage; otherwise <see langword="null"/>. /// </param> /// <returns><see langword="true"/> if successful; otherwise <see langword="false"/>.</returns> public override bool GetCredentials(TargetUri targetUri, out Credential credentials) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("BasicAuthentication::GetCredentials"); this.CredentialStore.ReadCredentials(targetUri, out credentials); return credentials != null; }
/// <summary> /// Sets a <see cref="Credential"/> in the storage used by the authentication object. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator used to uniquely identify the credentials. /// </param> /// <param name="credentials">The value to be stored.</param> /// <returns><see langword="true"/> if successful; otherwise <see langword="false"/>.</returns> public override bool SetCredentials(TargetUri targetUri, Credential credentials) { BaseSecureStore.ValidateTargetUri(targetUri); Credential.Validate(credentials); Trace.WriteLine("BasicAuthentication::SetCredentials"); this.CredentialStore.WriteCredentials(targetUri, credentials); return true; }
public static string UriToName(TargetUri targetUri, string @namespace) { BaseSecureStore.ValidateTargetUri(targetUri); if (String.IsNullOrWhiteSpace(@namespace)) throw new ArgumentNullException(@namespace); string targetName = $"{@namespace}:{targetUri}"; targetName = targetName.TrimEnd('/', '\\'); return targetName; }
/// <summary> /// Deletes the token for target URI from the token store /// </summary> /// <param name="targetUri">The URI of the target for which the token is being deleted</param> public void DeleteToken(TargetUri targetUri) { ValidateTargetUri(targetUri); Trace.WriteLine("TokenStore::ReadToken"); string targetName = this.GetTargetName(targetUri); this.Delete(targetName); _tokenCache.DeleteToken(targetUri); }
/// <summary> /// Reads a token from the current user's Visual Studio hive in the Windows Registry. /// </summary> /// <param name="targetUri">Key used to select the token.</param> /// <returns>A <see cref="Token"/> if successful; otherwise <see langword="null"/>.</returns> public Token ReadToken(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Token token = null; foreach (var key in EnumerateKeys(false)) { if (key == null) continue; string url; string type; string value; if (KeyIsValid(key, out url, out type, out value)) { try { Uri tokenUri = new Uri(url); if (tokenUri.IsBaseOf(targetUri)) { byte[] data = Convert.FromBase64String(value); data = ProtectedData.Unprotect(data, null, DataProtectionScope.CurrentUser); value = Encoding.UTF8.GetString(data); TokenType tokenType; if (String.Equals(type, "Federated", StringComparison.OrdinalIgnoreCase)) { tokenType = TokenType.Federated; } else { throw new InvalidOperationException("Unexpected token type encountered"); } token = new Token(value, tokenType); Git.Trace.WriteLine($"token for '{targetUri}' read from registry."); return token; } } catch { Git.Trace.WriteLine("! token read from registry was corrupt."); } } } return token; }
/// <summary> /// Deletes a <see cref="Credential"/> from the storage used by the authentication object. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator used to uniquely identify the credentials. /// </param> public override void DeleteCredentials(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Credential credentials = null; if ((credentials = this.PersonalAccessTokenStore.ReadCredentials(targetUri)) != null) { this.PersonalAccessTokenStore.DeleteCredentials(targetUri); Git.Trace.WriteLine($"credentials for '{targetUri}' deleted"); } }
/// <summary> /// Deletes credentials for target URI from the credential store /// </summary> /// <param name="targetUri">The URI of the target for which credentials are being deleted</param> public void DeleteCredentials(TargetUri targetUri) { ValidateTargetUri(targetUri); Trace.WriteLine("CredentialStore::DeleteCredentials"); string targetName = this.GetTargetName(targetUri); this.Delete(targetName); _credentialCache.DeleteCredentials(targetUri); }
/// <summary> /// Deletes a <see cref="Credential"/> from the storage used by the authentication object. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator used to uniquely identitfy the credentials. /// </param> public override void DeleteCredentials(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("GithubAuthentication::DeleteCredentials"); Credential credentials = null; if (this.PersonalAccessTokenStore.ReadCredentials(targetUri, out credentials)) { this.PersonalAccessTokenStore.DeleteCredentials(targetUri); Trace.WriteLine(" credentials deleted"); } }
public static bool CredentialModalPrompt(TargetUri targetUri, out string username, out string password) { var credentialViewModel = new CredentialsViewModel(); Git.Trace.WriteLine($"prompting user for credentials for '{targetUri}'."); bool credentialValid = ShowViewModel(credentialViewModel, () => new CredentialsWindow()); username = credentialViewModel.Login; password = credentialViewModel.Password; return credentialValid; }
public static bool AuthenticationCodeModalPrompt(TargetUri targetUri, GitHubAuthenticationResultType resultType, string username, out string authenticationCode) { var twoFactorViewModel = new TwoFactorViewModel(resultType == GitHubAuthenticationResultType.TwoFactorSms); Git.Trace.WriteLine($"prompting user for authentication code for '{targetUri}'."); bool authenticationCodeValid = ShowViewModel(twoFactorViewModel, () => new TwoFactorWindow()); authenticationCode = authenticationCodeValid ? twoFactorViewModel.AuthenticationCode : null; return authenticationCodeValid; }
/// <summary> /// Generates a personal access token for use with Visual Studio Online. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator of the resource access tokens are being requested for. /// </param> /// <param name="accessToken"></param> /// <param name="tokenScope"></param> /// <param name="requireCompactToken"></param> /// <returns></returns> public async Task<Token> GeneratePersonalAccessToken(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope, bool requireCompactToken) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(accessToken); if (ReferenceEquals(tokenScope, null)) throw new ArgumentNullException(nameof(tokenScope)); try { using (HttpClient httpClient = CreateHttpClient(targetUri, accessToken)) { if (await PopulateTokenTargetId(targetUri, accessToken)) { Uri requestUri = await CreatePersonalAccessTokenRequestUri(httpClient, targetUri, requireCompactToken); using (StringContent content = GetAccessTokenRequestBody(targetUri, accessToken, tokenScope)) using (HttpResponseMessage response = await httpClient.PostAsync(requestUri, content)) { if (response.IsSuccessStatusCode) { string responseText = await response.Content.ReadAsStringAsync(); if (!String.IsNullOrWhiteSpace(responseText)) { // find the 'token : <value>' portion of the result content, if any Match tokenMatch = null; if ((tokenMatch = Regex.Match(responseText, @"\s*""token""\s*:\s*""([^\""]+)""\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success) { string tokenValue = tokenMatch.Groups[1].Value; Token token = new Token(tokenValue, TokenType.Personal); Git.Trace.WriteLine($"personal access token acquisition for '{targetUri}' succeeded."); return token; } } } } } } } catch { Git.Trace.WriteLine("! an error occurred."); } Git.Trace.WriteLine($"personal access token acquisition for '{targetUri}' failed."); return null; }
/// <summary> /// Deletes a token from the cache. /// </summary> /// <param name="targetUri">The key which to find and delete the token with.</param> public void DeleteToken(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); Trace.WriteLine("SecretCache::DeleteToken"); string targetName = this.GetTargetName(targetUri); lock (_cache) { if (_cache.ContainsKey(targetName) && _cache[targetName] is Token) { _cache.Remove(targetName); } } }
public static async Task<AuthenticationHeaderValue[]> GetHeaderValues(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); if (targetUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.Ordinal) || targetUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.Ordinal)) { try { // Try the query uri string queryUrl = targetUri.QueryUri.ToString(); // Try the actual uri string actualUrl = targetUri.ActualUri.ToString(); // Send off the HTTP requests and wait for them to complete var results = await Task.WhenAll(RequestAuthenticate(queryUrl, targetUri.HttpClientHandler), RequestAuthenticate(actualUrl, targetUri.HttpClientHandler)); // If any of then returned true, then we're looking at a TFS server HashSet<AuthenticationHeaderValue> set = new HashSet<AuthenticationHeaderValue>(); // combine the results into a unique set foreach (var result in results) { foreach (var item in result) { set.Add(item); } } return set.ToArray(); } catch (Exception exception) { Git.Trace.WriteLine("error testing targetUri for NTML: " + exception.Message); } } return NullResult; }
/// <summary> /// Opens an interactive logon prompt to acquire acquire an authentication token from the /// Microsoft Live authentication and identity service. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator of the resource access tokens are being requested for. /// </param> /// <param name="requireCompactToken"> /// True if a compact access token is required; false if a standard token is acceptable. /// </param> /// <returns>A <see cref="Credential"/> for packing into a basic authentication header; /// otherwise <see langword="null"/>.</returns> public async Task<Credential> InteractiveLogon(TargetUri targetUri, bool requireCompactToken) { const string QueryParameters = "domain_hint=live.com&display=popup&site_id=501454&nux=1"; BaseSecureStore.ValidateTargetUri(targetUri); try { Token token; if ((token = await this.VstsAuthority.InteractiveAcquireToken(targetUri, this.ClientId, this.Resource, new Uri(RedirectUrl), QueryParameters)) != null) { Git.Trace.WriteLine($"token '{targetUri}' successfully acquired."); return await this.GeneratePersonalAccessToken(targetUri, token, requireCompactToken); } } catch (AdalException exception) { Debug.Write(exception); } Git.Trace.WriteLine($"failed to acquire token for '{targetUri}'."); return null; }
/// <summary> /// Not supported /// </summary> /// <param name="targetUri"></param> public void DeleteToken(TargetUri targetUri) { // we've decided to not support registry deletes until the rules are established throw new NotSupportedException("Deletes from the registry are not supported by this library."); }
protected abstract string GetTargetName(TargetUri targetUri);
private static async Task <Uri> CreatePersonalAccessTokenRequestUri(HttpClient client, TargetUri targetUri, bool requireCompactToken) { const string SessionTokenUrl = "_apis/token/sessiontokens?api-version=1.0"; const string CompactTokenUrl = SessionTokenUrl + "&tokentype=compact"; if (client == null) { throw new ArgumentNullException(nameof(client)); } BaseSecureStore.ValidateTargetUri(targetUri); Uri idenityServiceUri = await GetIdentityServiceUri(client, targetUri); if (idenityServiceUri == null) { throw new VstsLocationServiceException($"Failed to find Identity Service for {targetUri}"); } string url = idenityServiceUri.ToString(); url += requireCompactToken ? CompactTokenUrl : SessionTokenUrl; return(new Uri(url, UriKind.Absolute)); }
public static async Task <Guid?> DetectAuthority(RuntimeContext context, TargetUri targetUri) { const string VstsBaseUrlHost = "visualstudio.com"; const string VstsResourceTenantHeader = "X-VSS-ResourceTenant"; BaseSecureStore.ValidateTargetUri(targetUri); var tenantId = Guid.Empty; if (targetUri.Host.EndsWith(VstsBaseUrlHost, StringComparison.OrdinalIgnoreCase)) { context.Trace.WriteLine($"'{targetUri}' is subdomain of '{VstsBaseUrlHost}', checking AAD vs MSA."); string tenant = null; if (StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttp) || StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, Uri.UriSchemeHttps)) { // Query the cache first. string tenantUrl = targetUri.ToString(); // Read the cache from disk. var cache = await DeserializeTenantCache(context); // Check the cache for an existing value. if (cache.TryGetValue(tenantUrl, out tenantId)) { return(tenantId); } var options = new NetworkRequestOptions(false) { Flags = NetworkRequestOptionFlags.UseProxy, Timeout = TimeSpan.FromMilliseconds(Global.RequestTimeout), }; try { using (var response = await context.Network.HttpGetAsync(targetUri, options)) { if (response.IsSuccessStatusCode) { if (response.Headers.TryGetValues(VstsResourceTenantHeader, out IEnumerable <string> values)) { tenant = System.Linq.Enumerable.First(values); if (!string.IsNullOrWhiteSpace(tenant) && Guid.TryParse(tenant, out tenantId)) { // Update the cache. cache[tenantUrl] = tenantId; // Write the cache to disk. await SerializeTenantCache(context, cache); // Success, notify the caller return(tenantId); } } } else { context.Trace.WriteLine($"unable to get response from '{targetUri}', server responded with '{(int)response.StatusCode} {response.StatusCode}'."); } } } catch (HttpRequestException exception) { context.Trace.WriteLine($"unable to get response from '{targetUri}' due to '{exception.Message}'."); } } else { context.Trace.WriteLine($"detected non-https based protocol: '{targetUri.Scheme}'."); } } // Fallback to basic authentication. return(null); }
/// <summary> /// Not supported /// </summary> /// <param name="targetUri"></param> /// <param name="token"></param> public void WriteToken(TargetUri targetUri, Token token) { // we've decided to not support registry writes until the format is standardized throw new NotSupportedException("Writes to the registry are not supported by this library."); }
/// <summary> /// Reads a token for a target URI from the token store /// </summary> /// <param name="targetUri">The URI of the target for which a token is being read</param> /// <returns>A <see cref="Token"/> from the store is successful; otherwise <see langword="null"/>.</returns> public Token ReadToken(TargetUri targetUri) { ValidateTargetUri(targetUri); string targetName = this.GetTargetName(targetUri); return _tokenCache.ReadToken(targetUri) ?? ReadToken(targetName); }
/// <summary> /// Deletes the token for target URI from the token store /// </summary> /// <param name="targetUri">The URI of the target for which the token is being deleted</param> public void DeleteToken(TargetUri targetUri) { ValidateTargetUri(targetUri); string targetName = this.GetTargetName(targetUri); this.Delete(targetName); _tokenCache.DeleteToken(targetUri); }
private static HttpMessageHandler GetHttpMessageHandler(TargetUri targetUri, NetworkRequestOptions options) { if (targetUri is null) { throw new ArgumentNullException(nameof(targetUri)); } var handler = new HttpClientHandler(); if (options != null) { if ((options.Flags & NetworkRequestOptionFlags.AllowRedirections) != 0 && options.MaxRedirections <= 0) { var inner = new ArgumentOutOfRangeException(nameof(options.MaxRedirections)); throw new ArgumentException("`NetworkRequestOption.MaxRedirections` must be greater than zero with `NetworkRequestOptionFlags.AllowAutoRedirect`.", nameof(options), inner); } if (options.CookieContainer is null && (options.Flags & NetworkRequestOptionFlags.UseCookies) != 0) { var inner = new ArgumentNullException(nameof(options.CookieContainer)); throw new ArgumentException("`NetworkRequestOption.CookieContainer` cannot be null with `NetworkRequestOptionFlags.UseCookies`.", nameof(options), inner); } if ((options.Flags & NetworkRequestOptionFlags.UseCredentials) != 0 && options.Authorization is null) { var inner = new ArgumentNullException(nameof(options.Authorization)); throw new ArgumentException("`NetworkRequestOption.Authentication` cannot be null with `NetworkRequestOptionFlags.UseCredentials`.", nameof(options), inner); } if ((options.Flags & NetworkRequestOptionFlags.AllowRedirections) == 0 || options.MaxRedirections <= 0) { handler.AllowAutoRedirect = false; } else { handler.AllowAutoRedirect = true; handler.MaxAutomaticRedirections = options.MaxRedirections; } if ((options.Flags & NetworkRequestOptionFlags.UseCookies) != 0 && options.CookieContainer != null) { handler.CookieContainer = options.CookieContainer; handler.UseCookies = true; } if ((options.Flags & NetworkRequestOptionFlags.UseCredentials) != 0 && options.Authorization != null) { handler.UseDefaultCredentials = false; } handler.PreAuthenticate = (options.Flags & NetworkRequestOptionFlags.PreAuthenticate) != 0; if ((options.Flags & NetworkRequestOptionFlags.UseProxy) != 0 && targetUri.ProxyUri != null) { var proxy = GetHttpWebProxy(targetUri); if (proxy != null) { handler.Proxy = proxy; handler.UseProxy = true; } } } return(handler); }
private HttpClient GetHttpClient(TargetUri targetUri, HttpMessageHandler handler, NetworkRequestOptions options) { var httpClient = new HttpClient(handler); if (options != null) { if (options.Timeout > TimeSpan.Zero) { httpClient.Timeout = options.Timeout; } if (options.Headers != null) { foreach (var header in options.Headers) { httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); } } // Manually add the correct headers for the type of authentication that is happening because // if we rely on the framework to correctly write the headers neither GitHub nor Azure DevOps // authentication works reliably. if (options.Authorization != null) { switch (options.Authorization) { case Token token: { // Different types of tokens are packed differently. switch (token.Type) { case TokenType.AzureAccess: case TokenType.BitbucketAccess: { // ADAL access tokens are packed into the Authorization header. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token.Value); } break; case TokenType.AzureFederated: { // Federated authentication tokens are sent as cookie(s). httpClient.DefaultRequestHeaders.Add("Cookie", token.Value); } break; case TokenType.Personal: { // Personal access tokens are designed to treated like credentials, // so treat them like credentials. var credentials = (Credential)token; // Credentials are packed into the 'Authorization' header as a base64 encoded pair. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials.ToBase64String()); } break; default: Trace.WriteLine("! unsupported token type, not appending an authentication header to the request."); break; } } break; case Credential credentials: { // Credentials are packed into the 'Authorization' header as a base64 encoded pair. httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", credentials.ToBase64String()); } break; } } } // Ensure that the user-agent string is set. if (httpClient.DefaultRequestHeaders.UserAgent is null || httpClient.DefaultRequestHeaders.UserAgent.Count == 0) { httpClient.DefaultRequestHeaders.Add("User-Agent", Global.UserAgent); } return(httpClient); }
/// <summary> /// Formats a TargetName string based on the TargetUri base on the format started by git-credential-winstore /// </summary> /// <param name="targetUri">Uri of the target</param> /// <returns>Properly formatted TargetName string</returns> protected override string GetTargetName(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); return(_getTargetName(targetUri, _namespace)); }
/// <summary> /// Validates that a set of credentials grants access to the target resource. /// </summary> /// <param name="targetUri">The target resource to validate against.</param> /// <param name="credentials">The credentials to validate.</param> /// <returns><see langword="true"/> if successful; <see langword="false"/> otherwise.</returns> public async Task <bool> ValidateCredentials(TargetUri targetUri, Credential credentials) { Trace.WriteLine("BaseVstsAuthentication::ValidateCredentials"); return(await this.VstsAuthority.ValidateCredentials(targetUri, credentials)); }
public Task <HttpResponseMessage> HttpGetAsync(TargetUri targetUri) => HttpGetAsync(targetUri, NetworkRequestOptions.Default);
/// <summary> /// Reads credentials for a target URI from the credential store /// </summary> /// <param name="targetUri">The URI of the target for which credentials are being read</param> /// <param name="credentials">The credentials from the store; <see langword="null"/> if failure</param> /// <returns><see langword="true"/> if success; <see langword="false"/> if failure</returns> public bool ReadCredentials(TargetUri targetUri, out Credential credentials) { ValidateTargetUri(targetUri); string targetName = this.GetTargetName(targetUri); Trace.WriteLine("CredentialStore::ReadCredentials"); if (!_credentialCache.ReadCredentials(targetUri, out credentials)) { credentials = this.ReadCredentials(targetName); } return credentials != null; }
public Task <INetworkResponseMessage> HttpPostAsync(TargetUri targetUri, StringContent content) => HttpPostAsync(targetUri, content, NetworkRequestOptions.Default);
/// <summary> /// Deletes credentials for target URI from the credential store /// </summary> /// <param name="targetUri">The URI of the target for which credentials are being deleted</param> public void DeleteCredentials(TargetUri targetUri) { ValidateTargetUri(targetUri); string targetName = this.GetTargetName(targetUri); this.Delete(targetName); _credentialCache.DeleteCredentials(targetUri); }
/// <summary> /// Generates a personal access token for use with Visual Studio Online. /// </summary> /// <param name="targetUri"> /// The uniform resource indicator of the resource access tokens are being requested for. /// </param> /// <param name="accessToken"></param> /// <param name="tokenScope"></param> /// <param name="requireCompactToken"></param> /// <returns></returns> public async Task <Token> GeneratePersonalAccessToken(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope, bool requireCompactToken) { const string AccessTokenHeader = "Bearer"; Debug.Assert(targetUri != null, "The targetUri parameter is null"); Debug.Assert(accessToken != null && !String.IsNullOrWhiteSpace(accessToken.Value) && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); Debug.Assert(tokenScope != null); Trace.WriteLine("VstsAzureAuthority::GeneratePersonalAccessToken"); try { // create a `HttpClient` with a minimum number of redirects, default creds, and a reasonable timeout (access token generation seems to hang occasionally) 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()); switch (accessToken.Type) { case TokenType.Access: Trace.WriteLine(" using Azure access token to acquire personal access token"); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(AccessTokenHeader, accessToken.Value); break; case TokenType.Federated: Trace.WriteLine(" using federated authentication token to acquire personal access token"); httpClient.DefaultRequestHeaders.Add("Cookie", accessToken.Value); break; default: return(null); } if (await PopulateTokenTargetId(targetUri, accessToken)) { Uri requestUri; if (TryCreateRequestUri(targetUri, requireCompactToken, out requestUri)) { Trace.WriteLine(" request url is " + requestUri); using (StringContent content = GetAccessTokenRequestBody(targetUri, accessToken, tokenScope)) using (HttpResponseMessage response = await httpClient.PostAsync(requestUri, content)) { if (response.StatusCode == HttpStatusCode.OK) { string responseText = await response.Content.ReadAsStringAsync(); if (!String.IsNullOrWhiteSpace(responseText)) { // find the 'token : <value>' portion of the result content, if any Match tokenMatch = null; if ((tokenMatch = Regex.Match(responseText, @"\s*""token""\s*:\s*""([^\""]+)""\s*", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success) { string tokenValue = tokenMatch.Groups[1].Value; Token token = new Token(tokenValue, TokenType.Personal); Trace.WriteLine(" personal access token acquisition succeeded."); return(token); } } } } } } } } catch { Trace.WriteLine(" an error occurred."); } Trace.WriteLine(" personal access token acquisition failed."); return(null); }
/// <summary> /// Reads credentials for a target URI from the credential store /// </summary> /// <param name="targetUri">The URI of the target for which credentials are being read</param> /// <param name="credentials"></param> /// <returns>A <see cref="Credential"/> from the store is successful; otherwise <see langword="null"/>.</returns> public Credential ReadCredentials(TargetUri targetUri) { ValidateTargetUri(targetUri); string targetName = this.GetTargetName(targetUri); return _credentialCache.ReadCredentials(targetUri) ?? this.ReadCredentials(targetName); }
/// <summary> /// Validates that a set of credentials grants access to the target resource. /// <para/> /// Returns `<see langword="true"/>` if successful; otherwise `<see langword="false"/>`. /// </summary> /// <param name="targetUri">The target resource to validate against.</param> /// <param name="credentials">The credentials to validate.</param> public async Task <bool> ValidateCredentials(TargetUri targetUri, Credential credentials) { return(await VstsAuthority.ValidateCredentials(targetUri, credentials)); }
/// <summary> /// Writes credentials for a target URI to the credential store /// </summary> /// <param name="targetUri">The URI of the target for which credentials are being stored</param> /// <param name="credentials">The credentials to be stored</param> public void WriteCredentials(TargetUri targetUri, Credential credentials) { ValidateTargetUri(targetUri); BaseSecureStore.ValidateCredential(credentials); string targetName = this.GetTargetName(targetUri); this.WriteCredential(targetName, credentials); _credentialCache.WriteCredentials(targetUri, credentials); }
/// <summary> /// Writes a token for a target URI to the token store /// </summary> /// <param name="targetUri">The URI of the target for which a token is being stored</param> /// <param name="token">The token to be stored</param> public void WriteToken(TargetUri targetUri, Token token) { ValidateTargetUri(targetUri); Token.Validate(token); string targetName = this.GetTargetName(targetUri); this.WriteToken(targetName, token); _tokenCache.WriteToken(targetUri, token); }
/// <summary> /// Formats a TargetName string based on the TargetUri base on the format started by git-credential-winstore /// </summary> /// <param name="targetUri">Uri of the target</param> /// <returns>Properly formatted TargetName string</returns> protected override string GetTargetName(TargetUri targetUri) { BaseSecureStore.ValidateTargetUri(targetUri); return _getTargetName(targetUri, _namespace); }
public static async Task <KeyValuePair <bool, Guid> > DetectAuthority(TargetUri targetUri) { const string VstsBaseUrlHost = "visualstudio.com"; const string VstsResourceTenantHeader = "X-VSS-ResourceTenant"; BaseSecureStore.ValidateTargetUri(targetUri); var tenantId = Guid.Empty; if (targetUri.Host.EndsWith(VstsBaseUrlHost, StringComparison.OrdinalIgnoreCase)) { Git.Trace.WriteLine($"'{targetUri}' is subdomain of '{VstsBaseUrlHost}', checking AAD vs MSA."); string tenant = null; WebResponse response; if (StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, "http") || StringComparer.OrdinalIgnoreCase.Equals(targetUri.Scheme, "https")) { // Query the cache first string tenantUrl = targetUri.ToString(); // Read the cache from disk var cache = await DeserializeTenantCache(); // Check the cache for an existing value if (cache.TryGetValue(tenantUrl, out tenantId)) { return(new KeyValuePair <bool, Guid>(true, tenantId)); } try { // build a request that we expect to fail, do not allow redirect to sign in url var request = WebRequest.CreateHttp(targetUri); request.UserAgent = Global.UserAgent; request.Method = "HEAD"; request.AllowAutoRedirect = false; // get the response from the server response = await request.GetResponseAsync(); } catch (WebException exception) { response = exception.Response; } // if the response exists and we have headers, parse them if (response != null && response.SupportsHeaders) { // find the VSTS resource tenant entry tenant = response.Headers[VstsResourceTenantHeader]; if (!string.IsNullOrWhiteSpace(tenant) && Guid.TryParse(tenant, out tenantId)) { // Update the cache. cache[tenantUrl] = tenantId; // Write the cache to disk. await SerializeTenantCache(cache); // Success, notify the caller return(new KeyValuePair <bool, Guid>(true, tenantId)); } } } else { Git.Trace.WriteLine($"detected non-https based protocol: {targetUri.Scheme}."); } } // if all else fails, fallback to basic authentication return(new KeyValuePair <bool, Guid>(false, tenantId)); }