fix(auth): fix remaining nullability in UserAuthorizer and Builder - #14158
fix(auth): fix remaining nullability in UserAuthorizer and Builder#14158lqiu96 wants to merge 8 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces nullability annotations, adds non-null preconditions to several builder methods in UserAuthorizer, fixes a typo, and prevents a potential NullPointerException when storing credentials with a null expiration time. It also adds corresponding unit tests. The review feedback identifies a regression in setPKCEProvider where the newly added non-null precondition prevents unsetting or disabling PKCE, and suggests restoring the nullable behavior.
…th/oauth2/UserAuthorizer.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
| @@ -836,8 +837,7 @@ public URI getTokenServerUri() { | |||
| * | |||
| * @return The refresh token, or null if not granted. | |||
| */ | |||
| @Nullable | |||
| public String getRefreshToken() { | |||
| public @Nullable String getRefreshToken() { | |||
There was a problem hiding this comment.
Turns out Oauth2Utils#validateOptionalString can return null if it doesn't exist. So these two fields use it and it can be null
| this.clientId = Preconditions.checkNotNull(builder.clientId); | ||
| this.clientSecret = builder.clientSecret; | ||
| this.accessToken = builder.accessToken; | ||
| this.httpTransportFactory = builder.httpTransportFactory; | ||
| this.tokenServerUri = builder.tokenServerUri; | ||
| this.accessToken = Preconditions.checkNotNull(builder.accessToken); | ||
| this.httpTransportFactory = Preconditions.checkNotNull(builder.httpTransportFactory); | ||
| this.tokenServerUri = Preconditions.checkNotNull(builder.tokenServerUri); |
There was a problem hiding this comment.
these are passed in from UserAuthorizer which enforces them to be non-null
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates UserAuthorizer by adding @Nullable annotations, fixing typos, preventing integer overflow in expiration calculations, and introducing new unit tests. The reviewer identified a critical issue: omitting expiration_time_millis when storing credentials with a null access token will cause a NullPointerException or validation failure during loading, as the loading logic now unboxes this value into a primitive long. To resolve this, the reviewer suggests making expiration_time_millis optional during retrieval and expanding the test suite to verify that credentials with a null access token can be successfully loaded back.
| long expirationMillis = | ||
| OAuth2Utils.validateLong(tokenJson, "expiration_time_millis", TOKEN_STORE_ERROR); | ||
| Date expirationTime = new Date(expirationMillis); |
There was a problem hiding this comment.
In storeCredentials (lines 431-433), expiration_time_millis is now omitted from the stored JSON if expiresBy is null (which happens when accessToken is null).
However, here in getCredentials, expiration_time_millis is retrieved using OAuth2Utils.validateLong and unboxed into a primitive long. If the stored credentials do not contain expiration_time_millis, this will either throw a validation exception (if validateLong enforces presence) or throw a NullPointerException during unboxing (if validateLong returns null).
To support loading credentials that only have a refresh token, both access_token and expiration_time_millis should be treated as optional during loading.
Long expirationMillis =
tokenJson.containsKey("expiration_time_millis")
? OAuth2Utils.validateLong(tokenJson, "expiration_time_millis", TOKEN_STORE_ERROR)
: null;
Date expirationTime = expirationMillis != null ? new Date(expirationMillis) : null;| assertTrue(loaded.contains(REFRESH_TOKEN)); | ||
| } |
There was a problem hiding this comment.
To fully verify that credentials stored with a null access token can be successfully loaded back, we should also call authorizer.getCredentials(USER_ID) and assert its properties. Currently, doing so will fail due to the strict validation of expiration_time_millis in getCredentials.
assertTrue(loaded.contains(REFRESH_TOKEN));
UserCredentials loadedCredentials = authorizer.getCredentials(USER_ID);
assertNotNull(loadedCredentials);
assertEquals(REFRESH_TOKEN, loadedCredentials.getRefreshToken());
assertNull(loadedCredentials.getAccessToken());
}
Fix remaining nullability annotations, builder defaults, and safe token storage in
UserAuthorizer.UserAuthorizer.Builderfields@Nullableso unconfigured fields default to null beforebuild()resolves defaults.UserAuthorizer.Buildersetters.UserAuthorizer.storeCredentials, safely guard expiration time checking to avoid NullPointerException whencredentials.getAccessToken()is null.TokenResponseWithConfigto markclientSecretas@Nullablefor public OAuth clients, and markrefreshTokenreturn type as@Nullable.UserAuthorizerTestcoveringtoBuilder(), builder defaults,TokenResponseWithConfig, andstoreCredentialswith null access token.