Skip to content

fix(auth): fix remaining nullability in UserAuthorizer and Builder - #14158

Open
lqiu96 wants to merge 8 commits into
mainfrom
fix_auth_user_authorizer_nullability
Open

fix(auth): fix remaining nullability in UserAuthorizer and Builder#14158
lqiu96 wants to merge 8 commits into
mainfrom
fix_auth_user_authorizer_nullability

Conversation

@lqiu96

@lqiu96 lqiu96 commented Aug 20, 2026

Copy link
Copy Markdown
Member

Fix remaining nullability annotations, builder defaults, and safe token storage in UserAuthorizer.

  • Make UserAuthorizer.Builder fields @Nullable so unconfigured fields default to null before build() resolves defaults.
  • Enforce non-null parameters across all UserAuthorizer.Builder setters.
  • In UserAuthorizer.storeCredentials, safely guard expiration time checking to avoid NullPointerException when credentials.getAccessToken() is null.
  • Update TokenResponseWithConfig to mark clientSecret as @Nullable for public OAuth clients, and mark refreshToken return type as @Nullable.
  • Add unit tests in UserAuthorizerTest covering toBuilder(), builder defaults, TokenResponseWithConfig, and storeCredentials with null access token.

@lqiu96
lqiu96 requested review from a team as code owners August 20, 2026 22:09

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 804 to +840
@@ -836,8 +837,7 @@ public URI getTokenServerUri() {
*
* @return The refresh token, or null if not granted.
*/
@Nullable
public String getRefreshToken() {
public @Nullable String getRefreshToken() {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Turns out Oauth2Utils#validateOptionalString can return null if it doesn't exist. So these two fields use it and it can be null

Comment on lines +782 to +786
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);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are passed in from UserAuthorizer which enforces them to be non-null

@lqiu96
lqiu96 requested a review from whowes August 20, 2026 23:17
@lqiu96

lqiu96 commented Aug 21, 2026

Copy link
Copy Markdown
Member Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +252 to 254
long expirationMillis =
OAuth2Utils.validateLong(tokenJson, "expiration_time_millis", TOKEN_STORE_ERROR);
Date expirationTime = new Date(expirationMillis);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

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;

Comment on lines +1007 to +1008
assertTrue(loaded.contains(REFRESH_TOKEN));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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());
  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant