package config import ( "errors" "testing" "github.com/cli/cli/v2/internal/config/migration" "github.com/cli/cli/v2/internal/keyring" ghConfig "github.com/cli/go-gh/v2/pkg/config" "github.com/stretchr/testify/require" ) // Note that NewIsolatedTestConfig sets up a Mock keyring as well func newTestAuthConfig(t *testing.T) *AuthConfig { cfg, _ := NewIsolatedTestConfig(t) return cfg.Authentication() } func TestTokenFromKeyring(t *testing.T) { // Given a keyring that contains a token for a host authCfg := newTestAuthConfig(t) require.NoError(t, keyring.Set(keyringServiceName("github.com"), "", "test-token")) // When we get the token from the auth config token, err := authCfg.TokenFromKeyring("github.com") // Then it returns successfully with the correct token require.NoError(t, err) require.Equal(t, "test-token", token) } func TestTokenFromKeyringForUser(t *testing.T) { // Given a keyring that contains a token for a host with a specific user authCfg := newTestAuthConfig(t) require.NoError(t, keyring.Set(keyringServiceName("github.com"), "test-user", "test-token")) // When we get the token from the auth config token, err := authCfg.TokenFromKeyringForUser("github.com", "test-user") // Then it returns successfully with the correct token require.NoError(t, err) require.Equal(t, "test-token", token) } func TestTokenFromKeyringForUserErrorsIfUsernameIsBlank(t *testing.T) { authCfg := newTestAuthConfig(t) // When we get the token from the keyring for an empty username _, err := authCfg.TokenFromKeyringForUser("github.com", "") // Then it returns an error require.ErrorContains(t, err, "username cannot be blank") } func TestTokenStoredInConfig(t *testing.T) { // When the user has logged in insecurely authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user", "test-token", "", false) require.NoError(t, err) // When we get the token token, source := authCfg.ActiveToken("github.com") // Then the token is successfully fetched // and the source is set to oauth_token but this isn't great: // https://github.com/cli/go-gh/issues/94 require.Equal(t, "test-token", token) require.Equal(t, oauthTokenKey, source) } func TestTokenStoredInEnv(t *testing.T) { // When the user is authenticated via env var authCfg := newTestAuthConfig(t) t.Setenv("GH_TOKEN", "test-token") // When we get the token token, source := authCfg.ActiveToken("github.com") // Then the token is successfully fetched // and the source is set to the name of the env var require.Equal(t, "test-token", token) require.Equal(t, "GH_TOKEN", source) } func TestTokenStoredInKeyring(t *testing.T) { // When the user has logged in securely authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user", "test-token", "", true) require.NoError(t, err) // When we get the token token, source := authCfg.ActiveToken("github.com") // Then the token is successfully fetched // and the source is set to keyring require.Equal(t, "test-token", token) require.Equal(t, "keyring", source) } func TestTokenFromKeyringNonExistent(t *testing.T) { // Given a keyring that doesn't contain any tokens authCfg := newTestAuthConfig(t) // When we try to get a token from the auth config _, err := authCfg.TokenFromKeyring("github.com") // Then it returns failure bubbling the ErrNotFound require.ErrorContains(t, err, "secret not found in keyring") } func TestHasEnvTokenWithoutAnyEnvToken(t *testing.T) { // Given we have no env set authCfg := newTestAuthConfig(t) // When we check if it has an env token hasEnvToken := authCfg.HasEnvToken() // Then it returns false require.False(t, hasEnvToken, "expected not to have env token") } func TestHasEnvTokenWithEnvToken(t *testing.T) { // Given we have an env token set // Note that any valid env var for tokens will do, not just GH_ENTERPRISE_TOKEN authCfg := newTestAuthConfig(t) t.Setenv("GH_ENTERPRISE_TOKEN", "test-token") // When we check if it has an env token hasEnvToken := authCfg.HasEnvToken() // Then it returns true require.True(t, hasEnvToken, "expected to have env token") } func TestHasEnvTokenWithNoEnvTokenButAConfigVar(t *testing.T) { t.Skip("this test is explicitly breaking some implementation assumptions") // Given a token in the config authCfg := newTestAuthConfig(t) // Using example.com here will cause the token to be returned from the config _, err := authCfg.Login("example.com", "test-user", "test-token", "", false) require.NoError(t, err) // When we check if it has an env token hasEnvToken := authCfg.HasEnvToken() // Then it SHOULD return false require.False(t, hasEnvToken, "expected not to have env token") } func TestUserNotLoggedIn(t *testing.T) { // Given we have not logged in authCfg := newTestAuthConfig(t) // When we get the user _, err := authCfg.ActiveUser("github.com") // Then it returns failure, bubbling the KeyNotFoundError var keyNotFoundError *ghConfig.KeyNotFoundError require.ErrorAs(t, err, &keyNotFoundError) } func TestHostsIncludesEnvVar(t *testing.T) { // Given the GH_HOST env var is set authCfg := newTestAuthConfig(t) t.Setenv("GH_HOST", "ghe.io") // When we get the hosts hosts := authCfg.Hosts() // Then the host in the env var is included require.Contains(t, hosts, "ghe.io") } func TestDefaultHostFromEnvVar(t *testing.T) { // Given the GH_HOST env var is set authCfg := newTestAuthConfig(t) t.Setenv("GH_HOST", "ghe.io") // When we get the DefaultHost defaultHost, source := authCfg.DefaultHost() // Then the returned host and source are using the env var require.Equal(t, "ghe.io", defaultHost) require.Equal(t, "GH_HOST", source) } func TestDefaultHostNotLoggedIn(t *testing.T) { // Given we are not logged in authCfg := newTestAuthConfig(t) // When we get the DefaultHost defaultHost, source := authCfg.DefaultHost() // Then the returned host is always github.com require.Equal(t, "github.com", defaultHost) require.Equal(t, "default", source) } func TestDefaultHostLoggedInToOnlyOneHost(t *testing.T) { // Given we are logged into one host (not github.com to differentiate from the fallback) authCfg := newTestAuthConfig(t) _, err := authCfg.Login("ghe.io", "test-user", "test-token", "", false) require.NoError(t, err) // When we get the DefaultHost defaultHost, source := authCfg.DefaultHost() // Then the returned host is that logged in host and the source is the hosts config require.Equal(t, "ghe.io", defaultHost) require.Equal(t, hostsKey, source) } func TestLoginSecureStorageUsesKeyring(t *testing.T) { // Given a usable keyring authCfg := newTestAuthConfig(t) host := "github.com" user := "test-user" token := "test-token" // When we login with secure storage insecureStorageUsed, err := authCfg.Login(host, user, token, "", true) // Then it returns success, notes that insecure storage was not used, and stores the token in the keyring require.NoError(t, err) require.False(t, insecureStorageUsed, "expected to use secure storage") gotToken, err := keyring.Get(keyringServiceName(host), "") require.NoError(t, err) require.Equal(t, token, gotToken) gotToken, err = keyring.Get(keyringServiceName(host), user) require.NoError(t, err) require.Equal(t, token, gotToken) } func TestLoginSecureStorageRemovesOldInsecureConfigToken(t *testing.T) { // Given a usable keyring and an oauth token in the config authCfg := newTestAuthConfig(t) authCfg.cfg.Set([]string{hostsKey, "github.com", oauthTokenKey}, "old-token") // When we login with secure storage _, err := authCfg.Login("github.com", "test-user", "test-token", "", true) // Then it returns success, having also removed the old token from the config require.NoError(t, err) requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}) } func TestLoginSecureStorageWithErrorFallsbackAndReports(t *testing.T) { // Given a keyring that errors authCfg := newTestAuthConfig(t) keyring.MockInitWithError(errors.New("test-explosion")) // When we login with secure storage insecureStorageUsed, err := authCfg.Login("github.com", "test-user", "test-token", "", true) // Then it returns success, reports that insecure storage was used, and stores the token in the config require.NoError(t, err) require.True(t, insecureStorageUsed, "expected to use insecure storage") requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}, "test-token") } func TestLoginInsecureStorage(t *testing.T) { // Given we are not logged in authCfg := newTestAuthConfig(t) // When we login with insecure storage insecureStorageUsed, err := authCfg.Login("github.com", "test-user", "test-token", "", false) // Then it returns success, notes that insecure storage was used, and stores the token in the config require.NoError(t, err) require.True(t, insecureStorageUsed, "expected to use insecure storage") requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}, "test-token") } func TestLoginSetsUserForProvidedHost(t *testing.T) { // Given we are not logged in authCfg := newTestAuthConfig(t) // When we login _, err := authCfg.Login("github.com", "test-user", "test-token", "ssh", false) // Then it returns success and the user is set require.NoError(t, err) user, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "test-user", user) } func TestLoginSetsGitProtocolForProvidedHost(t *testing.T) { // Given we are logged in authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we get the host git protocol hostProtocol, err := authCfg.cfg.Get([]string{hostsKey, "github.com", gitProtocolKey}) require.NoError(t, err) // Then it returns the git protocol we provided on login require.Equal(t, "ssh", hostProtocol) } func TestLoginAddsHostIfNotAlreadyAdded(t *testing.T) { // Given we are logged in authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we get the hosts hosts := authCfg.Hosts() // Then it includes our logged in host require.Contains(t, hosts, "github.com") } // This test mimics the behaviour of logging in with a token, not providing // a git protocol, and using secure storage. func TestLoginAddsUserToConfigWithoutGitProtocolAndWithSecureStorage(t *testing.T) { // Given we are not logged in authCfg := newTestAuthConfig(t) // When we log in without git protocol and with secure storage _, err := authCfg.Login("github.com", "test-user", "test-token", "", true) require.NoError(t, err) // Then the username is added under the users config users, err := authCfg.cfg.Keys([]string{hostsKey, "github.com", usersKey}) require.NoError(t, err) require.Contains(t, users, "test-user") } func TestLogoutRemovesHostAndKeyringToken(t *testing.T) { // Given we are logged into a host authCfg := newTestAuthConfig(t) host := "github.com" user := "test-user" token := "test-token" _, err := authCfg.Login(host, user, token, "ssh", true) require.NoError(t, err) // When we logout err = authCfg.Logout(host, user) // Then we return success, and the host and token are removed from the config and keyring require.NoError(t, err) requireNoKey(t, authCfg.cfg, []string{hostsKey, host}) _, err = keyring.Get(keyringServiceName(host), "") require.ErrorContains(t, err, "secret not found in keyring") _, err = keyring.Get(keyringServiceName(host), user) require.ErrorContains(t, err, "secret not found in keyring") } func TestLogoutOfActiveUserSwitchesUserIfPossible(t *testing.T) { // Given we have two accounts logged into a host authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "inactive-user", "test-token-1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "active-user", "test-token-2", "https", true) require.NoError(t, err) // When we logout of the active user err = authCfg.Logout("github.com", "active-user") // Then we return success and the inactive user is now active require.NoError(t, err) activeUser, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "inactive-user", activeUser) token, err := authCfg.TokenFromKeyring("github.com") require.NoError(t, err) require.Equal(t, "test-token-1", token) usersForHost := authCfg.UsersForHost("github.com") require.NotContains(t, "active-user", usersForHost) } func TestLogoutOfInactiveUserDoesNotSwitchUser(t *testing.T) { // Given we have two accounts logged into a host authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "inactive-user-1", "test-token-1.1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "inactive-user-2", "test-token-1.2", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "active-user", "test-token-2", "https", true) require.NoError(t, err) // When we logout of an inactive user err = authCfg.Logout("github.com", "inactive-user-1") // Then we return success and the active user is still active require.NoError(t, err) activeUser, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "active-user", activeUser) } // Note that I'm not sure this test enforces particularly desirable behaviour // since it leads users to believe a token has been removed when really // that might have failed for some reason. // // The original intention here is that if the logout fails, the user can't // really do anything to recover. On the other hand, a user might // want to rectify this manually, for example if there were on a shared machine. func TestLogoutIgnoresErrorsFromConfigAndKeyring(t *testing.T) { // Given we have keyring that errors, and a config that // doesn't even have a hosts key (which would cause Remove to fail) keyring.MockInitWithError(errors.New("test-explosion")) authCfg := newTestAuthConfig(t) // When we logout err := authCfg.Logout("github.com", "test-user") // Then it returns success anyway, suppressing the errors require.NoError(t, err) } func TestSwitchUserMakesSecureTokenActive(t *testing.T) { // Given we have a user with a secure token authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", true) require.NoError(t, err) // When we switch to that user require.NoError(t, authCfg.SwitchUser("github.com", "test-user-1")) // Their secure token is now active token, err := authCfg.TokenFromKeyring("github.com") require.NoError(t, err) require.Equal(t, "test-token-1", token) } func TestSwitchUserMakesInsecureTokenActive(t *testing.T) { // Given we have a user with an insecure token authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", false) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", false) require.NoError(t, err) // When we switch to that user require.NoError(t, authCfg.SwitchUser("github.com", "test-user-1")) // Their insecure token is now active token, source := authCfg.ActiveToken("github.com") require.Equal(t, "test-token-1", token) require.Equal(t, oauthTokenKey, source) } func TestSwitchUserUpdatesTheActiveUser(t *testing.T) { // Given we have two users logged into a host authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", false) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", false) require.NoError(t, err) // When we switch to the other user require.NoError(t, authCfg.SwitchUser("github.com", "test-user-1")) // Then the active user is updated activeUser, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "test-user-1", activeUser) } func TestSwitchUserErrorsImmediatelyIfTheActiveTokenComesFromEnvironment(t *testing.T) { // Given we have a token in the env authCfg := newTestAuthConfig(t) t.Setenv("GH_TOKEN", "unimportant-test-value") _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", true) require.NoError(t, err) // When we switch to a user err = authCfg.SwitchUser("github.com", "test-user-1") // Then it errors immediately with an informative message require.ErrorContains(t, err, "currently active token for github.com is from GH_TOKEN") } func TestSwitchUserErrorsAndRestoresUserAndInsecureConfigUnderFailure(t *testing.T) { // Given we have a user but no token can be found (because we deleted them, simulating an error case) authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", false) require.NoError(t, err) require.NoError(t, keyring.Delete(keyringServiceName("github.com"), "test-user-1")) // When we switch to the user err = authCfg.SwitchUser("github.com", "test-user-1") // Then it returns an error require.EqualError(t, err, "no token found for test-user-1") // And restores the previous state activeUser, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "test-user-2", activeUser) token, source := authCfg.ActiveToken("github.com") require.Equal(t, "test-token-2", token) require.Equal(t, "oauth_token", source) } func TestSwitchUserErrorsAndRestoresUserAndKeyringUnderFailure(t *testing.T) { // Given we have a user but no token can be found (because we deleted them, simulating an error case) authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", false) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", true) require.NoError(t, err) require.NoError(t, authCfg.cfg.Remove([]string{hostsKey, "github.com", usersKey, "test-user-1", oauthTokenKey})) // When we switch to the user err = authCfg.SwitchUser("github.com", "test-user-1") // Then it returns an error require.EqualError(t, err, "no token found for test-user-1") // And restores the previous state activeUser, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "test-user-2", activeUser) token, source := authCfg.ActiveToken("github.com") require.Equal(t, "test-token-2", token) require.Equal(t, "keyring", source) } func TestSwitchClearsActiveSecureTokenWhenSwitchingToInsecureUser(t *testing.T) { // Given we have an active secure token authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", false) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", true) require.NoError(t, err) // When we switch to an insecure user require.NoError(t, authCfg.SwitchUser("github.com", "test-user-1")) // Then the active secure token is cleared _, err = authCfg.TokenFromKeyring("github.com") require.Error(t, err) } func TestSwitchClearsActiveInsecureTokenWhenSwitchingToSecureUser(t *testing.T) { // Given we have an active insecure token authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token-1", "ssh", true) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token-2", "ssh", false) require.NoError(t, err) // When we switch to a secure user require.NoError(t, authCfg.SwitchUser("github.com", "test-user-1")) // Then the active insecure token is cleared requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}) } func TestUsersForHostNoHost(t *testing.T) { // Given we have a config with no hosts authCfg := newTestAuthConfig(t) // When we get the users for a host that doesn't exist users := authCfg.UsersForHost("github.com") // Then it returns nil require.Nil(t, users) } func TestUsersForHostWithUsers(t *testing.T) { // Given we have a config with a host and users authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token", "ssh", false) require.NoError(t, err) _, err = authCfg.Login("github.com", "test-user-2", "test-token", "ssh", false) require.NoError(t, err) // When we get the users for that host users := authCfg.UsersForHost("github.com") // Then it succeeds and returns the users require.Equal(t, []string{"test-user-1", "test-user-2"}, users) } func TestTokenForUserSecureLogin(t *testing.T) { // Given a user has logged in securely authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token", "ssh", true) require.NoError(t, err) // When we get the token token, source, err := authCfg.TokenForUser("github.com", "test-user-1") // Then it returns the token and the source as keyring require.NoError(t, err) require.Equal(t, "test-token", token) require.Equal(t, "keyring", source) } func TestTokenForUserInsecureLogin(t *testing.T) { // Given a user has logged in insecurely authCfg := newTestAuthConfig(t) _, err := authCfg.Login("github.com", "test-user-1", "test-token", "ssh", false) require.NoError(t, err) // When we get the token token, source, err := authCfg.TokenForUser("github.com", "test-user-1") // Then it returns the token and the source as oauth_token require.NoError(t, err) require.Equal(t, "test-token", token) require.Equal(t, "oauth_token", source) } func TestTokenForUserNotFoundErrors(t *testing.T) { // Given a user has not logged in authCfg := newTestAuthConfig(t) // When we get the token _, _, err := authCfg.TokenForUser("github.com", "test-user-1") // Then it returns an error require.EqualError(t, err, "no token found for 'test-user-1'") } func requireKeyWithValue(t *testing.T, cfg *ghConfig.Config, keys []string, value string) { t.Helper() actual, err := cfg.Get(keys) require.NoError(t, err) require.Equal(t, value, actual) } func requireNoKey(t *testing.T, cfg *ghConfig.Config, keys []string) { t.Helper() _, err := cfg.Get(keys) var keyNotFoundError *ghConfig.KeyNotFoundError require.ErrorAs(t, err, &keyNotFoundError) } // Post migration tests func TestUserWorksRightAfterMigration(t *testing.T) { // Given we have logged in before migration authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we migrate var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) // Then we can still get the user correctly user, err := authCfg.ActiveUser("github.com") require.NoError(t, err) require.Equal(t, "test-user", user) } func TestGitProtocolWorksRightAfterMigration(t *testing.T) { // Given we have logged in before migration with a non-default git protocol authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we migrate var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) // Then we can still get the git protocol correctly gitProtocol, err := authCfg.cfg.Get([]string{hostsKey, "github.com", gitProtocolKey}) require.NoError(t, err) require.Equal(t, "ssh", gitProtocol) } func TestHostsWorksRightAfterMigration(t *testing.T) { // Given we have logged in before migration authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "ghe.io", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we migrate var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) // Then we can still get the hosts correctly hosts := authCfg.Hosts() require.Contains(t, hosts, "ghe.io") } func TestDefaultHostWorksRightAfterMigration(t *testing.T) { // Given we have logged in before migration to an enterprise host authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "ghe.io", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we migrate var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) // Then the default host is still the enterprise host defaultHost, source := authCfg.DefaultHost() require.Equal(t, "ghe.io", defaultHost) require.Equal(t, hostsKey, source) } func TestTokenWorksRightAfterMigration(t *testing.T) { // Given we have logged in before migration authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we migrate var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) // Then we can still get the token correctly token, source := authCfg.ActiveToken("github.com") require.Equal(t, "test-token", token) require.Equal(t, oauthTokenKey, source) } func TestLogoutRightAfterMigrationRemovesHost(t *testing.T) { // Given we have logged in before migration authCfg := newTestAuthConfig(t) host := "github.com" user := "test-user" token := "test-token" _, err := preMigrationLogin(authCfg, host, user, token, "ssh", false) require.NoError(t, err) // When we migrate and logout var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) require.NoError(t, authCfg.Logout(host, user)) // Then the host is removed from the config requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com"}) } func TestLoginInsecurePostMigrationUsesConfigForToken(t *testing.T) { // Given we have not logged in authCfg := newTestAuthConfig(t) // When we migrate and login with insecure storage var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) insecureStorageUsed, err := authCfg.Login("github.com", "test-user", "test-token", "", false) // Then it returns success, notes that insecure storage was used, and stores the token in the config // both under the host and under the user require.NoError(t, err) require.True(t, insecureStorageUsed, "expected to use insecure storage") requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}, "test-token") requireKeyWithValue(t, authCfg.cfg, []string{hostsKey, "github.com", usersKey, "test-user", oauthTokenKey}, "test-token") } func TestLoginPostMigrationSetsGitProtocol(t *testing.T) { // Given we have logged in after migration authCfg := newTestAuthConfig(t) var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) _, err := authCfg.Login("github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we get the host git protocol hostProtocol, err := authCfg.cfg.Get([]string{hostsKey, "github.com", gitProtocolKey}) require.NoError(t, err) // Then it returns the git protocol we provided on login require.Equal(t, "ssh", hostProtocol) } func TestLoginPostMigrationSetsUser(t *testing.T) { // Given we have logged in after migration authCfg := newTestAuthConfig(t) var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) _, err := authCfg.Login("github.com", "test-user", "test-token", "ssh", false) require.NoError(t, err) // When we get the user user, err := authCfg.ActiveUser("github.com") // Then it returns success and the user we provided on login require.NoError(t, err) require.Equal(t, "test-user", user) } func TestLoginSecurePostMigrationRemovesTokenFromConfig(t *testing.T) { // Given we have logged in insecurely authCfg := newTestAuthConfig(t) _, err := preMigrationLogin(authCfg, "github.com", "test-user", "test-token", "", false) require.NoError(t, err) // When we migrate and login again with secure storage var m migration.MultiAccount c := cfg{authCfg.cfg} require.NoError(t, c.Migrate(m)) _, err = authCfg.Login("github.com", "test-user", "test-token", "", true) // Then it returns success, having removed the old insecure oauth token entry require.NoError(t, err) requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com", oauthTokenKey}) requireNoKey(t, authCfg.cfg, []string{hostsKey, "github.com", usersKey, "test-user", oauthTokenKey}) } // Copied and pasted directly from the trunk branch before doing any work on // login, plus the addition of AuthConfig as the first arg since it is a method // receiver in the real implementation. func preMigrationLogin(c *AuthConfig, hostname, username, token, gitProtocol string, secureStorage bool) (bool, error) { var setErr error if secureStorage { if setErr = keyring.Set(keyringServiceName(hostname), "", token); setErr == nil { // Clean up the previous oauth_token from the config file. _ = c.cfg.Remove([]string{hostsKey, hostname, oauthTokenKey}) } } insecureStorageUsed := false if !secureStorage || setErr != nil { c.cfg.Set([]string{hostsKey, hostname, oauthTokenKey}, token) insecureStorageUsed = true } c.cfg.Set([]string{hostsKey, hostname, userKey}, username) if gitProtocol != "" { c.cfg.Set([]string{hostsKey, hostname, gitProtocolKey}, gitProtocol) } return insecureStorageUsed, ghConfig.Write(c.cfg) }