示例#1
0
        /// <remarks>
        /// For dev.azure.com-style URLs we use the path arg to get the Azure DevOps organization name.
        /// We ensure the presence of the path arg by setting credential.useHttpPath = true at install time.
        ///
        /// The result of this workaround is that we are now unable to determine if the user wanted to store
        /// credentials with the full path or not for dev.azure.com-style URLs.
        ///
        /// Rather than always assume we're storing credentials against the full path, and therefore resulting
        /// in an personal access token being created per remote URL/repository, we never store against
        /// the full path and always store with the organization URL "dev.azure.com/org".
        ///
        /// For visualstudio.com-style URLs we know the AzDevOps organization name from the host arg, and
        /// don't set the useHttpPath option. This means if we get the full path for a vs.com-style URL
        /// we can store against the full remote path (the intended design).
        ///
        /// Users that need to clone a repository from Azure Repos against the full path therefore must
        /// use the vs.com-style remote URL and not the dev.azure.com one.
        /// </remarks>
        private static string GetServiceName(InputArguments input)
        {
            if (!input.TryGetHostAndPort(out string hostName, out _))
            {
                throw new InvalidOperationException("Failed to parse host name and/or port");
            }

            // dev.azure.com
            if (UriHelpers.IsDevAzureComHost(hostName))
            {
                // We can never store the new dev.azure.com-style URLs against the full path because
                // we have forced the useHttpPath option to true to in order to retrieve the AzDevOps
                // organization name from Git.
                return(UriHelpers.CreateOrganizationUri(input).AbsoluteUri.TrimEnd('/'));
            }

            // *.visualstudio.com
            if (UriHelpers.IsVisualStudioComHost(hostName))
            {
                // If we're given the full path for an older *.visualstudio.com-style URL then we should
                // respect that in the service name.
                return(input.GetRemoteUri().AbsoluteUri.TrimEnd('/'));
            }

            throw new InvalidOperationException("Host is not Azure DevOps.");
        }
示例#2
0
        private void VerifyOAuthFlowRan(string password, bool storedAccount, bool expected, InputArguments input, Task <ICredential> credential,
                                        string preconfiguredAuthModes)
        {
            Assert.Equal(expected, credential != null);

            var remoteUri = input.GetRemoteUri();

            if (storedAccount)
            {
                // use refresh token to get new access token and refresh token
                bitbucketAuthentication.Verify(m => m.RefreshOAuthCredentialsAsync(MOCK_REFRESH_TOKEN), Times.Once);

                // check access token works
                bitbucketApi.Verify(m => m.GetUserInformationAsync(null, MOCK_ACCESS_TOKEN, true), Times.Once);
            }
            else
            {
                if (preconfiguredAuthModes == null || preconfiguredAuthModes.Contains("basic"))
                {
                    // prompt user for basic auth, if basic auth is not excluded
                    bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()), Times.Once);

                    // check if entered Basic Auth credentials work, if basic auth is not excluded
                    bitbucketApi.Verify(m => m.GetUserInformationAsync(input.UserName, password, false), Times.Once);
                }

                // Basic Auth 403-ed so push user through OAuth flow
                bitbucketAuthentication.Verify(m => m.ShowOAuthRequiredPromptAsync(), Times.Once);
            }
        }
        public override async Task <ICredential> GenerateCredentialAsync(InputArguments input)
        {
            ThrowIfDisposed();

            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
            {
                throw new Exception("Unencrypted HTTP is not supported for GitLab. Ensure the repository remote URL is using HTTPS.");
            }

            Uri remoteUri = input.GetRemoteUri();

            AuthenticationModes authModes = GetSupportedAuthenticationModes(remoteUri);

            AuthenticationPromptResult promptResult = await _gitLabAuth.GetAuthenticationAsync(remoteUri, input.UserName, authModes);

            switch (promptResult.AuthenticationMode)
            {
            case AuthenticationModes.Basic:
            case AuthenticationModes.Pat:
                return(promptResult.Credential);

            case AuthenticationModes.Browser:
                return(await GenerateOAuthCredentialAsync(input));

            default:
                throw new ArgumentOutOfRangeException(nameof(promptResult));
            }
        }
        private void VerifyBasicAuthFlowRan(string password, bool storedAccount, bool expected, InputArguments input, System.Threading.Tasks.Task <ICredential> credential,
                                            string preconfiguredAuthModes)
        {
            Assert.Equal(expected, credential != null);

            var remoteUri = input.GetRemoteUri();

            if (storedAccount)
            {
                // stored username/password so no need to prompt the user for them
                bitbucketAuthentication.Verify(m => m.GetBasicCredentialsAsync(remoteUri, input.UserName), Times.Never);
            }
            else
            {
                // no username/password credentials so prompt the user for them
                bitbucketAuthentication.Verify(m => m.GetBasicCredentialsAsync(remoteUri, input.UserName), Times.Once);
            }

            // check username/password for Bitbucket.org
            if ((preconfiguredAuthModes == null && "bitbucket.org" == remoteUri.Host) ||
                (preconfiguredAuthModes != null && preconfiguredAuthModes.Contains("oauth")))
            {
                bitbucketApi.Verify(m => m.GetUserInformationAsync(input.UserName, password, false), Times.Once);
            }
        }
示例#5
0
        private void VerifyInteractiveOAuthFlowRan(string password, InputArguments input, System.Threading.Tasks.Task <ICredential> credential)
        {
            var remoteUri = input.GetRemoteUri();

            // Basic Auth 403-ed so push user through OAuth flow
            bitbucketAuthentication.Verify(m => m.ShowOAuthRequiredPromptAsync(), Times.Once);
        }
示例#6
0
        private static void MockUserEntersValidBasicCredentials(Mock <IBitbucketAuthentication> bitbucketAuthentication, InputArguments input, string password)
        {
            var remoteUri = input.GetRemoteUri();

            bitbucketAuthentication.Setup(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()))
            .ReturnsAsync(new CredentialsPromptResult(AuthenticationModes.Basic, new TestCredential(input.Host, input.UserName, password)));
        }
示例#7
0
        private static void MockStoredAccount(TestCommandContext context, InputArguments input, string password)
        {
            var remoteUri = input.GetRemoteUri();
            var remoteUrl = remoteUri.AbsoluteUri.Substring(0, remoteUri.AbsoluteUri.Length - 1);

            context.CredentialStore.Add(remoteUrl, new TestCredential(input.Host, input.UserName, password));
        }
        public Task StoreCredentialAsync(InputArguments input)
        {
            Uri remoteUri = input.GetRemoteUri();

            if (UsePersonalAccessTokens())
            {
                string service = GetServiceName(remoteUri);

                // We always store credentials against the given username argument for
                // both vs.com and dev.azure.com-style URLs.
                string account = input.UserName;

                // Add or update the credential in the store.
                _context.Trace.WriteLine($"Storing credential with service={service} account={account}...");
                _context.CredentialStore.AddOrUpdate(service, account, input.Password);
                _context.Trace.WriteLine("Credential was successfully stored.");
            }
            else
            {
                string orgName = UriHelpers.GetOrganizationName(remoteUri);
                _context.Trace.WriteLine($"Signing user {input.UserName} in to organization '{orgName}'...");
                _bindingManager.SignIn(orgName, input.UserName);
            }

            return(Task.CompletedTask);
        }
        public Task EraseCredentialAsync(InputArguments input)
        {
            Uri remoteUri = input.GetRemoteUri();

            if (UsePersonalAccessTokens())
            {
                string service = GetServiceName(remoteUri);
                string account = GetAccountNameForCredentialQuery(input);

                // Try to locate an existing credential
                _context.Trace.WriteLine($"Erasing stored credential in store with service={service} account={account}...");
                if (_context.CredentialStore.Remove(service, account))
                {
                    _context.Trace.WriteLine("Credential was successfully erased.");
                }
                else
                {
                    _context.Trace.WriteLine("No credential was erased.");
                }
            }
            else
            {
                string orgName = UriHelpers.GetOrganizationName(remoteUri);

                _context.Trace.WriteLine($"Signing out of organization '{orgName}'...");
                _bindingManager.SignOut(orgName);

                // Clear the authority cache in case this was the reason for failure
                _authorityCache.EraseAuthority(orgName);
            }

            return(Task.CompletedTask);
        }
        internal async Task ExecuteAsync()
        {
            Context.Trace.WriteLine($"Start '{Name}' command...");

            // Parse standard input arguments
            // git-credential treats the keys as case-sensitive; so should we.
            IDictionary <string, string> inputDict = await Context.Streams.In.ReadDictionaryAsync(StringComparer.Ordinal);

            var input = new InputArguments(inputDict);

            // Validate minimum arguments are present
            EnsureMinimumInputArguments(input);

            // Set the remote URI to scope settings to throughout the process from now on
            Context.Settings.RemoteUri = input.GetRemoteUri();

            // Determine the host provider
            Context.Trace.WriteLine("Detecting host provider for input:");
            Context.Trace.WriteDictionarySecrets(inputDict, new [] { "password" }, StringComparer.OrdinalIgnoreCase);
            IHostProvider provider = await _hostProviderRegistry.GetProviderAsync(input);

            Context.Trace.WriteLine($"Host provider '{provider.Name}' was selected.");

            await ExecuteInternalAsync(input, provider);

            Context.Trace.WriteLine($"End '{Name}' command...");
        }
示例#11
0
        public override async Task <ICredential> GenerateCredentialAsync(InputArguments input)
        {
            ThrowIfDisposed();

            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
            {
                throw new Exception("Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS.");
            }

            Uri remoteUri = input.GetRemoteUri();

            string service = GetServiceName(input);

            AuthenticationModes authModes = await GetSupportedAuthenticationModesAsync(remoteUri);

            AuthenticationPromptResult promptResult = await _gitHubAuth.GetAuthenticationAsync(remoteUri, input.UserName, authModes);

            switch (promptResult.AuthenticationMode)
            {
            case AuthenticationModes.Basic:
                GitCredential patCredential = await GeneratePersonalAccessTokenAsync(remoteUri, promptResult.Credential);

                // HACK: Store the PAT immediately in case this PAT is not valid for SSO.
                // We don't know if this PAT is valid for SAML SSO and if it's not Git will fail
                // with a 403 and call neither 'store' or 'erase'. The user is expected to fiddle with
                // the PAT permissions manually on the web and then retry the Git operation.
                // We must store the PAT now so they can resume/repeat the operation with the same,
                // now SSO authorized, PAT.
                // See: https://github.com/GitCredentialManager/git-credential-manager/issues/133
                Context.CredentialStore.AddOrUpdate(service, patCredential.Account, patCredential.Password);
                return(patCredential);

            case AuthenticationModes.Browser:
                return(await GenerateOAuthCredentialAsync(remoteUri, useBrowser : true));

            case AuthenticationModes.Device:
                return(await GenerateOAuthCredentialAsync(remoteUri, useBrowser : false));

            case AuthenticationModes.Pat:
                // The token returned by the user should be good to use directly as the password for Git
                string token = promptResult.Credential.Password;

                // Resolve the GitHub user handle if we don't have a specific username already from the
                // initial request. The reason for this is GitHub requires a (any?) value for the username
                // when Git makes calls to GitHub.
                string userName = promptResult.Credential.Account;
                if (userName is null)
                {
                    GitHubUserInfo userInfo = await _gitHubApi.GetUserInfoAsync(remoteUri, token);

                    userName = userInfo.Login;
                }

                return(new GitCredential(userName, token));

            default:
                throw new ArgumentOutOfRangeException(nameof(promptResult));
            }
        }
示例#12
0
        public void InputArguments_GetRemoteUri_NoAuthority_ReturnsNull()
        {
            var dict = new Dictionary <string, string>();

            var inputArgs = new InputArguments(dict);

            Uri actualUri = inputArgs.GetRemoteUri();

            Assert.Null(actualUri);
        }
示例#13
0
        private void VerifyInteractiveOAuthFlowNeverRan(InputArguments input, System.Threading.Tasks.Task <ICredential> credential)
        {
            var remoteUri = input.GetRemoteUri();

            // never prompt user through OAuth flow
            bitbucketAuthentication.Verify(m => m.ShowOAuthRequiredPromptAsync(), Times.Never);

            // Never try to refresh Access Token
            bitbucketAuthentication.Verify(m => m.RefreshOAuthCredentialsAsync(It.IsAny <string>()), Times.Never);

            // never check access token works
            bitbucketApi.Verify(m => m.GetUserInformationAsync(null, MOCK_ACCESS_TOKEN, true), Times.Never);
        }
示例#14
0
        private static string GetRefreshTokenServiceName(InputArguments input)
        {
            Uri baseUri = input.GetRemoteUri(includeUser: false);

            // The refresh token key never includes the path component.
            // Instead we use the path component to specify this is the "refresh_token".
            Uri uri = new UriBuilder(baseUri)
            {
                Path = "/refresh_token"
            }.Uri;

            return(uri.AbsoluteUri.TrimEnd('/'));
        }
示例#15
0
        private void VerifyInteractiveBasicAuthFlowRan(string password, InputArguments input, Task <ICredential> credential)
        {
            var remoteUri = input.GetRemoteUri();

            // verify users was prompted for username/password credentials
            bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()), Times.Once);

            // check username/password for Bitbucket.org
            if (BITBUCKET_DOT_ORG_HOST == remoteUri.Host)
            {
                bitbucketApi.Verify(m => m.GetUserInformationAsync(input.UserName, password, false), Times.Once);
            }
        }
        public Task StoreCredentialAsync(InputArguments input)
        {
            // It doesn't matter if this is an OAuth access token, or the literal username & password
            // because we store them the same way, against the same credential key in the store.
            // The OAuth refresh token is already stored on the 'get' request.
            Uri    remoteUri = input.GetRemoteUri();
            string service   = GetServiceName(remoteUri);

            _context.Trace.WriteLine("Storing credential...");
            _context.CredentialStore.AddOrUpdate(service, input.UserName, input.Password);
            _context.Trace.WriteLine("Credential was successfully stored.");

            return(Task.CompletedTask);
        }
        public override async Task <ICredential> GenerateCredentialAsync(InputArguments input)
        {
            ThrowIfDisposed();

            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
            {
                throw new Exception("Unencrypted HTTP is not supported for Azure Repos. Ensure the repository remote URL is using HTTPS.");
            }

            Uri orgUri    = UriHelpers.CreateOrganizationUri(input);
            Uri remoteUri = input.GetRemoteUri();

            // Determine the MS authentication authority for this organization
            Context.Trace.WriteLine("Determining Microsoft Authentication Authority...");
            string authAuthority = await _azDevOps.GetAuthorityAsync(orgUri);

            Context.Trace.WriteLine($"Authority is '{authAuthority}'.");

            // Get an AAD access token for the Azure DevOps SPS
            Context.Trace.WriteLine("Getting Azure AD access token...");
            JsonWebToken accessToken = await _msAuth.GetAccessTokenAsync(
                authAuthority,
                AzureDevOpsConstants.AadClientId,
                AzureDevOpsConstants.AadRedirectUri,
                AzureDevOpsConstants.AadResourceId,
                remoteUri,
                null);

            string atUser = accessToken.GetAzureUserName();

            Context.Trace.WriteLineSecrets($"Acquired Azure access token. User='******' Token='{{0}}'", new object[] { accessToken.EncodedToken });

            // Ask the Azure DevOps instance to create a new PAT
            var patScopes = new[]
            {
                AzureDevOpsConstants.PersonalAccessTokenScopes.ReposWrite,
                AzureDevOpsConstants.PersonalAccessTokenScopes.ArtifactsRead
            };

            Context.Trace.WriteLine($"Creating Azure DevOps PAT with scopes '{string.Join(", ", patScopes)}'...");
            string pat = await _azDevOps.CreatePersonalAccessTokenAsync(
                orgUri,
                accessToken,
                patScopes);

            Context.Trace.WriteLineSecrets("PAT created. PAT='{0}'", new object[] { pat });

            return(new GitCredential(Constants.PersonalAccessTokenUserName, pat));
        }
示例#18
0
        private void VerifyOAuthFlowDidNotRun(string password, bool expected, InputArguments input, System.Threading.Tasks.Task <ICredential> credential)
        {
            Assert.Equal(expected, credential != null);

            var remoteUri = input.GetRemoteUri();

            // never prompt user through OAuth flow
            bitbucketAuthentication.Verify(m => m.ShowOAuthRequiredPromptAsync(), Times.Never);

            // Never try to refresh Access Token
            bitbucketAuthentication.Verify(m => m.RefreshOAuthCredentialsAsync(It.IsAny <string>()), Times.Never);

            // never check access token works
            bitbucketApi.Verify(m => m.GetUserInformationAsync(null, MOCK_ACCESS_TOKEN, true), Times.Never);
        }
示例#19
0
        private void VerifyBasicAuthFlowRan(string password, bool expected, InputArguments input, Task <ICredential> credential,
                                            string preconfiguredAuthModes)
        {
            Assert.Equal(expected, credential != null);

            var remoteUri = input.GetRemoteUri();

            bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()), Times.Once);

            // check username/password for Bitbucket.org
            if ((preconfiguredAuthModes == null && BITBUCKET_DOT_ORG_HOST == remoteUri.Host) ||
                (preconfiguredAuthModes != null && preconfiguredAuthModes.Contains("oauth")))
            {
                bitbucketApi.Verify(m => m.GetUserInformationAsync(input.UserName, password, false), Times.Once);
            }
        }
        public async Task <ICredential> GetCredentialAsync(InputArguments input)
        {
            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") &&
                input.TryGetHostAndPort(out string host, out _) && IsBitbucketOrg(host))
            {
                throw new Exception("Unencrypted HTTP is not supported for Bitbucket.org. Ensure the repository remote URL is using HTTPS.");
            }

            Uri remoteUri = input.GetRemoteUri();

            AuthenticationModes authModes = GetSupportedAuthenticationModes(remoteUri);

            return(await GetStoredCredentials(remoteUri, input.UserName, authModes) ??
                   await GetRefreshedCredentials(remoteUri, input.UserName, authModes));
        }
示例#21
0
        public void InputArguments_GetRemoteUri_Authority_ReturnsUriWithAuthority()
        {
            var expectedUri = new Uri("https://example.com/");

            var dict = new Dictionary <string, string>
            {
                ["protocol"] = "https",
                ["host"]     = "example.com"
            };

            var inputArgs = new InputArguments(dict);

            Uri actualUri = inputArgs.GetRemoteUri();

            Assert.NotNull(actualUri);
            Assert.Equal(expectedUri, actualUri);
        }
        // <remarks>Stores OAuth tokens as a side effect</remarks>
        public override async Task <ICredential> GetCredentialAsync(InputArguments input)
        {
            string      service    = GetServiceName(input);
            ICredential credential = Context.CredentialStore.Get(service, input.UserName);

            if (credential?.Account == "oauth2" && await IsOAuthTokenExpired(input.GetRemoteUri(), credential.Password))
            {
                Context.Trace.WriteLine("Removing expired OAuth access token...");
                Context.CredentialStore.Remove(service, credential.Account);
                credential = null;
            }

            if (credential != null)
            {
                return(credential);
            }

            string refreshService = GetRefreshTokenServiceName(input);
            string refreshToken   = Context.CredentialStore.Get(refreshService, input.UserName)?.Password;

            if (refreshToken != null)
            {
                Context.Trace.WriteLine("Refreshing OAuth token...");
                try
                {
                    credential = await RefreshOAuthCredentialAsync(input, refreshToken);
                }
                catch (Exception e)
                {
                    Context.Terminal.WriteLine($"OAuth token refresh failed: {e.Message}");
                }
            }

            credential ??= await GenerateCredentialAsync(input);

            if (credential is OAuthCredential oAuthCredential)
            {
                Context.Trace.WriteLine("Pre-emptively storing OAuth access and refresh tokens...");
                // freshly-generated OAuth credential
                // store credential, since we know it to be valid (whereas Git will only store credential if git push succeeds)
                Context.CredentialStore.AddOrUpdate(service, oAuthCredential.Account, oAuthCredential.AccessToken);
                // store refresh token under a separate service
                Context.CredentialStore.AddOrUpdate(refreshService, oAuthCredential.Account, oAuthCredential.RefreshToken);
            }
            return(credential);
        }
示例#23
0
        private void VerifyBasicAuthFlowNeverRan(string password, InputArguments input, bool storedAccount,
                                                 string preconfiguredAuthModes)
        {
            var remoteUri = input.GetRemoteUri();

            if (!storedAccount &&
                (preconfiguredAuthModes == null || preconfiguredAuthModes.Contains("basic")))
            {
                // never prompt the user for basic credentials
                bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()), Times.Once);
            }
            else
            {
                // never prompt the user for basic credentials
                bitbucketAuthentication.Verify(m => m.GetCredentialsAsync(remoteUri, input.UserName, It.IsAny <AuthenticationModes>()), Times.Never);
            }
        }
        private void VerifyBasicAuthFlowDidNotRun(string password, bool expected, InputArguments input, bool storedAccount, System.Threading.Tasks.Task <ICredential> credential,
                                                  string preconfiguredAuthModes)
        {
            Assert.Equal(expected, credential != null);

            var remoteUri = input.GetRemoteUri();

            if (!storedAccount &&
                (preconfiguredAuthModes == null || preconfiguredAuthModes.Contains("basic")))
            {
                // never prompt the user for basic credentials
                bitbucketAuthentication.Verify(m => m.GetBasicCredentialsAsync(remoteUri, input.UserName), Times.Once);
            }
            else
            {
                // never prompt the user for basic credentials
                bitbucketAuthentication.Verify(m => m.GetBasicCredentialsAsync(remoteUri, input.UserName), Times.Never);
            }
        }
        public Task EraseCredentialAsync(InputArguments input)
        {
            // Erase the stored credential (which may be either the literal username & password, or
            // the OAuth access token). We don't need to erase the OAuth refresh token because on the
            // next 'get' request, if the RT is bad we will erase and reacquire a new one at that point.
            Uri    remoteUri = input.GetRemoteUri();
            string service   = GetServiceName(remoteUri);

            _context.Trace.WriteLine("Erasing credential...");
            if (_context.CredentialStore.Remove(service, input.UserName))
            {
                _context.Trace.WriteLine("Credential was successfully erased.");
            }
            else
            {
                _context.Trace.WriteLine("Credential was not erased.");
            }

            return(Task.CompletedTask);
        }
示例#26
0
        public void InputArguments_GetRemoteUri_IncludeUser_Authority_ReturnsUriWithAuthorityAndUser()
        {
            var expectedUri = new Uri("https://[email protected]/");

            var dict = new Dictionary <string, string>
            {
                ["protocol"] = "https",
                ["host"]     = "example.com",

                // Username should appear in the returned URI; the password should not
                ["username"] = "******",
                ["password"] = "******"
            };

            var inputArgs = new InputArguments(dict);

            Uri actualUri = inputArgs.GetRemoteUri(includeUser: true);

            Assert.NotNull(actualUri);
            Assert.Equal(expectedUri, actualUri);
        }
示例#27
0
        public override async Task <ICredential> GenerateCredentialAsync(InputArguments input)
        {
            ThrowIfDisposed();

            // We should not allow unencrypted communication and should inform the user
            if (StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http"))
            {
                throw new Exception("Unencrypted HTTP is not supported for GitHub. Ensure the repository remote URL is using HTTPS.");
            }

            Uri remoteUri = input.GetRemoteUri();

            string service = GetServiceName(input);

            AuthenticationModes authModes = await GetSupportedAuthenticationModesAsync(remoteUri);

            AuthenticationPromptResult promptResult = await _gitHubAuth.GetAuthenticationAsync(remoteUri, input.UserName, authModes);

            switch (promptResult.AuthenticationMode)
            {
            case AuthenticationModes.Basic:
                GitCredential patCredential = await GeneratePersonalAccessTokenAsync(remoteUri, promptResult.BasicCredential);

                // HACK: Store the PAT immediately in case this PAT is not valid for SSO.
                // We don't know if this PAT is valid for SAML SSO and if it's not Git will fail
                // with a 403 and call neither 'store' or 'erase'. The user is expected to fiddle with
                // the PAT permissions manually on the web and then retry the Git operation.
                // We must store the PAT now so they can resume/repeat the operation with the same,
                // now SSO authorized, PAT.
                // See: https://github.com/microsoft/Git-Credential-Manager-Core/issues/133
                Context.CredentialStore.AddOrUpdate(service, patCredential.Account, patCredential.Password);
                return(patCredential);

            case AuthenticationModes.OAuth:
                return(await GenerateOAuthCredentialAsync(remoteUri));

            default:
                throw new ArgumentOutOfRangeException(nameof(promptResult));
            }
        }
示例#28
0
        public void InputArguments_GetRemoteUri_AuthorityPathUserInfo_ReturnsUriWithAuthorityAndPath()
        {
            var expectedUri = new Uri("https://example.com/an/example/path");

            var dict = new Dictionary <string, string>
            {
                ["protocol"] = "https",
                ["host"]     = "example.com",
                ["path"]     = "an/example/path",

                // Username and password are not expected to appear in the returned URI
                ["username"] = "******",
                ["password"] = "******"
            };

            var inputArgs = new InputArguments(dict);

            Uri actualUri = inputArgs.GetRemoteUri();

            Assert.NotNull(actualUri);
            Assert.Equal(expectedUri, actualUri);
        }
        public async Task <ICredential> GetCredentialAsync(InputArguments input)
        {
            Uri remoteUri = input.GetRemoteUri();

            if (UsePersonalAccessTokens())
            {
                string service = GetServiceName(remoteUri);
                string account = GetAccountNameForCredentialQuery(input);

                _context.Trace.WriteLine($"Looking for existing credential in store with service={service} account={account}...");

                ICredential credential = _context.CredentialStore.Get(service, account);
                if (credential == null)
                {
                    _context.Trace.WriteLine("No existing credentials found.");

                    // No existing credential was found, create a new one
                    _context.Trace.WriteLine("Creating new credential...");
                    credential = await GeneratePersonalAccessTokenAsync(input);

                    _context.Trace.WriteLine("Credential created.");
                }
                else
                {
                    _context.Trace.WriteLine("Existing credential found.");
                }

                return(credential);
            }
            else
            {
                // Include the username request here so that we may use it as an override
                // for user account lookups when getting Azure Access Tokens.
                var azureResult = await GetAzureAccessTokenAsync(remoteUri, input.UserName);

                return(new GitCredential(azureResult.AccountUpn, azureResult.AccessToken));
            }
        }
        public override bool IsSupported(InputArguments input)
        {
            if (input is null)
            {
                return(false);
            }

            // We do not support unencrypted HTTP communications to GitLab,
            // but we report `true` here for HTTP so that we can show a helpful
            // error message for the user in `CreateCredentialAsync`.
            if (!StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "http") &&
                !StringComparer.OrdinalIgnoreCase.Equals(input.Protocol, "https"))
            {
                return(false);
            }

            if (GitLabConstants.IsGitLabDotCom(input.GetRemoteUri()))
            {
                return(true);
            }

            // Split port number and hostname from host input argument
            if (!input.TryGetHostAndPort(out string hostName, out _))
            {
                return(false);
            }

            string[] domains = hostName.Split(new char[] { '.' });

            // GitLab[.subdomain].domain.tld
            if (domains.Length >= 3 &&
                StringComparer.OrdinalIgnoreCase.Equals(domains[0], "gitlab"))
            {
                return(true);
            }

            return(false);
        }