/// <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> /// Generates a personal access token for use with Visual Studio Team Services. /// <para/> /// Returns the acquired token if successful; otherwise <see langword="null"/>; /// </summary> /// <param name="targetUri">The uniform resource indicator of the resource access tokens are being requested for.</param> /// <param name="accessToken">Access token granted by the identity authority (Azure).</param> /// <param name="tokenScope">The requested access scopes to be granted to the token.</param> /// <param name="requireCompactToken">`<see langword="true"/>` if requesting a compact format token; otherwise `<see langword="false"/>`.</param> /// <param name="tokenDuration"> /// The requested lifetime of the requested token. /// <para/> /// The authority granting the token decides the actual lifetime of any token granted, regardless of the duration requested. /// </param> public async Task <Token> GeneratePersonalAccessToken(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope, bool requireCompactToken, TimeSpan?tokenDuration = null) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(accessToken); if (tokenScope is null) { throw new ArgumentNullException(nameof(tokenScope)); } try { var options = new NetworkRequestOptions(true) { Authorization = accessToken, }; var requestUri = await CreatePersonalAccessTokenRequestUri(targetUri, requireCompactToken); using (StringContent content = GetAccessTokenRequestBody(targetUri, accessToken, tokenScope, tokenDuration)) using (var response = await Network.HttpPostAsync(targetUri, content, options)) { 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); Trace.WriteLine($"personal access token acquisition for '{targetUri}' succeeded."); return(token); } } } } } catch (Exception e) { Trace.WriteLine($"! an error occurred: {e.Message}"); } Trace.WriteLine($"personal access token acquisition for '{targetUri}' failed."); return(null); }
public async Task <bool> PopulateTokenTargetId(TargetUri targetUri, Token accessToken) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(accessToken); Trace.WriteLine("VstsAzureAuthority::PopulateTokenTargetId"); string resultId = null; Guid instanceId; try { // create an request to the VSTS deployment data end-point HttpWebRequest request = GetConnectionDataRequest(targetUri, accessToken); Trace.WriteLine(String.Format(" access token end-point is {0} {1}", request.Method, request.RequestUri)); // send the request and wait for the response using (var response = await request.GetResponseAsync()) using (var stream = response.GetResponseStream()) using (var reader = new StreamReader(stream, Encoding.UTF8)) { string content = await reader.ReadToEndAsync(); Match match; if ((match = Regex.Match(content, @"""instanceId""\s*\:\s*""([^""]+)""", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)).Success && match.Groups.Count == 2) { resultId = match.Groups[1].Value; } } } catch (WebException webException) { Trace.WriteLine(" server returned " + webException.Status); } if (Guid.TryParse(resultId, out instanceId)) { Trace.WriteLine(" target identity is " + resultId); accessToken.TargetIdentity = instanceId; return(true); } return(false); }
/// <summary> /// <para>Validates that <see cref="Token"/> are valid to grant access to the Visual Studio /// Online service represented by the <paramref name="targetUri"/> parameter.</para> /// </summary> /// <param name="targetUri">Uniform resource identifier for a VSTS service.</param> /// <param name="token"> /// <see cref="Token"/> expected to grant access to the VSTS service. /// </param> /// <returns>True if successful; otherwise false.</returns> public async Task <bool> ValidateToken(TargetUri targetUri, Token token) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(token); Trace.WriteLine("VstsAzureAuthority::ValidateToken"); // personal access tokens are effectively credentials, treat them as such if (token.Type == TokenType.Personal) { return(await this.ValidateCredentials(targetUri, (Credential)token)); } try { // create an request to the VSTS deployment data end-point HttpWebRequest request = GetConnectionDataRequest(targetUri, token); Trace.WriteLine(" validating token against " + request.Host); // send the request and wait for the response using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse) { // we're looking for 'OK 200' here, anything else is failure Trace.WriteLine(" server returned: " + response.StatusCode); return(response.StatusCode == HttpStatusCode.OK); } } catch (WebException webException) { Trace.WriteLine(" server returned: " + webException.Message); } catch { Trace.WriteLine(" unexpected error"); } Trace.WriteLine(" token validation failed"); return(false); }
/// <summary> /// Validates that a `<see cref="Token"/>` is valid to grant access to the VSTS resource referenced by `<paramref name="targetUri"/>`. /// <para/> /// Returns `<see langword="true"/>` if successful; otherwise `<see langword="false"/>`. /// </summary> /// <param name="targetUri">URI of the VSTS resource.</param> /// <param name="token">`<see cref="Token"/>` expected to grant access to the VSTS resource.</param> public async Task <bool> ValidateToken(TargetUri targetUri, Token token) { BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(token); // Personal access tokens are effectively credentials, treat them as such. if (token.Type == TokenType.Personal) { return(await ValidateCredentials(targetUri, (Credential)token)); } try { // Create an request to the VSTS deployment data end-point. HttpWebRequest request = GetConnectionDataRequest(targetUri, token); Git.Trace.WriteLine($"validating token against '{request.Host}'."); // Send the request and wait for the response. using (HttpWebResponse response = await request.GetResponseAsync() as HttpWebResponse) { // We're looking for 'OK 200' here, anything else is failure. Git.Trace.WriteLine($"server returned: '{response.StatusCode}'."); return(response.StatusCode == HttpStatusCode.OK); } } catch (WebException webException) { Git.Trace.WriteLine($"! server returned: '{webException.Message}'."); } catch { Git.Trace.WriteLine("! unexpected error"); } Git.Trace.WriteLine($"token validation for '{targetUri}' failed."); return(false); }
/// <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"; BaseSecureStore.ValidateTargetUri(targetUri); BaseSecureStore.ValidateToken(accessToken); if (ReferenceEquals(tokenScope, null)) { throw new ArgumentNullException(nameof(tokenScope)); } 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.UserAgent); 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); }