diff --git a/registry/remote/auth/client.go b/registry/remote/auth/client.go index fb803b3f5..19fef8074 100644 --- a/registry/remote/auth/client.go +++ b/registry/remote/auth/client.go @@ -121,13 +121,25 @@ type Client struct { // Reference: https://distribution.github.io/distribution/spec/auth/oauth/#getting-a-token ClientID string - // ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant - // instead the distribution spec when authenticating using username and - // password. + // legacyMode controls whether to use the legacy distribution spec + // instead of OAuth2 with password grant when authenticating using + // username and password. + // Default is false (OAuth2 is used). + // + // When legacyMode is true, the client uses the legacy distribution spec for + // authentication. If the registry says it supports bearer authentication, + // basic authentication will be performed unless there are no credentials + // or a refresh token is provided. This is the approach used in oras-go + // v1 and v2. + // + // When legacyMode is false (default), the client uses OAuth2 with password + // grant. If the registry says it supports bearer authentication, bearer + // authentication will be performed. + // // References: // - https://distribution.github.io/distribution/spec/auth/jwt/ // - https://distribution.github.io/distribution/spec/auth/oauth/ - ForceAttemptOAuth2 bool + legacyMode bool } // client returns an HTTP client used to access the remote registry. @@ -172,6 +184,27 @@ func (c *Client) SetUserAgent(userAgent string) { c.Header.Set(headerUserAgent, userAgent) } +// SetLegacyMode sets whether to use legacy distribution spec authentication +// instead of OAuth2 with password grant when authenticating using username +// and password. +// +// When legacy is true, the client uses the legacy distribution spec for +// authentication. If the registry says it supports bearer authentication, +// basic authentication will be performed unless there are no credentials +// or a refresh token is provided. This is the approach used in oras-go +// v1 and v2. +// +// When legacy is false (default), the client uses OAuth2 with password +// grant. If the registry says it supports bearer authentication, bearer +// authentication will be performed. +// +// References: +// - https://distribution.github.io/distribution/spec/auth/jwt/ +// - https://distribution.github.io/distribution/spec/auth/oauth/ +func (c *Client) SetLegacyMode(legacy bool) { + c.legacyMode = legacy +} + // Do sends the request to the remote server, attempting to resolve // authentication if 'Authorization' header is not set. // @@ -310,7 +343,7 @@ func (c *Client) fetchBearerToken(ctx context.Context, registry, realm, service if cred.AccessToken != "" { return cred.AccessToken, nil } - if cred == EmptyCredential || (cred.RefreshToken == "" && !c.ForceAttemptOAuth2) { + if cred == EmptyCredential || (cred.RefreshToken == "" && c.legacyMode) { return c.fetchDistributionToken(ctx, realm, service, scopes, cred.Username, cred.Password) } return c.fetchOAuth2Token(ctx, realm, service, scopes, cred) diff --git a/registry/remote/auth/client_test.go b/registry/remote/auth/client_test.go index 6a9c1fe77..6a2d8bf0a 100644 --- a/registry/remote/auth/client_test.go +++ b/registry/remote/auth/client_test.go @@ -725,6 +725,7 @@ func TestClient_Do_Bearer_Auth(t *testing.T) { }, nil }, } + client.SetLegacyMode(true) // first request req, err := http.NewRequest(http.MethodGet, ts.URL, nil) @@ -852,6 +853,7 @@ func TestClient_Do_Bearer_Auth_Cached(t *testing.T) { }, Cache: NewCache(), } + client.SetLegacyMode(true) // first request ctx := WithScopes(context.Background(), scopes...) @@ -994,6 +996,7 @@ func TestClient_Do_Bearer_Auth_Cached_PerHost(t *testing.T) { }), Cache: NewCache(), } + client1.SetLegacyMode(true) // set up server 2 username2 := "test_user2" @@ -1064,6 +1067,7 @@ func TestClient_Do_Bearer_Auth_Cached_PerHost(t *testing.T) { }), Cache: NewCache(), } + client2.SetLegacyMode(true) ctx := context.Background() ctx = WithScopesForHost(ctx, uri1.Host, scopes1...) @@ -1311,7 +1315,6 @@ func TestClient_Do_Bearer_OAuth2_Password(t *testing.T) { Password: password, }, nil }, - ForceAttemptOAuth2: true, } // first request @@ -1458,8 +1461,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached(t *testing.T) { Password: password, }, nil }, - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } // first request @@ -1621,8 +1623,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached_PerHost(t *testing.T) { Username: username1, Password: password1, }), - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } // set up server 2 username2 := "test_user2" @@ -1711,8 +1712,7 @@ func TestClient_Do_Bearer_OAuth2_Password_Cached_PerHost(t *testing.T) { Username: username2, Password: password2, }), - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } ctx := context.Background() @@ -2969,8 +2969,7 @@ func TestClient_Do_Scope_Hint_Mismatch(t *testing.T) { Password: password, }, nil }, - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } // first request @@ -3113,8 +3112,7 @@ func TestClient_Do_Scope_Hint_Mismatch_PerHost(t *testing.T) { Username: username1, Password: password1, }), - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } // set up server 1 @@ -3207,8 +3205,7 @@ func TestClient_Do_Scope_Hint_Mismatch_PerHost(t *testing.T) { Username: username2, Password: password2, }), - ForceAttemptOAuth2: true, - Cache: NewCache(), + Cache: NewCache(), } ctx := context.Background() @@ -3349,6 +3346,7 @@ func TestClient_Do_Invalid_Credential_Basic(t *testing.T) { }, nil }, } + client.SetLegacyMode(true) // request should fail req, err := http.NewRequest(http.MethodGet, ts.URL, nil) @@ -3434,6 +3432,7 @@ func TestClient_Do_Invalid_Credential_Bearer(t *testing.T) { }, nil }, } + client.SetLegacyMode(true) // request should fail req, err := http.NewRequest(http.MethodGet, ts.URL, nil) @@ -3619,6 +3618,7 @@ func TestClient_Do_Scheme_Change(t *testing.T) { }, Cache: NewCache(), } + client.SetLegacyMode(true) // request with bearer auth req, err := http.NewRequest(http.MethodGet, ts.URL, nil) diff --git a/registry/remote/auth/example_test.go b/registry/remote/auth/example_test.go index 918681d5e..d0dbcbf0d 100644 --- a/registry/remote/auth/example_test.go +++ b/registry/remote/auth/example_test.go @@ -190,8 +190,6 @@ func ExampleClient_Do_clientConfigurations() { Username: username, Password: password, }), - // ForceAttemptOAuth2 controls whether to follow OAuth2 with password grant. - ForceAttemptOAuth2: true, // Cache caches credentials for accessing the remote registry. Cache: auth.NewCache(), }