Copilot commented on code in PR #1441:
URL: https://github.com/apache/pulsar-client-go/pull/1441#discussion_r2564301378


##########
pulsaradmin/pkg/admin/auth/oauth2.go:
##########
@@ -130,44 +97,22 @@ func NewAuthenticationOAuth2WithParams(
                Audience:       audience,
        }
 
-       keyringStore, err := MakeKeyringStore()
-       if err != nil {
-               return nil, err
-       }
-
        p := &OAuth2Provider{
                clock:            clock2.RealClock{},
                issuer:           issuer,
-               store:            keyringStore,
                defaultTransport: transport,
        }

Review Comment:
   The `flow` field is nil when `NewAuthenticationOAuth2WithParams` is called, 
but `initCache()` requires a non-nil flow. This will cause a nil pointer 
dereference when the cache tries to use the flow.
   
   This function should initialize the flow similar to how 
`NewAuthenticationOAuth2WithFlow` does, or it should be removed if it's no 
longer used.



##########
oauth2/cache/cache.go:
##########
@@ -77,56 +71,21 @@ func (t *tokenCache) Token() (*xoauth2.Token, error) {
                return t.token, nil
        }
 
-       // load from the store and use the access token if it isn't expired
-       grant, err := t.store.LoadGrant(t.audience)
+       grant, err := t.flow.Authorize(t.audience)
        if err != nil {
-               return nil, fmt.Errorf("LoadGrant: %w", err)
+               return nil, err
        }
        t.token = grant.Token
-       if t.token != nil && t.validateAccessToken(*t.token) {
-               return t.token, nil
-       }
-
-       // obtain and cache a fresh access token
-       grant, err = t.refresher.Refresh(grant)
-       if err != nil {
-               return nil, fmt.Errorf("RefreshGrant: %w", err)
-       }
-       t.token = grant.Token
-       err = t.store.SaveGrant(t.audience, *grant)
-       if err != nil {
-               // TODO log rather than throw
-               return nil, fmt.Errorf("SaveGrant: %w", err)
-       }
 
        return t.token, nil
 }
 
 // InvalidateToken clears the access token (likely due to a response from the 
resource server).

Review Comment:
   The comment is now inaccurate. The old comment mentioned "Note that the 
token within the grant may contain a refresh token which should survive," but 
the new implementation simply clears the entire token without persisting 
anything. 
   
   Consider updating it to:
   ```go
   // InvalidateToken clears the cached access token (likely due to a response 
from the resource server).
   // The next call to Token() will obtain a fresh token by calling the flow's 
Authorize method.
   ```
   ```suggestion
   // InvalidateToken clears the cached access token (likely due to a response 
from the resource server).
   // The next call to Token() will obtain a fresh token by calling the flow's 
Authorize method.
   ```



##########
pulsaradmin/pkg/admin/auth/oauth2_test.go:
##########
@@ -99,37 +98,16 @@ func TestOauth2(t *testing.T) {
                Audience:       server.URL,
        }
 
-       memoryStore := store.NewMemoryStore()
-       err = saveGrant(memoryStore, kf, issuer.Audience)
-       if err != nil {
-               t.Fatal(err)
-       }
-
-       auth, err := NewAuthenticationOAuth2(issuer, memoryStore)
+       auth, err := NewAuthenticationOAuth2WithFlow(issuer, 
oauth2.ClientCredentialsFlowOptions{
+               KeyFile: kf,
+       })
        if err != nil {
                t.Fatal(err)
        }
 
-       token, err := auth.source.Token()
+       token, err := auth.(*OAuth2Provider).source.Token()

Review Comment:
   The type assertion `auth.(*OAuth2Provider)` will panic if the returned type 
doesn't match. While this is a test and the type should be correct, it's better 
practice to use the comma-ok idiom for type assertions to avoid panics:
   
   ```go
   provider, ok := auth.(*OAuth2Provider)
   if !ok {
       t.Fatal("unexpected provider type")
   }
   token, err := provider.source.Token()
   ```
   
   Alternatively, since this is accessing an internal field (`source`), 
consider adding a public method to retrieve the token for testing purposes.
   ```suggestion
        provider, ok := auth.(*OAuth2Provider)
        if !ok {
                t.Fatal("unexpected provider type")
        }
        token, err := provider.source.Token()
   ```



##########
oauth2/cache/cache.go:
##########
@@ -77,56 +71,21 @@ func (t *tokenCache) Token() (*xoauth2.Token, error) {
                return t.token, nil
        }
 
-       // load from the store and use the access token if it isn't expired
-       grant, err := t.store.LoadGrant(t.audience)
+       grant, err := t.flow.Authorize(t.audience)
        if err != nil {
-               return nil, fmt.Errorf("LoadGrant: %w", err)
+               return nil, err
        }
        t.token = grant.Token

Review Comment:
   Potential nil pointer dereference: If `flow.Authorize()` returns a grant 
with a nil `Token` field, line 78 will assign nil to `t.token`, but the 
function will return it without an error. This could cause issues for callers 
expecting a valid token.
   
   Consider adding a check after line 78:
   ```go
   if t.token == nil {
       return nil, errors.New("authorization succeeded but no token was 
returned")
   }
   ```



##########
oauth2/cache/cache.go:
##########
@@ -43,24 +40,21 @@ const (
 )
 
 // tokenCache implements a cache for the token associated with a specific 
audience.
-// it interacts with the store when the access token is near expiration or 
invalidated.
 // it is advisable to use a token cache instance per audience.

Review Comment:
   The documentation comment should be updated to reflect the new behavior. 
Since the old implementation interacted with a store for persistence, and the 
new implementation calls the flow to refresh tokens dynamically (enabling key 
file reloading), the comment should mention this:
   
   ```go
   // tokenCache implements a cache for the token associated with a specific 
audience.
   // When the cached token expires or is invalidated, it obtains a fresh token 
by 
   // calling the flow's Authorize method, which allows for dynamic reloading 
of credentials.
   // It is advisable to use a token cache instance per audience.
   ```
   ```suggestion
   // When the cached token expires or is invalidated, it obtains a fresh token 
by 
   // calling the flow's Authorize method, which allows for dynamic reloading 
of credentials.
   // It is advisable to use a token cache instance per audience.
   ```



##########
oauth2/cache/cache.go:
##########
@@ -43,24 +40,21 @@ const (
 )
 
 // tokenCache implements a cache for the token associated with a specific 
audience.
-// it interacts with the store when the access token is near expiration or 
invalidated.
 // it is advisable to use a token cache instance per audience.
 type tokenCache struct {
-       clock     clock.Clock
-       lock      sync.Mutex
-       store     store.Store
-       audience  string
-       refresher oauth2.AuthorizationGrantRefresher
-       token     *xoauth2.Token
+       clock    clock.Clock
+       lock     sync.Mutex
+       audience string
+       token    *xoauth2.Token
+       flow     *oauth2.ClientCredentialsFlow
 }
 
-func NewDefaultTokenCache(store store.Store, audience string,
-       refresher oauth2.AuthorizationGrantRefresher) (CachingTokenSource, 
error) {
+func NewDefaultTokenCache(audience string,
+       flow *oauth2.ClientCredentialsFlow) (CachingTokenSource, error) {
        cache := &tokenCache{
-               clock:     clock.RealClock{},
-               store:     store,
-               audience:  audience,
-               refresher: refresher,
+               clock:    clock.RealClock{},
+               audience: audience,
+               flow:     flow,
        }
        return cache, nil

Review Comment:
   Missing input validation: The function should validate that required 
parameters are non-nil. If `flow` is nil, it will cause a panic when `Token()` 
is called at line 74.
   
   Consider adding validation:
   ```go
   if flow == nil {
       return nil, errors.New("flow cannot be nil")
   }
   if audience == "" {
       return nil, errors.New("audience cannot be empty")
   }
   ```



##########
pulsar/auth/oauth2.go:
##########
@@ -69,46 +66,26 @@ func NewAuthenticationOAuth2WithParams(params 
map[string]string) (Provider, erro
                if err != nil {
                        return nil, err
                }
-               grant, err := flow.Authorize(issuer.Audience)
-               if err != nil {
-                       return nil, err
-               }
-               err = st.SaveGrant(issuer.Audience, *grant)
-               if err != nil {
-                       return nil, err
-               }
+               return NewAuthenticationOAuth2(issuer, flow), nil
        default:
                return nil, fmt.Errorf("unsupported authentication type: %s", 
params[ConfigParamType])
        }
 
-       return NewAuthenticationOAuth2(issuer, st), nil
 }
 

Review Comment:
   The empty line after the closing brace is unnecessary since the function 
ends immediately after. This is a minor style issue but removing it would 
improve consistency.
   ```suggestion
   
   ```



##########
pulsar/auth/oauth2_test.go:
##########
@@ -142,3 +161,71 @@ func TestNewAuthenticationOAuth2WithParams(t *testing.T) {
                assert.Equal(t, "token-content", string(token))
        }
 }
+
+func TestOAuth2KeyFileReloading(t *testing.T) {
+       server := mockOAuthServer()
+       defer server.Close()
+       expectedClientID.Store("client-id")
+       expectedClientSecret.Store("client-secret")
+       kf, err := mockKeyFile(server.URL)
+       defer os.Remove(kf)
+       require.NoError(t, err)
+
+       params := map[string]string{
+               ConfigParamType:      ConfigParamTypeClientCredentials,
+               ConfigParamIssuerURL: server.URL,
+               ConfigParamClientID:  "client-id",
+               ConfigParamAudience:  "audience",
+               ConfigParamKeyFile:   fmt.Sprintf("file://%s", kf),
+               ConfigParamScope:     "profile",
+       }
+
+       auth, err := NewAuthenticationOAuth2WithParams(params)
+       require.NoError(t, err)
+       err = auth.Init()
+       require.NoError(t, err)
+
+       token, err := auth.GetData()
+       require.NoError(t, err)
+       assert.Equal(t, "token-content", string(token))
+
+       expectedClientSecret.Store("new-client-secret")
+       _, err = auth.GetData()
+       require.Error(t, err) // The token refresh should be failed after 
updating the client-secret
+
+       // now update the key file to have different client credentials
+       keyFile, err := os.OpenFile(kf, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 
0644)
+       require.NoError(t, err)
+       _, err = keyFile.WriteString(fmt.Sprintf(`{
+  "type":"resource",
+  "client_id":"client-id",
+  "client_secret":"new-client-secret",
+  "client_email":"[email protected]",
+  "issuer_url":"%s"
+}`, server.URL))
+       require.NoError(t, err)
+       require.NoError(t, keyFile.Close())
+
+       token, err = auth.GetData()
+       if err != nil {
+               t.Fatal(err)
+       }
+       assert.Equal(t, "token-content", string(token))
+}
+
+func TestGrantProviderScopes(t *testing.T) {

Review Comment:
   The test `TestGrantProviderScopes` creates a mock server without 
initializing `expectedClientID` and `expectedClientSecret` atomic values. While 
this test doesn't appear to make actual token requests, if it did, the handler 
at line 60 would panic with a nil pointer dereference when calling 
`.Load().(string)` on uninitialized atomic.Values.
   
   Consider either:
   1. Initializing the atomic values before calling `mockOAuthServer()`, or
   2. Adding nil checks in the mock token handler to handle uninitialized 
values gracefully.
   ```suggestion
   func TestGrantProviderScopes(t *testing.T) {
        expectedClientID.Store("client-id")
        expectedClientSecret.Store("client-secret")
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to