Skip to content

Commit ece0440

Browse files
committed
fix(auth): fix JSpecify nullability in UserAuthorizer and TokenStore
1 parent 31c628f commit ece0440

4 files changed

Lines changed: 93 additions & 38 deletions

File tree

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/MemoryTokensStorage.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,15 @@
3535
import java.util.HashMap;
3636
import java.util.Map;
3737
import org.jspecify.annotations.NullMarked;
38+
import org.jspecify.annotations.Nullable;
3839

3940
/** Represents an in-memory storage of tokens. */
4041
@NullMarked
4142
public class MemoryTokensStorage implements TokenStore {
4243
private final Map<String, String> tokensStorage = new HashMap<>();
4344

4445
@Override
45-
public String load(String id) throws IOException {
46+
public @Nullable String load(String id) throws IOException {
4647
return tokensStorage.get(id);
4748
}
4849

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/TokenStore.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import java.io.IOException;
3535
import org.jspecify.annotations.NullMarked;
36+
import org.jspecify.annotations.Nullable;
3637

3738
/** Interface for long term storage of tokens */
3839
@NullMarked
@@ -45,7 +46,7 @@ public interface TokenStore {
4546
* @return The loaded token data.
4647
* @throws IOException An error loading the token data from storage.
4748
*/
48-
String load(String id) throws IOException;
49+
@Nullable String load(String id) throws IOException;
4950

5051
/**
5152
* Put the token data into storage for the given ID.

google-auth-library-java/oauth2_http/java/com/google/auth/oauth2/UserAuthorizer.java

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import java.net.URL;
5050
import java.util.ArrayList;
5151
import java.util.Collection;
52+
import java.util.Collections;
5253
import java.util.Date;
5354
import java.util.List;
5455
import java.util.Map;
@@ -86,7 +87,7 @@ public enum ClientAuthenticationType {
8687
private final HttpTransportFactory transportFactory;
8788
private final URI tokenServerUri;
8889
private final URI userAuthUri;
89-
private final PKCEProvider pkce;
90+
private final @Nullable PKCEProvider pkce;
9091
private final ClientAuthenticationType clientAuthenticationType;
9192

9293
/** Internal constructor. See {@link Builder}. */
@@ -147,7 +148,7 @@ public URI getCallbackUri() {
147148
* @param baseUri The URI to resolve the callback URI relative to.
148149
* @return The resolved URI.
149150
*/
150-
public URI getCallbackUri(URI baseUri) {
151+
public URI getCallbackUri(@Nullable URI baseUri) {
151152
if (callbackUri.isAbsolute()) {
152153
return callbackUri;
153154
}
@@ -184,7 +185,8 @@ public ClientAuthenticationType getClientAuthenticationType() {
184185
* @param baseUri The URI to resolve the OAuth2 callback URI relative to.
185186
* @return The URL that can be navigated or redirected to.
186187
*/
187-
public URL getAuthorizationUrl(String userId, String state, URI baseUri) {
188+
public URL getAuthorizationUrl(
189+
@Nullable String userId, @Nullable String state, @Nullable URI baseUri) {
188190
return this.getAuthorizationUrl(userId, state, baseUri, null);
189191
}
190192

@@ -198,9 +200,9 @@ public URL getAuthorizationUrl(String userId, String state, URI baseUri) {
198200
* @return The URL that can be navigated or redirected to.
199201
*/
200202
public URL getAuthorizationUrl(
201-
String userId,
202-
String state,
203-
URI baseUri,
203+
@Nullable String userId,
204+
@Nullable String state,
205+
@Nullable URI baseUri,
204206
@Nullable Map<String, String> additionalParameters) {
205207
URI resolvedCallbackUri = getCallbackUri(baseUri);
206208
String scopesString = Joiner.on(' ').join(scopes);
@@ -221,9 +223,7 @@ public URL getAuthorizationUrl(
221223
url.put("include_granted_scopes", true);
222224

223225
if (additionalParameters != null) {
224-
for (Map.Entry<String, String> entry : additionalParameters.entrySet()) {
225-
url.put(entry.getKey(), entry.getValue());
226-
}
226+
url.putAll(additionalParameters);
227227
}
228228

229229
if (pkce != null) {
@@ -240,12 +240,8 @@ public URL getAuthorizationUrl(
240240
* @return The loaded credentials or null if there are no valid approved credentials.
241241
* @throws IOException If there is error retrieving or loading the credentials.
242242
*/
243-
@Nullable
244-
public UserCredentials getCredentials(String userId) throws IOException {
243+
public @Nullable UserCredentials getCredentials(String userId) throws IOException {
245244
Preconditions.checkNotNull(userId);
246-
if (tokenStore == null) {
247-
throw new IllegalStateException("Method cannot be called if token store is not specified.");
248-
}
249245
String tokenData = tokenStore.load(userId);
250246
if (tokenData == null) {
251247
return null;
@@ -288,8 +284,9 @@ public UserCredentials getCredentials(String userId) throws IOException {
288284
* @return the UserCredentials instance created from the authorization code.
289285
* @throws IOException An error from the server API call to get the tokens.
290286
*/
291-
public UserCredentials getCredentialsFromCode(String code, URI baseUri) throws IOException {
292-
return getCredentialsFromCode(code, baseUri, null);
287+
public UserCredentials getCredentialsFromCode(String code, @Nullable URI baseUri)
288+
throws IOException {
289+
return getCredentialsFromCode(code, baseUri, Collections.emptyMap());
293290
}
294291

295292
/**
@@ -303,8 +300,11 @@ public UserCredentials getCredentialsFromCode(String code, URI baseUri) throws I
303300
* @throws IOException An error from the server API call to get the tokens.
304301
*/
305302
public UserCredentials getCredentialsFromCode(
306-
String code, URI baseUri, @Nullable Map<String, String> additionalParameters)
303+
String code, @Nullable URI baseUri, @Nullable Map<String, String> additionalParameters)
307304
throws IOException {
305+
if (additionalParameters == null) {
306+
additionalParameters = Collections.emptyMap();
307+
}
308308
TokenResponseWithConfig tokenResponseWithConfig =
309309
getCredentialsFromCodeInternal(code, baseUri, additionalParameters);
310310
return UserCredentials.newBuilder()
@@ -330,7 +330,11 @@ public UserCredentials getCredentialsFromCode(
330330
* @throws IOException If an error occurs during the token exchange process.
331331
*/
332332
public TokenResponseWithConfig getTokenResponseFromAuthCodeExchange(
333-
String code, URI callbackUri, Map<String, String> additionalParameters) throws IOException {
333+
String code, @Nullable URI callbackUri, @Nullable Map<String, String> additionalParameters)
334+
throws IOException {
335+
if (additionalParameters == null) {
336+
additionalParameters = Collections.emptyMap();
337+
}
334338
return getCredentialsFromCodeInternal(code, callbackUri, additionalParameters);
335339
}
336340

@@ -343,8 +347,8 @@ public TokenResponseWithConfig getTokenResponseFromAuthCodeExchange(
343347
* @return UserCredentials instance created from the authorization code.
344348
* @throws IOException An error from the server API call to get the tokens or store the tokens.
345349
*/
346-
public UserCredentials getAndStoreCredentialsFromCode(String userId, String code, URI baseUri)
347-
throws IOException {
350+
public UserCredentials getAndStoreCredentialsFromCode(
351+
String userId, String code, @Nullable URI baseUri) throws IOException {
348352
Preconditions.checkNotNull(userId);
349353
Preconditions.checkNotNull(code);
350354
UserCredentials credentials = getCredentialsFromCode(code, baseUri);
@@ -361,9 +365,6 @@ public UserCredentials getAndStoreCredentialsFromCode(String userId, String code
361365
*/
362366
public void revokeAuthorization(String userId) throws IOException {
363367
Preconditions.checkNotNull(userId);
364-
if (tokenStore == null) {
365-
throw new IllegalStateException("Method cannot be called if token store is not specified.");
366-
}
367368
String tokenData = tokenStore.load(userId);
368369
if (tokenData == null) {
369370
return;
@@ -414,9 +415,6 @@ public void revokeAuthorization(String userId) throws IOException {
414415
* @throws IOException An error storing the credentials.
415416
*/
416417
public void storeCredentials(String userId, UserCredentials credentials) throws IOException {
417-
if (tokenStore == null) {
418-
throw new IllegalStateException("Cannot store tokens if tokenStore is not specified.");
419-
}
420418
AccessToken accessToken = credentials.getAccessToken();
421419
String acessTokenValue = null;
422420
Date expiresBy = null;
@@ -451,7 +449,8 @@ protected void monitorCredentials(String userId, UserCredentials credentials) {
451449
}
452450

453451
private TokenResponseWithConfig getCredentialsFromCodeInternal(
454-
String code, URI baseUri, Map<String, String> additionalParameters) throws IOException {
452+
String code, @Nullable URI baseUri, Map<String, String> additionalParameters)
453+
throws IOException {
455454
Preconditions.checkNotNull(code);
456455
URI resolvedCallbackUri = getCallbackUri(baseUri);
457456

@@ -461,11 +460,7 @@ private TokenResponseWithConfig getCredentialsFromCodeInternal(
461460
tokenData.put("redirect_uri", resolvedCallbackUri);
462461
tokenData.put("grant_type", "authorization_code");
463462

464-
if (additionalParameters != null) {
465-
for (Map.Entry<String, String> entry : additionalParameters.entrySet()) {
466-
tokenData.put(entry.getKey(), entry.getValue());
467-
}
468-
}
463+
tokenData.putAll(additionalParameters);
469464

470465
if (pkce != null) {
471466
tokenData.put("code_verifier", pkce.getCodeVerifier());
@@ -565,7 +560,7 @@ public static class Builder {
565560
private URI userAuthUri;
566561
private Collection<String> scopes;
567562
private HttpTransportFactory transportFactory;
568-
private PKCEProvider pkce;
563+
private @Nullable PKCEProvider pkce;
569564
private ClientAuthenticationType clientAuthenticationType;
570565

571566
protected Builder() {}
@@ -676,14 +671,15 @@ public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
676671
* @return this {@code Builder} object
677672
*/
678673
@CanIgnoreReturnValue
679-
public Builder setPKCEProvider(PKCEProvider pkce) {
674+
public Builder setPKCEProvider(@Nullable PKCEProvider pkce) {
680675
if (pkce != null) {
681676
if (pkce.getCodeChallenge() == null
682677
|| pkce.getCodeVerifier() == null
683678
|| pkce.getCodeChallengeMethod() == null) {
684679

685680
throw new IllegalArgumentException(
686-
"PKCE provider contained null implementations. PKCE object must implement all PKCEProvider methods.");
681+
"PKCE provider contained null implementations. PKCE object must implement all"
682+
+ " PKCEProvider methods.");
687683
}
688684
}
689685
this.pkce = pkce;
@@ -732,7 +728,7 @@ public HttpTransportFactory getHttpTransportFactory() {
732728
return transportFactory;
733729
}
734730

735-
public PKCEProvider getPKCEProvider() {
731+
public @Nullable PKCEProvider getPKCEProvider() {
736732
return pkce;
737733
}
738734

google-auth-library-java/oauth2_http/javatests/com/google/auth/oauth2/UserAuthorizerTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,63 @@ void getCallbackUri_relativeToBase() {
173173
assertEquals(expectedCallbackURI, absoluteCallbackURI);
174174
}
175175

176+
@Test
177+
void getCallbackUri_absoluteCallback_nullBaseUri() {
178+
final URI callbackURI = URI.create("http://example.com/bar");
179+
UserAuthorizer authorizer =
180+
UserAuthorizer.newBuilder()
181+
.setClientId(CLIENT_ID)
182+
.setScopes(DUMMY_SCOPES)
183+
.setCallbackUri(callbackURI)
184+
.build();
185+
186+
URI resultCallbackURI = authorizer.getCallbackUri(null);
187+
188+
assertEquals(callbackURI, resultCallbackURI);
189+
}
190+
191+
@Test
192+
void getCallbackUri_relativeCallback_nullBaseUri_throwsIllegalStateException() {
193+
final URI callbackURI = URI.create("/bar");
194+
UserAuthorizer authorizer =
195+
UserAuthorizer.newBuilder()
196+
.setClientId(CLIENT_ID)
197+
.setScopes(DUMMY_SCOPES)
198+
.setCallbackUri(callbackURI)
199+
.build();
200+
201+
assertThrows(IllegalStateException.class, () -> authorizer.getCallbackUri(null));
202+
}
203+
204+
@Test
205+
void getAuthorizationUrl_nullBaseUri() throws IOException {
206+
final String protocol = "https";
207+
final String host = "accounts.test.com";
208+
final String path = "/o/o/oauth2/auth";
209+
final URI authUri = URI.create(protocol + "://" + host + path);
210+
final URI absoluteCallbackUri = URI.create("http://example.com/oauth2callback");
211+
UserAuthorizer authorizer =
212+
UserAuthorizer.newBuilder()
213+
.setClientId(CLIENT_ID)
214+
.setScopes(DUMMY_SCOPES)
215+
.setCallbackUri(absoluteCallbackUri)
216+
.setUserAuthUri(authUri)
217+
.build();
218+
219+
URL authorizationUrl = authorizer.getAuthorizationUrl(USER_ID, "state", null);
220+
221+
assertEquals(protocol, authorizationUrl.getProtocol());
222+
assertEquals(path, authorizationUrl.getPath());
223+
assertEquals(host, authorizationUrl.getHost());
224+
String query = authorizationUrl.getQuery();
225+
Map<String, String> parameters = TestUtils.parseQuery(query);
226+
assertEquals("state", parameters.get("state"));
227+
assertEquals(USER_ID, parameters.get("login_hint"));
228+
assertEquals(absoluteCallbackUri.toString(), parameters.get("redirect_uri"));
229+
assertEquals(CLIENT_ID_VALUE, parameters.get("client_id"));
230+
assertEquals(DUMMY_SCOPE, parameters.get("scope"));
231+
}
232+
176233
@Test
177234
void getAuthorizationUrl() throws IOException {
178235
final String customState = "custom_state";

0 commit comments

Comments
 (0)