From 34ee692d12cf8b05180e61b3c937ce9d009036e5 Mon Sep 17 00:00:00 2001 From: Laura Brehm Date: Tue, 23 Jul 2024 17:27:01 +0100 Subject: [PATCH] oauth store: use different keys for oauth credentials Signed-off-by: Laura Brehm --- cli/config/credentials/oauth_store.go | 151 +++-- cli/config/credentials/oauth_store_test.go | 610 ++++++++++++++------- 2 files changed, 505 insertions(+), 256 deletions(-) diff --git a/cli/config/credentials/oauth_store.go b/cli/config/credentials/oauth_store.go index 76afc80ce330..a81c0bb88836 100644 --- a/cli/config/credentials/oauth_store.go +++ b/cli/config/credentials/oauth_store.go @@ -41,23 +41,11 @@ func (o *oauthStore) Get(serverAddress string) (types.AuthConfig, error) { return o.backingStore.Get(serverAddress) } - auth, err := o.backingStore.Get(serverAddress) - if err != nil { - // If an error happens here, it's not due to the backing store not - // containing credentials, but rather an actual issue with the backing - // store itself. This should be propagated up. - return types.AuthConfig{}, err - } - - tokenRes, err := o.parseToken(auth.Password) - // if the password is not a token, return the auth config as is - if err != nil { - //nolint:nilerr - return auth, nil + tokenRes := o.fetchFromBackingStore() + if tokenRes == nil { + return o.backingStore.Get(serverAddress) } - println(tokenRes.Claims.Expiry.Time().Local().String()) - // if the access token is valid for less than minimumTokenLifetime, refresh it if tokenRes.RefreshToken != "" && tokenRes.Claims.Expiry.Time().Before(time.Now().Add(minimumTokenLifetime)) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -66,10 +54,10 @@ func (o *oauthStore) Get(serverAddress string) (types.AuthConfig, error) { if err != nil || refreshRes == nil { return types.AuthConfig{}, fmt.Errorf("failed to refresh token: %w", err) } - tokenRes = *refreshRes + tokenRes = refreshRes } - err = o.storeInBackingStore(tokenRes) + err := o.storeInBackingStore(*tokenRes) if err != nil { return types.AuthConfig{}, err } @@ -77,7 +65,6 @@ func (o *oauthStore) Get(serverAddress string) (types.AuthConfig, error) { return types.AuthConfig{ Username: tokenRes.Claims.Domain.Username, Password: tokenRes.AccessToken, - Email: tokenRes.Claims.Domain.Email, ServerAddress: defaultRegistry, }, nil } @@ -86,67 +73,127 @@ func (o *oauthStore) Get(serverAddress string) (types.AuthConfig, error) { // store contains credentials for the official registry, these are refreshed/processed // according to the same rules as Get. func (o *oauthStore) GetAll() (map[string]types.AuthConfig, error) { + // fetch all authconfigs from backing store allAuths, err := o.backingStore.GetAll() if err != nil { return nil, err } - if _, ok := allAuths[defaultRegistry]; !ok { + // if there are no oauth-type credentials for the default registry, + // we can return as-is + tokenRes := o.fetchFromBackingStore() + if tokenRes == nil { return allAuths, nil } + // if there is an oauth-type entry, then we need to parse it/refresh it auth, err := o.Get(defaultRegistry) if err != nil { return nil, err } allAuths[defaultRegistry] = auth + + // delete access/refresh-token specific entries since the caller + // doesn't care about those + delete(allAuths, accessTokenServerAddress) + delete(allAuths, refreshTokenServerAddress) + return allAuths, err } -// Erase removes the credentials from the backing store, logging out of the -// tenant if running +// Erase removes the credentials from the backing store. +// If the address pertains to the default registry, and there are oauth-type +// credentials stored, it also revokes the refresh token with the tenant. func (o *oauthStore) Erase(serverAddress string) error { - if serverAddress == defaultRegistry { - auth, err := o.backingStore.Get(defaultRegistry) - if err != nil { - return err - } - if tokenRes, err := o.parseToken(auth.Password); err == nil { - // todo(laurazard): should use a context with a timeout here? - _ = o.manager.Logout(context.TODO(), tokenRes.RefreshToken) - } + if serverAddress != defaultRegistry { + return o.backingStore.Erase(serverAddress) + } + + refreshTokenAuth, err := o.backingStore.Get(refreshTokenServerAddress) + if err == nil && refreshTokenAuth.Password != "" { + _ = o.manager.Logout(context.TODO(), refreshTokenAuth.Password) } - return o.backingStore.Erase(serverAddress) + + _ = o.backingStore.Erase(defaultRegistry) + _ = o.backingStore.Erase(accessTokenServerAddress) + _ = o.backingStore.Erase(refreshTokenServerAddress) + return nil } -// Store stores the provided credentials in the backing store, without any -// additional processing. +// Store stores the provided credentials in the backing store. +// If the provided credentials represent oauth-type credentials for the default +// registry, then those are stored as separate entries in the backing store. +// If there are basic auths and we're storing an oauth login, the basic auth +// entry is removed from the backing store, and vice versa. func (o *oauthStore) Store(auth types.AuthConfig) error { - return o.backingStore.Store(auth) + if auth.ServerAddress != defaultRegistry { + return o.backingStore.Store(auth) + } + + accessToken, refreshToken, err := oauth.SplitTokens(auth.Password) + if err != nil { + // not storing an oauth-type login, so just store the auth as-is + return errors.Join( + // first, remove oauth logins if we had any + o.backingStore.Erase(accessTokenServerAddress), + o.backingStore.Erase(refreshTokenServerAddress), + o.backingStore.Store(auth), + ) + } + + // erase basic auths before storing our oauth-type login + _ = o.backingStore.Erase(defaultRegistry) + return errors.Join( + o.backingStore.Store(types.AuthConfig{ + Username: auth.Username, + Password: accessToken, + ServerAddress: accessTokenServerAddress, + }), + o.backingStore.Store(types.AuthConfig{ + Username: auth.Username, + Password: refreshToken, + ServerAddress: refreshTokenServerAddress, + }), + ) +} + +const ( + defaultRegistryHostname = "index.docker.io/v1" + accessTokenServerAddress = "https://access-token." + defaultRegistryHostname + refreshTokenServerAddress = "https://refresh-token." + defaultRegistryHostname +) + +func (o *oauthStore) storeInBackingStore(tokenRes oauth.TokenResult) error { + return errors.Join( + o.backingStore.Store(types.AuthConfig{ + Username: tokenRes.Claims.Domain.Username, + Password: tokenRes.AccessToken, + ServerAddress: accessTokenServerAddress, + }), + o.backingStore.Store(types.AuthConfig{ + Username: tokenRes.Claims.Domain.Username, + Password: tokenRes.RefreshToken, + ServerAddress: refreshTokenServerAddress, + }), + ) } -func (o *oauthStore) parseToken(password string) (oauth.TokenResult, error) { - accessToken, refreshToken, err := oauth.SplitTokens(password) +func (o *oauthStore) fetchFromBackingStore() *oauth.TokenResult { + accessTokenAuth, err := o.backingStore.Get(accessTokenServerAddress) if err != nil { - return oauth.TokenResult{}, errors.New("failed to parse token") + return nil } - claims, err := oauth.GetClaims(accessToken) + refreshTokenAuth, err := o.backingStore.Get(refreshTokenServerAddress) if err != nil { - return oauth.TokenResult{}, err + return nil } - return oauth.TokenResult{ - AccessToken: accessToken, - RefreshToken: refreshToken, + claims, err := oauth.GetClaims(accessTokenAuth.Password) + if err != nil { + return nil + } + return &oauth.TokenResult{ + AccessToken: accessTokenAuth.Password, + RefreshToken: refreshTokenAuth.Password, Claims: claims, - }, nil -} - -func (o *oauthStore) storeInBackingStore(tokenRes oauth.TokenResult) error { - auth := types.AuthConfig{ - Username: tokenRes.Claims.Domain.Username, - Password: oauth.ConcatTokens(tokenRes.AccessToken, tokenRes.RefreshToken), - Email: tokenRes.Claims.Domain.Email, - ServerAddress: defaultRegistry, } - return o.backingStore.Store(auth) } diff --git a/cli/config/credentials/oauth_store_test.go b/cli/config/credentials/oauth_store_test.go index e1e02cc5e6ff..2d0fef1cc33b 100644 --- a/cli/config/credentials/oauth_store_test.go +++ b/cli/config/credentials/oauth_store_test.go @@ -23,173 +23,186 @@ const ( func TestOAuthStoreGet(t *testing.T) { t.Run("official registry", func(t *testing.T) { - t.Run("valid credentials - no refresh", func(t *testing.T) { - auths := map[string]types.AuthConfig{ - defaultRegistry: { + t.Run("oauth creds", func(t *testing.T) { + t.Run("valid credentials - no refresh", func(t *testing.T) { + auths := map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + } + s := &oauthStore{ + backingStore: NewFileStore(newStore(auths)), + } + + auth, err := s.Get(defaultRegistry) + assert.NilError(t, err) + + assert.DeepEqual(t, auth, types.AuthConfig{ Username: "bork!", - Email: "bork@docker.com", - Password: validNotExpiredToken + "..refresh-token", + Password: validNotExpiredToken, ServerAddress: defaultRegistry, - }, - } - s := &oauthStore{ - backingStore: NewFileStore(newStore(auths)), - } - - auth, err := s.Get(defaultRegistry) - assert.NilError(t, err) - - assert.DeepEqual(t, auth, types.AuthConfig{ - Username: "bork!", - Password: validNotExpiredToken, - Email: "bork@docker.com", - ServerAddress: defaultRegistry, + }) }) - }) - t.Run("no credentials - return", func(t *testing.T) { - auths := map[string]types.AuthConfig{} - f := newStore(auths) - s := &oauthStore{ - backingStore: NewFileStore(f), - } - - auth, err := s.Get(defaultRegistry) - assert.NilError(t, err) - - assert.DeepEqual(t, auth, types.AuthConfig{}) - assert.Equal(t, len(auths), 0) - }) - - t.Run("expired credentials - refresh", func(t *testing.T) { - f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { - Username: "bork!", - Email: "bork@docker.com", - Password: validExpiredToken + "..refresh-token", - ServerAddress: defaultRegistry, - }, - }) - var receivedRefreshToken string - manager := &testManager{ - refresh: func(token string) (*oauth.TokenResult, error) { - receivedRefreshToken = token - return &oauth.TokenResult{ - AccessToken: "abcd1234", - RefreshToken: "efgh5678", - Claims: oauth.Claims{ - Claims: jwt.Claims{ - Expiry: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), + t.Run("expired credentials - refresh", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + }) + var receivedRefreshToken string + manager := &testManager{ + refresh: func(token string) (*oauth.TokenResult, error) { + receivedRefreshToken = token + return &oauth.TokenResult{ + AccessToken: "abcd1234", + RefreshToken: "efgh5678", + Claims: oauth.Claims{ + Claims: jwt.Claims{ + Expiry: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)), + }, + Domain: oauth.DomainClaims{Username: "bork!", Email: "bork@docker.com"}, }, - Domain: oauth.DomainClaims{Username: "bork!", Email: "bork@docker.com"}, - }, - }, nil - }, - } - s := &oauthStore{ - backingStore: NewFileStore(f), - manager: manager, - } - - auth, err := s.Get(defaultRegistry) - assert.NilError(t, err) - - assert.DeepEqual(t, auth, types.AuthConfig{ - Username: "bork!", - Password: "abcd1234", - Email: "bork@docker.com", - ServerAddress: defaultRegistry, - }) - assert.Equal(t, receivedRefreshToken, "refresh-token") - assert.DeepEqual(t, f.GetAuthConfigs()[defaultRegistry], types.AuthConfig{ - Username: "bork!", - Password: "abcd1234..efgh5678", - Email: "bork@docker.com", - ServerAddress: defaultRegistry, - }) - }) - - t.Run("expired credentials - refresh fails - return error", func(t *testing.T) { - f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { + }, nil + }, + } + s := &oauthStore{ + backingStore: NewFileStore(f), + manager: manager, + } + + auth, err := s.Get(defaultRegistry) + assert.NilError(t, err) + + assert.DeepEqual(t, auth, types.AuthConfig{ Username: "bork!", - Email: "bork@docker.com", - Password: validExpiredToken + "..refresh-token", + Password: "abcd1234", ServerAddress: defaultRegistry, - }, + }) + assert.Equal(t, receivedRefreshToken, "refresh-token") + assert.DeepEqual(t, f.GetAuthConfigs()[accessTokenServerAddress], types.AuthConfig{ + Username: "bork!", + Password: "abcd1234", + ServerAddress: accessTokenServerAddress, + }) + assert.DeepEqual(t, f.GetAuthConfigs()[refreshTokenServerAddress], types.AuthConfig{ + Username: "bork!", + Password: "efgh5678", + ServerAddress: refreshTokenServerAddress, + }) }) - var refreshCalled bool - manager := &testManager{ - refresh: func(_ string) (*oauth.TokenResult, error) { - refreshCalled = true - return &oauth.TokenResult{}, errors.New("refresh failed") - }, - } - s := &oauthStore{ - backingStore: NewFileStore(f), - manager: manager, - } - - _, err := s.Get(defaultRegistry) - assert.ErrorContains(t, err, "refresh failed") - assert.Check(t, refreshCalled) + t.Run("expired credentials - refresh fails - return error", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + }) + var refreshCalled bool + manager := &testManager{ + refresh: func(_ string) (*oauth.TokenResult, error) { + refreshCalled = true + return &oauth.TokenResult{}, errors.New("refresh failed") + }, + } + s := &oauthStore{ + backingStore: NewFileStore(f), + manager: manager, + } + + _, err := s.Get(defaultRegistry) + assert.ErrorContains(t, err, "refresh failed") + + assert.Check(t, refreshCalled) + }) }) - t.Run("old non-access token credentials", func(t *testing.T) { - f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { + t.Run("non-oauth creds", func(t *testing.T) { + t.Run("old non-access token credentials", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + defaultRegistry: { + Username: "bork!", + Password: "a-password", + ServerAddress: defaultRegistry, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + + auth, err := s.Get(defaultRegistry) + assert.NilError(t, err) + + assert.DeepEqual(t, auth, types.AuthConfig{ Username: "bork!", - Email: "bork@docker.com", Password: "a-password", ServerAddress: defaultRegistry, - }, - }) - s := &oauthStore{ - backingStore: NewFileStore(f), - } - - auth, err := s.Get(defaultRegistry) - assert.NilError(t, err) - - assert.DeepEqual(t, auth, types.AuthConfig{ - Username: "bork!", - Email: "bork@docker.com", - Password: "a-password", - ServerAddress: defaultRegistry, + }) }) - }) - t.Run("old non-access token credentials w/ ..", func(t *testing.T) { - f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { + t.Run("old non-access token credentials w/ ..", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + defaultRegistry: { + Username: "bork!", + Password: "a-password..with-dots", + ServerAddress: defaultRegistry, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + + auth, err := s.Get(defaultRegistry) + assert.NilError(t, err) + + assert.DeepEqual(t, auth, types.AuthConfig{ Username: "bork!", - Email: "bork@docker.com", Password: "a-password..with-dots", ServerAddress: defaultRegistry, - }, + }) }) - s := &oauthStore{ - backingStore: NewFileStore(f), - } + }) + }) - auth, err := s.Get(defaultRegistry) - assert.NilError(t, err) + t.Run("no credentials - return", func(t *testing.T) { + auths := map[string]types.AuthConfig{} + f := newStore(auths) + s := &oauthStore{ + backingStore: NewFileStore(f), + } - assert.DeepEqual(t, auth, types.AuthConfig{ - Username: "bork!", - Email: "bork@docker.com", - Password: "a-password..with-dots", - ServerAddress: defaultRegistry, - }) - }) + auth, err := s.Get(defaultRegistry) + assert.NilError(t, err) + + assert.DeepEqual(t, auth, types.AuthConfig{}) + assert.Equal(t, len(auths), 0) }) t.Run("defers when different registry", func(t *testing.T) { auth := types.AuthConfig{ Username: "foo", Password: "bar", - Email: "foo@example.com", ServerAddress: validServerAddress, } f := newStore(map[string]types.AuthConfig{ @@ -217,12 +230,40 @@ func TestGetAll(t *testing.T) { assert.Check(t, is.Len(as, 0)) }) - t.Run("1 - official registry", func(t *testing.T) { + + t.Run("official registry - oauth creds", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + + as, err := s.GetAll() + assert.NilError(t, err) + + assert.Equal(t, len(as), 1) + assert.DeepEqual(t, as[defaultRegistry], types.AuthConfig{ + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: defaultRegistry, + }) + }) + + t.Run("official registry - regular creds", func(t *testing.T) { f := newStore(map[string]types.AuthConfig{ defaultRegistry: { Username: "bork!", - Password: validNotExpiredToken + "..refresh-token", - Email: "bork@docker.com", + Password: "meow", ServerAddress: defaultRegistry, }, }) @@ -234,14 +275,18 @@ func TestGetAll(t *testing.T) { assert.NilError(t, err) assert.Equal(t, len(as), 1) + assert.DeepEqual(t, as[defaultRegistry], types.AuthConfig{ + Username: "bork!", + Password: "meow", + ServerAddress: defaultRegistry, + }) }) t.Run("1 - other registry", func(t *testing.T) { f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { + validServerAddress2: { Username: "bork!", Password: "password", - Email: "bork@docker.com", ServerAddress: validServerAddress2, }, }) @@ -253,20 +298,28 @@ func TestGetAll(t *testing.T) { assert.NilError(t, err) assert.Equal(t, len(as), 1) + assert.DeepEqual(t, as[validServerAddress2], types.AuthConfig{ + Username: "bork!", + Password: "password", + ServerAddress: validServerAddress2, + }) }) t.Run("multiple - official and other registry", func(t *testing.T) { f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { + accessTokenServerAddress: { Username: "bork!", - Password: validNotExpiredToken + "..refresh-token", - Email: "bork@docker.com", - ServerAddress: defaultRegistry, + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "..refresh-token", + ServerAddress: refreshTokenServerAddress, }, validServerAddress2: { Username: "foo", Password: "bar", - Email: "bork@dockr.com", ServerAddress: validServerAddress2, }, }) @@ -278,39 +331,118 @@ func TestGetAll(t *testing.T) { assert.NilError(t, err) assert.Equal(t, len(as), 2) + assert.DeepEqual(t, as[defaultRegistry], types.AuthConfig{ + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: defaultRegistry, + }) + assert.DeepEqual(t, as[validServerAddress2], types.AuthConfig{ + Username: "foo", + Password: "bar", + ServerAddress: validServerAddress2, + }) }) } func TestErase(t *testing.T) { t.Run("official registry", func(t *testing.T) { - f := newStore(map[string]types.AuthConfig{ - defaultRegistry: { - Email: "foo@example.com", - Password: validNotExpiredToken + "..refresh-token", - }, + t.Run("oauth creds", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + }) + var revokedToken string + manager := &testManager{ + logout: func(token string) error { + revokedToken = token + return nil + }, + } + s := &oauthStore{ + backingStore: NewFileStore(f), + manager: manager, + } + err := s.Erase(defaultRegistry) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 0)) + assert.Equal(t, revokedToken, "refresh-token") }) - var revokedToken string - manager := &testManager{ - logout: func(token string) error { - revokedToken = token - return nil - }, - } - s := &oauthStore{ - backingStore: NewFileStore(f), - manager: manager, - } - err := s.Erase(defaultRegistry) - assert.NilError(t, err) - assert.Check(t, is.Len(f.GetAuthConfigs(), 0)) - assert.Equal(t, revokedToken, "refresh-token") + t.Run("partial oauth creds", func(t *testing.T) { + t.Run("missing refresh token", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "bork!", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + err := s.Erase(defaultRegistry) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 0)) + }) + t.Run("missing access token", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + refreshTokenServerAddress: { + Username: "bork!", + Password: "refresh-token", + ServerAddress: accessTokenServerAddress, + }, + }) + var revokedToken string + m := testManager{ + logout: func(token string) error { + revokedToken = token + return nil + }, + } + s := &oauthStore{ + backingStore: NewFileStore(f), + manager: &m, + } + err := s.Erase(defaultRegistry) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 0)) + assert.Equal(t, revokedToken, "refresh-token") + }) + }) + + t.Run("non-oauth creds", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + defaultRegistry: { + Username: "bork!", + Password: "meow", + ServerAddress: defaultRegistry, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + err := s.Erase(defaultRegistry) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 0)) + }) }) t.Run("different registry", func(t *testing.T) { f := newStore(map[string]types.AuthConfig{ validServerAddress2: { - Email: "foo@example.com", + Username: "bork!", }, }) s := &oauthStore{ @@ -324,43 +456,114 @@ func TestErase(t *testing.T) { func TestStore(t *testing.T) { t.Run("official registry", func(t *testing.T) { - t.Run("regular credentials", func(t *testing.T) { - f := newStore(make(map[string]types.AuthConfig)) - s := &oauthStore{ - backingStore: NewFileStore(f), - } - auth := types.AuthConfig{ - Username: "foo", - Password: "bar", - Email: "foo@example.com", - ServerAddress: defaultRegistry, - } - err := s.Store(auth) - assert.NilError(t, err) + t.Run("no prior credentials", func(t *testing.T) { + t.Run("regular credentials", func(t *testing.T) { + f := newStore(make(map[string]types.AuthConfig)) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + auth := types.AuthConfig{ + Username: "foo", + Password: "bar", + ServerAddress: defaultRegistry, + } + err := s.Store(auth) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 1)) + assert.Check(t, is.DeepEqual(f.GetAuthConfigs()[defaultRegistry], auth)) + }) - assert.Check(t, is.Len(f.GetAuthConfigs(), 1)) + t.Run("access token", func(t *testing.T) { + f := newStore(make(map[string]types.AuthConfig)) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + auth := types.AuthConfig{ + Username: "foo", + Password: validNotExpiredToken + "..refresh-token", + ServerAddress: defaultRegistry, + } + err := s.Store(auth) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 2)) + assert.DeepEqual(t, f.GetAuthConfigs()[accessTokenServerAddress], types.AuthConfig{ + Username: "foo", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }) + assert.DeepEqual(t, f.GetAuthConfigs()[refreshTokenServerAddress], types.AuthConfig{ + Username: "foo", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }) + }) }) - t.Run("access token", func(t *testing.T) { - f := newStore(make(map[string]types.AuthConfig)) - s := &oauthStore{ - backingStore: NewFileStore(f), - } - auth := types.AuthConfig{ - Username: "foo", - Password: validNotExpiredToken + "..refresh-token", - Email: "foo@example.com", - ServerAddress: defaultRegistry, - } - err := s.Store(auth) - assert.NilError(t, err) + t.Run("prior credentials", func(t *testing.T) { + t.Run("basic auth -> oauth", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + defaultRegistry: { + Username: "foo", + Password: "bar", + ServerAddress: defaultRegistry, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + auth := types.AuthConfig{ + Username: "foo", + Password: validNotExpiredToken + "..refresh-token", + ServerAddress: defaultRegistry, + } + err := s.Store(auth) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 2)) + assert.DeepEqual(t, f.GetAuthConfigs()[accessTokenServerAddress], types.AuthConfig{ + Username: "foo", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }) + assert.DeepEqual(t, f.GetAuthConfigs()[refreshTokenServerAddress], types.AuthConfig{ + Username: "foo", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }) + }) - assert.Check(t, is.Len(f.GetAuthConfigs(), 1)) - assert.DeepEqual(t, f.GetAuthConfigs()[defaultRegistry], types.AuthConfig{ - Username: "foo", - Password: validNotExpiredToken + "..refresh-token", - Email: "foo@example.com", - ServerAddress: defaultRegistry, + t.Run("oauth -> basic auth", func(t *testing.T) { + f := newStore(map[string]types.AuthConfig{ + accessTokenServerAddress: { + Username: "foo", + Password: validNotExpiredToken, + ServerAddress: accessTokenServerAddress, + }, + refreshTokenServerAddress: { + Username: "foo", + Password: "refresh-token", + ServerAddress: refreshTokenServerAddress, + }, + }) + s := &oauthStore{ + backingStore: NewFileStore(f), + } + auth := types.AuthConfig{ + Username: "foo", + Password: "bar", + ServerAddress: defaultRegistry, + } + err := s.Store(auth) + assert.NilError(t, err) + + assert.Check(t, is.Len(f.GetAuthConfigs(), 1)) + assert.DeepEqual(t, f.GetAuthConfigs()[defaultRegistry], types.AuthConfig{ + Username: "foo", + Password: "bar", + ServerAddress: defaultRegistry, + }) }) }) }) @@ -373,7 +576,6 @@ func TestStore(t *testing.T) { auth := types.AuthConfig{ Username: "foo", Password: "bar", - Email: "foo@example.com", ServerAddress: validServerAddress2, } err := s.Store(auth)