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 = _gitLabAuth.GetAuthentication(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));
            }
        }
Пример #2
0
 public BuildProviderExportAttribute(Type contractType, String guid, String name, AuthenticationModes supportedAuthenticationModes)
     : base(contractType)
 {
     Id   = guid;
     Name = name;
     SupportedAuthenticationModes = supportedAuthenticationModes;
 }
Пример #3
0
        /// <summary>
        /// Authentication
        /// </summary>
        /// <typeparam name="REQ"></typeparam>
        /// <typeparam name="RES"></typeparam>
        /// <param name="authentications"></param>
        /// <param name="credentials"></param>
        /// <returns></returns>
        internal MeisterStatus Authenticate <REQ, RES>(AuthenticationModes authentications, AuthenticationHeaderValue credentials)
        {
            string authHeader = credentials.Parameter;

            if (authHeader == null)
            {
                throw new MeisterException(resourceManager.GetString("BadAuthetication", CultureInfo.InvariantCulture));
            }
            switch (authentications)
            {
            case AuthenticationModes.Basic:
                return(RunAsBasicAuthentication(authHeader));

            case AuthenticationModes.OAuth:
                return(RunAsOAuth2Authentication(authHeader));

            case AuthenticationModes.JWT:
                break;

            case AuthenticationModes.SAML2:
                break;

            default:
                break;
            }
            return(new MeisterStatus());
        }
Пример #4
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));
            }
        }
        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 targetUri = GetTargetUri(input);

            AuthenticationModes authModes = await GetSupportedAuthenticationModesAsync(targetUri);

            AuthenticationPromptResult promptResult = await _gitHubAuth.GetAuthenticationAsync(targetUri, authModes);

            switch (promptResult.AuthenticationMode)
            {
            case AuthenticationModes.Basic:
                return(await GeneratePersonalAccessTokenAsync(targetUri, promptResult.BasicCredential));

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

            default:
                throw new ArgumentOutOfRangeException(nameof(promptResult));
            }
        }
        public async Task GitHubHostProvider_GetSupportedAuthenticationModes_OverrideNone_ReturnsDetectedValue()
        {
            var targetUri     = new Uri("https://github.com");
            var expectedModes = GitHubConstants.DotComAuthenticationModes;

            var context = new TestCommandContext
            {
                Settings =
                {
                    Environment   =
                    {
                        Variables =
                        {
                            [GitHubConstants.EnvironmentVariables.AuthenticationModes] = AuthenticationModes.None.ToString()
                        }
                    }
                }
            };

            var ghApiMock  = new Mock <IGitHubRestApi>(MockBehavior.Strict);
            var ghAuthMock = new Mock <IGitHubAuthentication>(MockBehavior.Strict);

            var provider = new GitHubHostProvider(context, ghApiMock.Object, ghAuthMock.Object);

            AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
Пример #7
0
        public async Task GitHubHostProvider_GetSupportedAuthenticationModes_WithMetadata(string uriString, string gitHubAuthModes,
                                                                                          string installedVersion, bool verifiablePasswordAuthentication, AuthenticationModes expectedModes)
        {
            var targetUri = new Uri(uriString);

            var context = new TestCommandContext {
            };

            if (gitHubAuthModes != null)
            {
                context.Environment.Variables.Add(GitHubConstants.EnvironmentVariables.AuthenticationModes, gitHubAuthModes);
            }

            var ghApiMock  = new Mock <IGitHubRestApi>(MockBehavior.Strict);
            var ghAuthMock = new Mock <IGitHubAuthentication>(MockBehavior.Strict);

            var metaInfo = new GitHubMetaInfo
            {
                InstalledVersion = installedVersion,
                VerifiablePasswordAuthentication = verifiablePasswordAuthentication
            };

            ghApiMock.Setup(x => x.GetMetaInfoAsync(targetUri)).ReturnsAsync(metaInfo);

            var provider = new GitHubHostProvider(context, ghApiMock.Object, ghAuthMock.Object);

            AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
        public async Task GitHubHostProvider_GetSupportedAuthenticationModes_GitHubDotCom_ReturnsDotComModes()
        {
            var targetUri     = new Uri("https://github.com");
            var expectedModes = GitHubConstants.DotComAuthenticationModes;

            var provider = new GitHubHostProvider(new TestCommandContext());

            AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
        public void GitLabHostProvider_GetSupportedAuthenticationModes_DotCom_ReturnsDotComModes()
        {
            Uri targetUri = GitLabConstants.GitLabDotCom;
            AuthenticationModes expected = GitLabConstants.DotComAuthenticationModes;

            var context  = new TestCommandContext();
            var provider = new GitLabHostProvider(context);
            AuthenticationModes actual = provider.GetSupportedAuthenticationModes(targetUri);

            Assert.Equal(expected, actual);
        }
        public void GitLabHostProvider_GetSupportedAuthenticationModes_Custom_NoOAuthConfig_ReturnsBasicPat()
        {
            var targetUri = new Uri("https://gitlab.example.com");
            var expected  = AuthenticationModes.Basic
                            | AuthenticationModes.Pat;

            var context  = new TestCommandContext();
            var provider = new GitLabHostProvider(context);
            AuthenticationModes actual = provider.GetSupportedAuthenticationModes(targetUri);

            Assert.Equal(expected, actual);
        }
Пример #11
0
        public AuthenticationViewModel(AuthenticationModes authMode, PersonViewModel person)
        {
            _personToAuthenticate = person;

            KeypadButtonPressedCommand = new RelayCommand <string>((p) =>
            {
                lock (this)
                {
                    pinEntered += p;
                    PinCheck(_personToAuthenticate.Pin);
                }
            });
        }
        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));
        }
        public void GitLabHostProvider_GetSupportedAuthenticationModes_Custom_WithOAuthConfig_ReturnsBasicPatBrowser()
        {
            var targetUri = new Uri("https://gitlab.example.com");
            var expected  = AuthenticationModes.Basic
                            | AuthenticationModes.Pat
                            | AuthenticationModes.Browser;

            var context = new TestCommandContext();

            context.Environment.Variables[GitLabConstants.EnvironmentVariables.DevOAuthClientId] = "abcdefg1234567";

            var provider = new GitLabHostProvider(context);
            AuthenticationModes actual = provider.GetSupportedAuthenticationModes(targetUri);

            Assert.Equal(expected, actual);
        }
Пример #14
0
    public bool Authenticate(string userid, string psw, Uri gatewayUri, string sapclient, bool od4)
    {
        string bae       = userid + ":" + psw;
        var    byteArray = Encoding.ASCII.GetBytes(bae);
        AuthenticationHeaderValue headerValue = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray));
        MeisterExtensions         me          = MeisterExtensions.RemoveNullsAndEmptyArrays;
        MeisterOptions            mo          = MeisterOptions.None;

        if (od4)
        {
            mo = MeisterOptions.UseODataV4;
        }
        RuntimeOptions      ro = RuntimeOptions.ExecuteSync;
        AuthenticationModes am = AuthenticationModes.Basic;

        resource = new Resource <dynamic, dynamic>(gatewayUri, headerValue, sapclient, me, mo, am, ro);
        return(resource.Authenticate());
    }
Пример #15
0
        public void BitbucketHostProvider_GetSupportedAuthenticationModes(string uriString, string bitbucketAuthModes, AuthenticationModes expectedModes)
        {
            var targetUri = new Uri(uriString);

            var context = new TestCommandContext {
            };

            if (bitbucketAuthModes != null)
            {
                context.Environment.Variables.Add(BitbucketConstants.EnvironmentVariables.AuthenticationModes, bitbucketAuthModes);
            }

            var provider = new BitbucketHostProvider(context, bitbucketAuthentication.Object, bitbucketApi.Object);

            AuthenticationModes actualModes = provider.GetSupportedAuthenticationModes(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
Пример #16
0
        public async Task GitHubHostProvider_GetSupportedAuthenticationModes(string uriString, string gitHubAuthModes, AuthenticationModes expectedModes)
        {
            var targetUri = new Uri(uriString);

            var context = new TestCommandContext {
            };

            if (gitHubAuthModes != null)
            {
                context.Environment.Variables.Add(GitHubConstants.EnvironmentVariables.AuthenticationModes, gitHubAuthModes);
            }

            var ghApiMock  = new Mock <IGitHubRestApi>(MockBehavior.Strict);
            var ghAuthMock = new Mock <IGitHubAuthentication>(MockBehavior.Strict);

            var provider = new GitHubHostProvider(context, ghApiMock.Object, ghAuthMock.Object);

            AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
Пример #17
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));
            }
        }
        public async Task GitHubHostProvider_GetSupportedAuthenticationModes_NotDotCom_NewInstanceWithPassword_ReturnsBasicAndOAuth()
        {
            var targetUri = new Uri("https://ghe.io");
            var metaInfo  = new GitHubMetaInfo
            {
                InstalledVersion = "100.0",
                VerifiablePasswordAuthentication = true
            };

            var expectedModes = AuthenticationModes.Basic | AuthenticationModes.OAuth;

            var context    = new TestCommandContext();
            var ghAuthMock = new Mock <IGitHubAuthentication>(MockBehavior.Strict);

            var ghApiMock = new Mock <IGitHubRestApi>(MockBehavior.Strict);

            ghApiMock.Setup(x => x.GetMetaInfoAsync(targetUri)).ReturnsAsync(metaInfo);

            var provider = new GitHubHostProvider(context, ghApiMock.Object, ghAuthMock.Object);

            AuthenticationModes actualModes = await provider.GetSupportedAuthenticationModesAsync(targetUri);

            Assert.Equal(expectedModes, actualModes);
        }
        private async Task <bool> ValidateCredentialsWork(Uri remoteUri, ICredential credentials, AuthenticationModes authModes)
        {
            if (credentials is null)
            {
                return(false);
            }

            // TODO: ideally we'd also check if the credentials have expired based on some local metadata
            // (once/if we get such metadata storage), and return false if they have.
            // This would be more efficient than having to make REST API calls to check.

            _context.Trace.WriteLineSecrets($"Validate credentials ({credentials.Account}/{{0}}) are fresh for {remoteUri} ...", new object[] { credentials.Password });

            if (!IsBitbucketOrg(remoteUri))
            {
                // TODO: Validate DC/Server credentials before returning them to Git
                // Currently credentials for DC/Server are not checked by GCM.
                // Instead the validation relies on Git to try and fail with the credentials and then request GCM to erase them
                _context.Trace.WriteLine("For DC/Server skip validating existing credentials");
                return(await Task.FromResult(true));
            }

            // Bitbucket supports both OAuth + Basic Auth unless there is explicit GCM configuration.
            // The credentials could be for either scheme therefore need to potentially test both possibilities.
            if (SupportsOAuth(authModes))
            {
                try
                {
                    await ResolveOAuthUserNameAsync(credentials.Password);

                    _context.Trace.WriteLine("Validated existing credentials using OAuth");
                    return(true);
                }
                catch (Exception)
                {
                    _context.Trace.WriteLine("Failed to validate existing credentials using OAuth");
                }
            }

            if (SupportsBasicAuth(authModes))
            {
                try
                {
                    await ResolveBasicAuthUserNameAsync(credentials.Account, credentials.Password);

                    _context.Trace.WriteLine("Validated existing credentials using BasicAuth");
                    return(true);
                }
                catch (Exception)
                {
                    _context.Trace.WriteLine("Failed to validate existing credentials using Basic Auth");
                    return(false);
                }
            }

            return(true);
        }
Пример #20
0
        public async Task <AuthenticationPromptResult> GetAuthenticationAsync(Uri targetUri, AuthenticationModes modes)
        {
            ThrowIfUserInteractionDisabled();

            // If the GitHub auth stack doesn't support flows such as RFC 8628 and we do not have
            // an interactive desktop session, we cannot offer OAuth authentication.
            if ((modes & AuthenticationModes.OAuth) != 0 &&
                !Context.SessionManager.IsDesktopSession &&
                !GitHubConstants.IsOAuthDeviceAuthSupported)
            {
                Context.Trace.WriteLine("Ignoring OAuth authentication mode because we are not in an interactive desktop session. GitHub does not support RFC 8628.");

                modes &= ~AuthenticationModes.OAuth;
            }

            if (modes == AuthenticationModes.None)
            {
                throw new ArgumentException($"Must specify at least one {nameof(AuthenticationModes)}", nameof(modes));
            }

            if (TryFindHelperExecutablePath(out string helperPath))
            {
                var promptArgs = new StringBuilder("prompt");
                if ((modes & AuthenticationModes.Basic) != 0)
                {
                    promptArgs.Append(" --basic");
                }
                if ((modes & AuthenticationModes.OAuth) != 0)
                {
                    promptArgs.Append(" --oauth");
                }
                if (!GitHubHostProvider.IsGitHubDotCom(targetUri))
                {
                    promptArgs.AppendFormat(" --enterprise-url {0}", targetUri.ToString());
                }

                IDictionary <string, string> resultDict = await InvokeHelperAsync(helperPath, promptArgs.ToString(), null);

                if (!resultDict.TryGetValue("mode", out string responseMode))
                {
                    throw new Exception("Missing 'mode' in response");
                }

                switch (responseMode.ToLowerInvariant())
                {
                case "oauth":
                    return(new AuthenticationPromptResult(AuthenticationModes.OAuth));

                case "basic":
                    if (!resultDict.TryGetValue("username", out string userName))
                    {
                        throw new Exception("Missing 'username' in response");
                    }

                    if (!resultDict.TryGetValue("password", out string password))
                    {
                        throw new Exception("Missing 'password' in response");
                    }

                    return(new AuthenticationPromptResult(new GitCredential(userName, password)));

                default:
                    throw new Exception($"Unknown mode value in response '{responseMode}'");
                }
            }
            else
            {
                ThrowIfTerminalPromptsDisabled();

                switch (modes)
                {
                case AuthenticationModes.Basic | AuthenticationModes.OAuth:
                    var menuTitle = $"Select an authentication method for '{targetUri}'";
                    var menu      = new TerminalMenu(Context.Terminal, menuTitle)
                    {
                        new TerminalMenuItem(1, "Web browser"),
                        new TerminalMenuItem(2, "Username/password", true)
                    };

                    int option = menu.Show();

                    if (option == 1)
                    {
                        goto case AuthenticationModes.OAuth;
                    }
                    if (option == 2)
                    {
                        goto case AuthenticationModes.Basic;
                    }

                    throw new Exception();

                case AuthenticationModes.Basic:
                    Context.Terminal.WriteLine("Enter GitHub credentials for '{0}'...", targetUri);
                    string userName = Context.Terminal.Prompt("Username");
                    string password = Context.Terminal.PromptSecret("Password");

                    return(new AuthenticationPromptResult(new GitCredential(userName, password)));

                case AuthenticationModes.OAuth:
                    return(new AuthenticationPromptResult(AuthenticationModes.OAuth));

                default:
                    throw new ArgumentOutOfRangeException(nameof(modes), $"Unknown {nameof(AuthenticationModes)} value");
                }
            }
        }
        private async Task <ICredential> GetStoredCredentials(Uri remoteUri, string userName, AuthenticationModes authModes)
        {
            if (_context.Settings.TryGetSetting(BitbucketConstants.EnvironmentVariables.AlwaysRefreshCredentials,
                                                Constants.GitConfiguration.Credential.SectionName, BitbucketConstants.GitConfiguration.Credential.AlwaysRefreshCredentials,
                                                out string alwaysRefreshCredentials) && alwaysRefreshCredentials.ToBooleanyOrDefault(false))
            {
                _context.Trace.WriteLine("Ignore stored credentials");
                return(null);
            }

            string credentialService = GetServiceName(remoteUri);

            _context.Trace.WriteLine($"Look for existing credentials under {credentialService} ...");

            ICredential credentials = _context.CredentialStore.Get(credentialService, userName);

            if (credentials == null)
            {
                _context.Trace.WriteLine("No stored credentials found");
                return(null);
            }

            _context.Trace.WriteLineSecrets($"Found stored credentials: {credentials.Account}/{{0}}", new object[] { credentials.Password });

            // Check credentials are still valid
            if (!await ValidateCredentialsWork(remoteUri, credentials, authModes))
            {
                return(null);
            }

            return(credentials);
        }
        private async Task <ICredential> GetRefreshedCredentials(Uri remoteUri, string userName, AuthenticationModes authModes)
        {
            _context.Trace.WriteLine("Refresh credentials...");

            // Check for presence of refresh_token entry in credential store
            var refreshTokenService = GetRefreshTokenServiceName(remoteUri);

            _context.Trace.WriteLine("Checking for refresh token...");
            ICredential refreshToken = SupportsOAuth(authModes)
                ? _context.CredentialStore.Get(refreshTokenService, userName)
                : null;

            if (refreshToken is null)
            {
                _context.Trace.WriteLine($"No stored refresh token found");
                // There is no refresh token either because this is a non-2FA enabled account (where OAuth is not
                // required), or because we previously erased the RT.

                bool skipOAuthPrompt = false;

                if (SupportsBasicAuth(authModes))
                {
                    _context.Trace.WriteLine("Prompt for credentials...");

                    // We don't have any credentials to use at all! Start with the assumption of no 2FA requirement
                    // and capture username and password via an interactive prompt.
                    var result = await _bitbucketAuth.GetCredentialsAsync(remoteUri, userName, authModes);

                    if (result is null || result.AuthenticationMode == AuthenticationModes.None)
                    {
                        _context.Trace.WriteLine("User cancelled credential prompt");
                        throw new Exception("User cancelled credential prompt.");
                    }

                    switch (result.AuthenticationMode)
                    {
                    case AuthenticationModes.OAuth:
                        // If the user wants to use OAuth fall through to interactive auth
                        // and avoid the OAuth "continue" confirmation prompt
                        skipOAuthPrompt = true;
                        break;

                    case AuthenticationModes.Basic:
                        // We need to check if these user/pass credentials require 2FA
                        _context.Trace.WriteLine("Checking if two-factor requirements for credentials...");

                        bool requires2Fa = await RequiresTwoFactorAuthenticationAsync(result.Credential);

                        if (!requires2Fa)
                        {
                            _context.Trace.WriteLine("Two-factor authentication not required");

                            // Return the valid credential
                            return(result.Credential);
                        }

                        // 2FA is required; fall through to interactive OAuth flow
                        break;

                    default:
                        throw new ArgumentOutOfRangeException(
                                  $"Unexpected {nameof(AuthenticationModes)} returned from prompt");
                    }

                    // Fall through to the start of the interactive OAuth authentication flow
                }

                if (SupportsOAuth(authModes) && !skipOAuthPrompt)
                {
                    _context.Trace.WriteLine("Two-factor authentication is required - prompting for auth via OAuth...");
                    _context.Trace.WriteLine("Prompt for OAuth...");

                    // Show the 2FA/OAuth authentication required prompt
                    bool @continue = await _bitbucketAuth.ShowOAuthRequiredPromptAsync();

                    if (!@continue)
                    {
                        _context.Trace.WriteLine("User cancelled OAuth prompt");
                        throw new Exception("User cancelled OAuth authentication.");
                    }

                    // Fall through to the start of the interactive OAuth authentication flow
                }
            }
            else
            {
                _context.Trace.WriteLineSecrets($"Found stored refresh token: {{0}}", new object[] { refreshToken });

                try
                {
                    return(await GetOAuthCredentialsViaRefreshFlow(remoteUri, refreshToken));
                }
                catch (OAuth2Exception ex)
                {
                    _context.Trace.WriteLine("Failed to refresh existing OAuth credential using refresh token");
                    _context.Trace.WriteException(ex);

                    // We failed to refresh the AT using the RT; log the refresh failure and fall through to restart
                    // the OAuth authentication flow
                }
            }

            return(await GetOAuthCredentialsViaInteractiveBrowserFlow(remoteUri));
        }
 private static bool SupportsBasicAuth(AuthenticationModes authModes)
 {
     return((authModes & AuthenticationModes.Basic) != 0);
 }
Пример #24
0
 public AuthenticationPromptResult(AuthenticationModes mode, ICredential credential)
     : this(mode)
 {
     Credential = credential;
 }
Пример #25
0
        public async Task <AuthenticationPromptResult> GetAuthenticationAsync(Uri targetUri, string userName, AuthenticationModes modes)
        {
            // If we don't have a desktop session/GUI then we cannot offer browser
            if (!Context.SessionManager.IsDesktopSession)
            {
                modes = modes & ~AuthenticationModes.Browser;
            }

            // We need at least one mode!
            if (modes == AuthenticationModes.None)
            {
                throw new ArgumentException(@$ "Must specify at least one {nameof(AuthenticationModes)}", nameof(modes));
            }

            if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
                TryFindHelperExecutablePath(out string helperPath))
            {
                var cmdArgs = new StringBuilder("prompt");
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    cmdArgs.AppendFormat(" --username {0}", QuoteCmdArg(userName));
                }

                cmdArgs.AppendFormat(" --url {0}", QuoteCmdArg(targetUri.ToString()));

                if ((modes & AuthenticationModes.Basic) != 0)
                {
                    cmdArgs.Append(" --basic");
                }
                if ((modes & AuthenticationModes.Browser) != 0)
                {
                    cmdArgs.Append(" --browser");
                }
                if ((modes & AuthenticationModes.Pat) != 0)
                {
                    cmdArgs.Append(" --pat");
                }

                IDictionary <string, string> resultDict = await InvokeHelperAsync(helperPath, cmdArgs.ToString());

                if (!resultDict.TryGetValue("mode", out string responseMode))
                {
                    throw new Exception("Missing 'mode' in response");
                }

                switch (responseMode.ToLowerInvariant())
                {
                case "pat":
                    if (!resultDict.TryGetValue("pat", out string pat))
                    {
                        throw new Exception("Missing 'pat' in response");
                    }

                    if (!resultDict.TryGetValue("username", out string patUserName))
                    {
                        // Username is optional for PATs
                    }

                    return(new AuthenticationPromptResult(
                               AuthenticationModes.Pat, new GitCredential(patUserName, pat)));

                case "browser":
                    return(new AuthenticationPromptResult(AuthenticationModes.Browser));

                case "basic":
                    if (!resultDict.TryGetValue("username", out userName))
                    {
                        throw new Exception("Missing 'username' in response");
                    }

                    if (!resultDict.TryGetValue("password", out string password))
                    {
                        throw new Exception("Missing 'password' in response");
                    }

                    return(new AuthenticationPromptResult(
                               AuthenticationModes.Basic, new GitCredential(userName, password)));

                default:
                    throw new Exception($"Unknown mode value in response '{responseMode}'");
                }
            }
            else
            {
                switch (modes)
                {
                case AuthenticationModes.Basic:
                    ThrowIfUserInteractionDisabled();
                    ThrowIfTerminalPromptsDisabled();
                    Context.Terminal.WriteLine("Enter GitLab credentials for '{0}'...", targetUri);

                    if (string.IsNullOrWhiteSpace(userName))
                    {
                        userName = Context.Terminal.Prompt("Username");
                    }
                    else
                    {
                        Context.Terminal.WriteLine("Username: {0}", userName);
                    }

                    string password = Context.Terminal.PromptSecret("Password");
                    return(new AuthenticationPromptResult(AuthenticationModes.Basic, new GitCredential(userName, password)));

                case AuthenticationModes.Pat:
                    ThrowIfUserInteractionDisabled();
                    ThrowIfTerminalPromptsDisabled();
                    Context.Terminal.WriteLine("Enter GitLab credentials for '{0}'...", targetUri);

                    if (string.IsNullOrWhiteSpace(userName))
                    {
                        userName = Context.Terminal.Prompt("Username");
                    }
                    else
                    {
                        Context.Terminal.WriteLine("Username: {0}", userName);
                    }

                    string token = Context.Terminal.PromptSecret("Personal access token");
                    return(new AuthenticationPromptResult(AuthenticationModes.Pat, new GitCredential(userName, token)));

                case AuthenticationModes.Browser:
                    return(new AuthenticationPromptResult(AuthenticationModes.Browser));

                case AuthenticationModes.None:
                    throw new ArgumentOutOfRangeException(nameof(modes), @$ "At least one {nameof(AuthenticationModes)} must be supplied");

                default:
                    ThrowIfUserInteractionDisabled();
                    ThrowIfTerminalPromptsDisabled();
                    var menuTitle = $"Select an authentication method for '{targetUri}'";
                    var menu      = new TerminalMenu(Context.Terminal, menuTitle);

                    TerminalMenuItem browserItem = null;
                    TerminalMenuItem basicItem   = null;
                    TerminalMenuItem patItem     = null;

                    if ((modes & AuthenticationModes.Browser) != 0)
                    {
                        browserItem = menu.Add("Web browser");
                    }
                    if ((modes & AuthenticationModes.Pat) != 0)
                    {
                        patItem = menu.Add("Personal access token");
                    }
                    if ((modes & AuthenticationModes.Basic) != 0)
                    {
                        basicItem = menu.Add("Username/password");
                    }

                    // Default to the 'first' choice in the menu
                    TerminalMenuItem choice = menu.Show(0);

                    if (choice == browserItem)
                    {
                        goto case AuthenticationModes.Browser;
                    }
                    if (choice == basicItem)
                    {
                        goto case AuthenticationModes.Basic;
                    }
                    if (choice == patItem)
                    {
                        goto case AuthenticationModes.Pat;
                    }

                    throw new Exception();
                }
            }
        }
Пример #26
0
 public AuthenticationPromptResult(AuthenticationModes mode)
 {
     AuthenticationMode = mode;
 }
Пример #27
0
        public async Task <CredentialsPromptResult> GetCredentialsAsync(Uri targetUri, string userName, AuthenticationModes modes)
        {
            ThrowIfUserInteractionDisabled();

            string password;

            // If we don't have a desktop session/GUI then we cannot offer OAuth since the only
            // supported grant is authcode (i.e, using a web browser; device code is not supported).
            if (!Context.SessionManager.IsDesktopSession)
            {
                modes = modes & ~AuthenticationModes.OAuth;
            }

            // If the only supported mode is OAuth then just return immediately
            if (modes == AuthenticationModes.OAuth)
            {
                return(new CredentialsPromptResult(AuthenticationModes.OAuth));
            }

            // We need at least one mode!
            if (modes == AuthenticationModes.None)
            {
                throw new ArgumentException(@$ "Must specify at least one {nameof(AuthenticationModes)}", nameof(modes));
            }

            // Shell out to the UI helper and show the Bitbucket u/p prompt
            if (Context.Settings.IsGuiPromptsEnabled && Context.SessionManager.IsDesktopSession &&
                TryFindHelperExecutablePath(out string helperPath))
            {
                var cmdArgs = new StringBuilder("userpass");
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    cmdArgs.AppendFormat(" --username {0}", QuoteCmdArg(userName));
                }

                if ((modes & AuthenticationModes.OAuth) != 0)
                {
                    cmdArgs.Append(" --show-oauth");
                }

                IDictionary <string, string> output = await InvokeHelperAsync(helperPath, cmdArgs.ToString());

                if (output.TryGetValue("mode", out string mode) &&
                    StringComparer.OrdinalIgnoreCase.Equals(mode, "oauth"))
                {
                    return(new CredentialsPromptResult(AuthenticationModes.OAuth));
                }
                else
                {
                    if (!output.TryGetValue("username", out userName))
                    {
                        throw new Exception("Missing username in response");
                    }

                    if (!output.TryGetValue("password", out password))
                    {
                        throw new Exception("Missing password in response");
                    }

                    return(new CredentialsPromptResult(
                               AuthenticationModes.Basic,
                               new GitCredential(userName, password)));
                }
            }
            else
            {
                ThrowIfTerminalPromptsDisabled();

                switch (modes)
                {
                case AuthenticationModes.Basic:
                    Context.Terminal.WriteLine("Enter Bitbucket credentials for '{0}'...", targetUri);

                    if (!string.IsNullOrWhiteSpace(userName))
                    {
                        // Don't need to prompt for the username if it has been specified already
                        Context.Terminal.WriteLine("Username: {0}", userName);
                    }
                    else
                    {
                        // Prompt for username
                        userName = Context.Terminal.Prompt("Username");
                    }

                    // Prompt for password
                    password = Context.Terminal.PromptSecret("Password");

                    return(new CredentialsPromptResult(
                               AuthenticationModes.Basic,
                               new GitCredential(userName, password)));

                case AuthenticationModes.OAuth:
                    return(new CredentialsPromptResult(AuthenticationModes.OAuth));

                case AuthenticationModes.None:
                    throw new ArgumentOutOfRangeException(nameof(modes), @$ "At least one {nameof(AuthenticationModes)} must be supplied");

                default:
                    var menuTitle = $"Select an authentication method for '{targetUri}'";
                    var menu      = new TerminalMenu(Context.Terminal, menuTitle);

                    TerminalMenuItem oauthItem = null;
                    TerminalMenuItem basicItem = null;

                    if ((modes & AuthenticationModes.OAuth) != 0)
                    {
                        oauthItem = menu.Add("OAuth");
                    }
                    if ((modes & AuthenticationModes.Basic) != 0)
                    {
                        basicItem = menu.Add("Username/password");
                    }

                    // Default to the 'first' choice in the menu
                    TerminalMenuItem choice = menu.Show(0);

                    if (choice == oauthItem)
                    {
                        goto case AuthenticationModes.OAuth;
                    }
                    if (choice == basicItem)
                    {
                        goto case AuthenticationModes.Basic;
                    }

                    throw new Exception();
                }
            }
        }
Пример #28
0
 public CredentialsPromptResult(AuthenticationModes mode)
 {
     AuthenticationMode = mode;
 }
 private static bool SupportsOAuth(AuthenticationModes authModes)
 {
     return((authModes & AuthenticationModes.OAuth) != 0);
 }
        public async Task <AuthenticationPromptResult> GetAuthenticationAsync(Uri targetUri, string userName, AuthenticationModes modes)
        {
            ThrowIfUserInteractionDisabled();

            if (modes == AuthenticationModes.None)
            {
                throw new ArgumentException($"Must specify at least one {nameof(AuthenticationModes)}", nameof(modes));
            }

            if (TryFindHelperExecutablePath(out string helperPath))
            {
                var promptArgs = new StringBuilder("prompt");
                if ((modes & AuthenticationModes.Basic) != 0)
                {
                    promptArgs.Append(" --basic");
                }
                if ((modes & AuthenticationModes.OAuth) != 0)
                {
                    promptArgs.Append(" --oauth");
                }
                if (!GitHubHostProvider.IsGitHubDotCom(targetUri))
                {
                    promptArgs.AppendFormat(" --enterprise-url {0}", targetUri);
                }
                if (!string.IsNullOrWhiteSpace(userName))
                {
                    promptArgs.AppendFormat("--username {0}", userName);
                }

                IDictionary <string, string> resultDict = await InvokeHelperAsync(helperPath, promptArgs.ToString(), null);

                if (!resultDict.TryGetValue("mode", out string responseMode))
                {
                    throw new Exception("Missing 'mode' in response");
                }

                switch (responseMode.ToLowerInvariant())
                {
                case "oauth":
                    return(new AuthenticationPromptResult(AuthenticationModes.OAuth));

                case "basic":
                    if (!resultDict.TryGetValue("username", out userName))
                    {
                        throw new Exception("Missing 'username' in response");
                    }

                    if (!resultDict.TryGetValue("password", out string password))
                    {
                        throw new Exception("Missing 'password' in response");
                    }

                    return(new AuthenticationPromptResult(new GitCredential(userName, password)));

                default:
                    throw new Exception($"Unknown mode value in response '{responseMode}'");
                }
            }
            else
            {
                ThrowIfTerminalPromptsDisabled();

                switch (modes)
                {
                case AuthenticationModes.Basic | AuthenticationModes.OAuth:
                    var menuTitle = $"Select an authentication method for '{targetUri}'";
                    var menu      = new TerminalMenu(Context.Terminal, menuTitle)
                    {
                        new TerminalMenuItem(1, "Web browser", isDefault: true),
                        new TerminalMenuItem(2, "Username/password")
                    };

                    int option = menu.Show();

                    if (option == 1)
                    {
                        goto case AuthenticationModes.OAuth;
                    }
                    if (option == 2)
                    {
                        goto case AuthenticationModes.Basic;
                    }

                    throw new Exception();

                case AuthenticationModes.Basic:
                    Context.Terminal.WriteLine("Enter GitHub credentials for '{0}'...", targetUri);

                    if (string.IsNullOrWhiteSpace(userName))
                    {
                        userName = Context.Terminal.Prompt("Username");
                    }
                    else
                    {
                        Context.Terminal.WriteLine("Username: {0}", userName);
                    }

                    string password = Context.Terminal.PromptSecret("Password");

                    return(new AuthenticationPromptResult(new GitCredential(userName, password)));

                case AuthenticationModes.OAuth:
                    return(new AuthenticationPromptResult(AuthenticationModes.OAuth));

                default:
                    throw new ArgumentOutOfRangeException(nameof(modes), $"Unknown {nameof(AuthenticationModes)} value");
                }
            }
        }