public VstsMsaAuthentication( VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore, ITokenStore adaRefreshTokenStore = null) : base(tokenScope, personalAccessTokenStore, adaRefreshTokenStore) { this.VstsAuthority = new VstsAzureAuthority(DefaultAuthorityHost); }
/// <summary> /// Invoked by a derived classes implementation. Allows custom back-end implementations to be used. /// </summary> /// <param name="tokenScope">The desired scope of the acquired personal access token(s).</param> /// <param name="personalAccessTokenStore">The secret store for acquired personal access token(s).</param> /// <param name="adaRefreshTokenStore">The secret store for acquired Azure refresh token(s).</param> protected BaseVstsAuthentication( VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore, ITokenStore adaRefreshTokenStore = null) : this(tokenScope, personalAccessTokenStore) { this.AdaRefreshTokenStore = adaRefreshTokenStore ?? this.AdaRefreshTokenStore; this.VstsAdalTokenCache = new VstsAdalTokenCache(); this.VstsIdeTokenCache = new TokenRegistry(); }
protected BaseVstsAuthentication(VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore) { if (ReferenceEquals(tokenScope, null)) throw new ArgumentNullException(nameof(TokenScope)); if (ReferenceEquals(personalAccessTokenStore, null)) throw new ArgumentNullException(nameof(personalAccessTokenStore)); this.ClientId = DefaultClientId; this.Resource = DefaultResource; this.TokenScope = tokenScope; this.PersonalAccessTokenStore = personalAccessTokenStore; this.VstsAuthority = new VstsAzureAuthority(); }
/// <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; }
private BaseVstsAuthentication(VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore) { if (tokenScope == null) throw new ArgumentNullException("scope", "The `scope` parameter is null or invalid."); if (personalAccessTokenStore == null) throw new ArgumentNullException("personalAccessTokenStore", "The `personalAccessTokenStore` paramter is null or invalid."); AdalTrace.TraceSource.Switch.Level = SourceLevels.Off; AdalTrace.LegacyTraceSwitch.Level = TraceLevel.Off; this.ClientId = DefaultClientId; this.Resource = DefaultResource; this.TokenScope = tokenScope; this.PersonalAccessTokenStore = personalAccessTokenStore; this.AdaRefreshTokenStore = new SecretStore(AdalRefreshPrefx); this.VstsAuthority = new VstsAzureAuthority(); }
/// <summary> /// /// </summary> /// <param name="tenantId"> /// <para>The unique identifier for the responsible Azure tenant.</para> /// <para>Use <see cref="BaseVstsAuthentication.GetAuthentication"/> /// to detect the tenant identity and create the authentication object.</para> /// </param> /// <param name="tokenScope">The scope of all access tokens acquired by the authority.</param> /// <param name="personalAccessTokenStore">The secure secret store for storing any personal /// access tokens acquired.</param> /// <param name="adaRefreshTokenStore">The secure secret store for storing any Azure tokens /// acquired.</param> public VstsAadAuthentication( Guid tenantId, VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore) : base(tokenScope, personalAccessTokenStore) { if (tenantId == Guid.Empty) { this.VstsAuthority = new VstsAzureAuthority(AzureAuthority.DefaultAuthorityHostUrl); } else { // create an authority host URL in the format of https://login.microsoft.com/12345678-9ABC-DEF0-1234-56789ABCDEF0 string authorityHost = AzureAuthority.GetAuthorityUrl(tenantId); this.VstsAuthority = new VstsAzureAuthority(authorityHost); } }
private static StringContent GetAccessTokenRequestBody(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope, TimeSpan?duration = null) { const string ContentBasicJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\" }}"; const string ContentTimedJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\", \"validTo\": \"{4:u}\" }}"; const string HttpJsonContentType = "application/json"; Debug.Assert(accessToken != null && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); Debug.Assert(tokenScope != null, "The tokenScope parameter is null"); Git.Trace.WriteLine($"creating access token scoped to '{tokenScope}' for '{accessToken.TargetIdentity}'"); string jsonContent = (duration.HasValue && duration.Value > TimeSpan.FromHours(1)) ? string.Format(ContentTimedJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName, DateTime.UtcNow + duration.Value) : string.Format(ContentBasicJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName); StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType); return(content); }
private static StringContent GetAccessTokenRequestBody(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope) { const string ContentJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\" }}"; const string HttpJsonContentType = "application/json"; Debug.Assert(accessToken != null && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); Debug.Assert(tokenScope != null, "The tokenScope parameter is null"); Git.Trace.WriteLine($"creating access token scoped to '{tokenScope}' for '{accessToken.TargetIdentity}'"); string jsonContent = String.Format(ContentJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName); StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType); return content; }
/// <summary> /// Creates a new authentication broker based for the specified resource. /// </summary> /// <param name="targetUri">The resource for which authentication is being requested.</param> /// <param name="scope">The scope of the access being requested.</param> /// <param name="personalAccessTokenStore">Storage container for personal access token secrets.</param> /// <param name="adaRefreshTokenStore">Storage container for Azure access token secrets.</param> /// <param name="authentication"> /// An implementation of <see cref="BaseAuthentication"/> if one was detected; /// <see langword="null"/> otherwise. /// </param> /// <returns> /// <see langword="true"/> if an authority could be determined; <see langword="false"/> otherwise. /// </returns> public static BaseAuthentication GetAuthentication( TargetUri targetUri, VstsTokenScope scope, ICredentialStore personalAccessTokenStore) { BaseSecureStore.ValidateTargetUri(targetUri); if (ReferenceEquals(scope, null)) throw new ArgumentNullException(nameof(scope)); if (ReferenceEquals(personalAccessTokenStore, null)) throw new ArgumentNullException(nameof(personalAccessTokenStore)); BaseAuthentication authentication = null; Guid tenantId; if (DetectAuthority(targetUri, out tenantId)) { // empty Guid is MSA, anything else is AAD if (tenantId == Guid.Empty) { Git.Trace.WriteLine("MSA authority detected."); authentication = new VstsMsaAuthentication(scope, personalAccessTokenStore); } else { Git.Trace.WriteLine($"AAD authority for tenant '{tenantId}' detected."); authentication = new VstsAadAuthentication(tenantId, scope, personalAccessTokenStore); (authentication as VstsAadAuthentication).TenantId = tenantId; } } return authentication; }
public VstsMsaAuthentication(VstsTokenScope tokenScope, ICredentialStore personalAccessTokenStore) : base(tokenScope, personalAccessTokenStore) { this.VstsAuthority = new VstsAzureAuthority(DefaultAuthorityHost); }
/// <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.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); }
private StringContent GetAccessTokenRequestBody(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope) { const string ContentJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\" }}"; const string HttpJsonContentType = "application/json"; Debug.Assert(accessToken != null && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); Debug.Assert(tokenScope != null, "The tokenScope parameter is null"); Trace.WriteLine(" creating access token scoped to '" + tokenScope + "' for '" + accessToken.TargetIdentity + "'"); string jsonContent = String.Format(ContentJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName); StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType); return(content); }
public static bool Find(string value, out VstsTokenScope vstsScope) { vstsScope = EnumerateValues().FirstOrDefault(v => StringComparer.OrdinalIgnoreCase.Equals(v.Value, value)); return(vstsScope != null); }
public bool Equals(VstsTokenScope other) => TokenScope.Equals(this as TokenScope, other as TokenScope);
/// <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(Uri targetUri, Token accessToken, VstsTokenScope tokenScope, bool requireCompactToken) { const string TokenAuthHost = "app.vssps.visualstudio.com"; const string SessionTokenUrl = "https://" + TokenAuthHost + "/_apis/token/sessiontokens?api-version=1.0"; const string CompactTokenUrl = SessionTokenUrl + "&tokentype=compact"; 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)) { string requestUrl = requireCompactToken ? CompactTokenUrl : SessionTokenUrl; using (StringContent content = GetAccessTokenRequestBody(targetUri, accessToken, tokenScope)) using (HttpResponseMessage response = await httpClient.PostAsync(requestUrl, 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 aquisition succeeded."); return token; } } } } } } } catch { Trace.WriteLine(" an error occured error."); } Trace.WriteLine(" personal access token aquisition failed."); return null; }
/// <summary> /// Creates a new authentication broker based for the specified resource. /// </summary> /// <param name="targetUri">The resource for which authentication is being requested.</param> /// <param name="scope">The scope of the access being requested.</param> /// <param name="personalAccessTokenStore">Storage container for personal access token secrets.</param> /// <param name="adaRefreshTokenStore">Storage container for Azure access token secrets.</param> /// <param name="authentication"> /// An implementation of <see cref="BaseAuthentication"/> if one was detected; /// <see langword="null"/> otherwise. /// </param> /// <returns> /// <see langword="true"/> if an authority could be determined; <see langword="false"/> otherwise. /// </returns> public static bool GetAuthentication( TargetUri targetUri, VstsTokenScope scope, ICredentialStore personalAccessTokenStore, ITokenStore adaRefreshTokenStore, out BaseAuthentication authentication) { Trace.WriteLine("BaseVstsAuthentication::DetectAuthority"); Guid tenantId; if (DetectAuthority(targetUri, out tenantId)) { // empty Guid is MSA, anything else is AAD if (tenantId == Guid.Empty) { Trace.WriteLine(" MSA authority detected"); authentication = new VstsMsaAuthentication(scope, personalAccessTokenStore, adaRefreshTokenStore); } else { Trace.WriteLine(" AAD authority for tenant '" + tenantId + "' detected"); authentication = new VstsAadAuthentication(tenantId, scope, personalAccessTokenStore, adaRefreshTokenStore); (authentication as VstsAadAuthentication).TenantId = tenantId; } } else { authentication = null; } return authentication != null; }
public async Task <Token> GeneratePersonalAccessToken(TargetUri targetUri, Token authorization, VstsTokenScope tokenScope, bool requireCompactToken, TimeSpan?tokenDuration = null) { if (targetUri is null) { throw new ArgumentNullException(nameof(targetUri)); } if (authorization is null) { throw new ArgumentNullException(nameof(authorization)); } if (tokenScope is null) { throw new ArgumentNullException(nameof(tokenScope)); } try { var requestUri = await CreatePersonalAccessTokenRequestUri(targetUri, authorization, requireCompactToken); var options = new NetworkRequestOptions(true) { Authorization = authorization, }; using (StringContent content = GetAccessTokenRequestBody(targetUri, tokenScope, tokenDuration)) using (var response = await Network.HttpPostAsync(requestUri, 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); } } } Trace.WriteLine($"failed to acquire personal access token for '{targetUri}' [{(int)response.StatusCode} {response.ReasonPhrase}]."); } } catch (Exception exception) { Trace.WriteException(exception); } Trace.WriteLine($"personal access token acquisition for '{targetUri}' failed."); return(null); }
/// <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); }
private StringContent GetAccessTokenRequestBody(TargetUri targetUri, Token accessToken, VstsTokenScope tokenScope, TimeSpan?duration = null) { const string ContentBasicJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\" }}"; const string ContentTimedJsonFormat = "{{ \"scope\" : \"{0}\", \"targetAccounts\" : [\"{1}\"], \"displayName\" : \"Git: {2} on {3}\", \"validTo\": \"{4:u}\" }}"; const string HttpJsonContentType = "application/json"; if (accessToken is null) { throw new ArgumentNullException(nameof(accessToken)); } if (tokenScope is null) { throw new ArgumentNullException(nameof(tokenScope)); } Trace.WriteLine($"creating access token scoped to '{tokenScope}' for '{accessToken.TargetIdentity}'"); string jsonContent = (duration.HasValue && duration.Value > TimeSpan.FromHours(1)) ? string.Format(ContentTimedJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName, DateTime.UtcNow + duration.Value) : string.Format(ContentBasicJsonFormat, tokenScope, accessToken.TargetIdentity, targetUri, Environment.MachineName); StringContent content = new StringContent(jsonContent, Encoding.UTF8, HttpJsonContentType); return(content); }