Represents a potentially proxied Uri.
        /// <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;
        }
Пример #23
0
 /// <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);
        }
Пример #27
0
 /// <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));
        }
Пример #34
0
 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);
        }
Пример #38
0
        /// <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));
        }