25
25
import com .google .api .core .ApiFuture ;
26
26
import com .google .common .annotations .VisibleForTesting ;
27
27
import com .google .common .base .Strings ;
28
+ import com .google .common .base .Supplier ;
29
+ import com .google .common .base .Suppliers ;
28
30
import com .google .firebase .FirebaseApp ;
29
31
import com .google .firebase .ImplFirebaseTrampolines ;
30
32
import com .google .firebase .auth .FirebaseUserManager .EmailLinkType ;
34
36
import com .google .firebase .auth .UserRecord .CreateRequest ;
35
37
import com .google .firebase .auth .UserRecord .UpdateRequest ;
36
38
import com .google .firebase .auth .internal .FirebaseTokenFactory ;
37
- import com .google .firebase .auth .internal .FirebaseTokenVerifier ;
38
- import com .google .firebase .auth .internal .KeyManagers ;
39
39
import com .google .firebase .internal .CallableOperation ;
40
40
import com .google .firebase .internal .FirebaseService ;
41
41
import com .google .firebase .internal .NonNull ;
42
42
import com .google .firebase .internal .Nullable ;
43
+
43
44
import java .io .IOException ;
44
45
import java .util .List ;
45
46
import java .util .Map ;
46
47
import java .util .concurrent .atomic .AtomicBoolean ;
47
- import java .util .concurrent .atomic .AtomicReference ;
48
48
49
49
/**
50
50
* This class is the entry point for all server-side Firebase Authentication actions.
56
56
*/
57
57
public class FirebaseAuth {
58
58
59
+ private static final String SERVICE_ID = FirebaseAuth .class .getName ();
60
+
59
61
private static final String ERROR_CUSTOM_TOKEN = "ERROR_CUSTOM_TOKEN" ;
60
- private static final String ERROR_INVALID_ID_TOKEN = "ERROR_INVALID_CREDENTIAL" ;
61
- private static final String ERROR_INVALID_SESSION_COOKIE = "ERROR_INVALID_COOKIE" ;
62
62
63
- private final Clock clock ;
63
+ private final Object lock = new Object ();
64
+ private final AtomicBoolean destroyed = new AtomicBoolean (false );
64
65
65
66
private final FirebaseApp firebaseApp ;
66
- private final KeyManagers keyManagers ;
67
- private final String projectId ;
67
+ private final Supplier <FirebaseTokenFactory > tokenFactory ;
68
+ private final Supplier <? extends FirebaseTokenVerifier > idTokenVerifier ;
69
+ private final Supplier <? extends FirebaseTokenVerifier > cookieVerifier ;
68
70
private final JsonFactory jsonFactory ;
69
71
private final FirebaseUserManager userManager ;
70
- private final AtomicReference <FirebaseTokenFactory > tokenFactory ;
71
- private final AtomicBoolean destroyed ;
72
- private final Object lock ;
73
-
74
- private FirebaseAuth (FirebaseApp firebaseApp ) {
75
- this (firebaseApp , KeyManagers .getDefault (firebaseApp , Clock .SYSTEM ), Clock .SYSTEM );
76
- }
77
72
78
- /**
79
- * Constructor for injecting a GooglePublicKeysManager, which is used to verify tokens are
80
- * correctly signed. This should only be used for testing to override the default key manager.
81
- */
82
- @ VisibleForTesting
83
- FirebaseAuth (FirebaseApp firebaseApp , KeyManagers keyManagers , Clock clock ) {
84
- this .firebaseApp = checkNotNull (firebaseApp );
85
- this .keyManagers = checkNotNull (keyManagers );
86
- this .clock = checkNotNull (clock );
87
- this .projectId = ImplFirebaseTrampolines .getProjectId (firebaseApp );
73
+ private FirebaseAuth (Builder builder ) {
74
+ this .firebaseApp = checkNotNull (builder .firebaseApp );
75
+ this .tokenFactory = threadSafeMemoize (builder .tokenFactory );
76
+ this .idTokenVerifier = threadSafeMemoize (builder .idTokenVerifier );
77
+ this .cookieVerifier = threadSafeMemoize (builder .cookieVerifier );
88
78
this .jsonFactory = firebaseApp .getOptions ().getJsonFactory ();
89
79
this .userManager = new FirebaseUserManager (firebaseApp );
90
- this .tokenFactory = new AtomicReference <>(null );
91
- this .destroyed = new AtomicBoolean (false );
92
- this .lock = new Object ();
93
80
}
94
81
95
82
/**
@@ -224,40 +211,23 @@ public ApiFuture<FirebaseToken> verifySessionCookieAsync(String cookie, boolean
224
211
private CallableOperation <FirebaseToken , FirebaseAuthException > verifySessionCookieOp (
225
212
final String cookie , final boolean checkRevoked ) {
226
213
checkNotDestroyed ();
227
- checkState (!Strings .isNullOrEmpty (projectId ),
228
- "Must initialize FirebaseApp with a project ID to call verifySessionCookie()" );
214
+ checkArgument (!Strings .isNullOrEmpty (cookie ), "Session cookie must not be null or empty" );
215
+ final FirebaseTokenVerifier sessionCookieVerifier = getSessionCookieVerifier ( checkRevoked );
229
216
return new CallableOperation <FirebaseToken , FirebaseAuthException >() {
230
217
@ Override
231
218
public FirebaseToken execute () throws FirebaseAuthException {
232
- FirebaseTokenVerifier firebaseTokenVerifier =
233
- FirebaseTokenVerifier .createSessionCookieVerifier (projectId , keyManagers , clock );
234
- FirebaseToken firebaseToken ;
235
- try {
236
- firebaseToken = FirebaseToken .parse (jsonFactory , cookie );
237
- } catch (IOException e ) {
238
- throw new FirebaseAuthException (ERROR_INVALID_SESSION_COOKIE ,
239
- "Failed to parse cookie" , e );
240
- }
241
- // This will throw a FirebaseAuthException with details on how the token is invalid.
242
- firebaseTokenVerifier .verifyTokenAndSignature (firebaseToken .getToken ());
243
-
244
- if (checkRevoked ) {
245
- checkRevoked (firebaseToken , "session cookie" ,
246
- FirebaseUserManager .SESSION_COOKIE_REVOKED_ERROR );
247
- }
248
- return firebaseToken ;
219
+ return sessionCookieVerifier .verifyToken (cookie );
249
220
}
250
221
};
251
222
}
252
223
253
- private void checkRevoked (
254
- FirebaseToken firebaseToken , String label , String errorCode ) throws FirebaseAuthException {
255
- String uid = firebaseToken .getUid ();
256
- UserRecord user = userManager .getUserById (uid );
257
- long issuedAt = (long ) firebaseToken .getClaims ().get ("iat" );
258
- if (user .getTokensValidAfterTimestamp () > issuedAt * 1000 ) {
259
- throw new FirebaseAuthException (errorCode , "Firebase " + label + " revoked" );
224
+ @ VisibleForTesting
225
+ FirebaseTokenVerifier getSessionCookieVerifier (boolean checkRevoked ) {
226
+ FirebaseTokenVerifier verifier = cookieVerifier .get ();
227
+ if (checkRevoked ) {
228
+ verifier = RevocationCheckDecorator .decorateSessionCookieVerifier (verifier , userManager );
260
229
}
230
+ return verifier ;
261
231
}
262
232
263
233
/**
@@ -355,7 +325,7 @@ private CallableOperation<String, FirebaseAuthException> createCustomTokenOp(
355
325
final String uid , final Map <String , Object > developerClaims ) {
356
326
checkNotDestroyed ();
357
327
checkArgument (!Strings .isNullOrEmpty (uid ), "uid must not be null or empty" );
358
- final FirebaseTokenFactory tokenFactory = ensureTokenFactory ();
328
+ final FirebaseTokenFactory tokenFactory = this . tokenFactory . get ();
359
329
return new CallableOperation <String , FirebaseAuthException >() {
360
330
@ Override
361
331
public String execute () throws FirebaseAuthException {
@@ -369,29 +339,6 @@ public String execute() throws FirebaseAuthException {
369
339
};
370
340
}
371
341
372
- private FirebaseTokenFactory ensureTokenFactory () {
373
- FirebaseTokenFactory result = this .tokenFactory .get ();
374
- if (result == null ) {
375
- synchronized (lock ) {
376
- result = this .tokenFactory .get ();
377
- if (result == null ) {
378
- try {
379
- result = FirebaseTokenFactory .fromApp (firebaseApp , clock );
380
- this .tokenFactory .set (result );
381
- } catch (IOException e ) {
382
- throw new IllegalStateException (
383
- "Failed to initialize FirebaseTokenFactory. Make sure to initialize the SDK "
384
- + "with service account credentials or specify a service account "
385
- + "ID with iam.serviceAccounts.signBlob permission. Please refer to "
386
- + "https://firebase.google.com/docs/auth/admin/create-custom-tokens for more "
387
- + "details on creating custom tokens." , e );
388
- }
389
- }
390
- }
391
- }
392
- return result ;
393
- }
394
-
395
342
/**
396
343
* Parses and verifies a Firebase ID Token.
397
344
*
@@ -472,31 +419,24 @@ private CallableOperation<FirebaseToken, FirebaseAuthException> verifyIdTokenOp(
472
419
final String token , final boolean checkRevoked ) {
473
420
checkNotDestroyed ();
474
421
checkArgument (!Strings .isNullOrEmpty (token ), "ID token must not be null or empty" );
475
- checkArgument (!Strings .isNullOrEmpty (projectId ),
476
- "Must initialize FirebaseApp with a project ID to call verifyIdToken()" );
422
+ final FirebaseTokenVerifier verifier = getIdTokenVerifier (checkRevoked );
477
423
return new CallableOperation <FirebaseToken , FirebaseAuthException >() {
478
424
@ Override
479
425
protected FirebaseToken execute () throws FirebaseAuthException {
480
- FirebaseTokenVerifier firebaseTokenVerifier =
481
- FirebaseTokenVerifier .createIdTokenVerifier (projectId , keyManagers , clock );
482
- FirebaseToken firebaseToken ;
483
- try {
484
- firebaseToken = FirebaseToken .parse (jsonFactory , token );
485
- } catch (IOException e ) {
486
- throw new FirebaseAuthException (ERROR_INVALID_ID_TOKEN , "Failed to parse token" , e );
487
- }
488
-
489
- // This will throw a FirebaseAuthException with details on how the token is invalid.
490
- firebaseTokenVerifier .verifyTokenAndSignature (firebaseToken .getToken ());
491
-
492
- if (checkRevoked ) {
493
- checkRevoked (firebaseToken , "auth token" , FirebaseUserManager .ID_TOKEN_REVOKED_ERROR );
494
- }
495
- return firebaseToken ;
426
+ return verifier .verifyToken (token );
496
427
}
497
428
};
498
429
}
499
430
431
+ @ VisibleForTesting
432
+ FirebaseTokenVerifier getIdTokenVerifier (boolean checkRevoked ) {
433
+ FirebaseTokenVerifier verifier = idTokenVerifier .get ();
434
+ if (checkRevoked ) {
435
+ verifier = RevocationCheckDecorator .decorateIdTokenVerifier (verifier , userManager );
436
+ }
437
+ return verifier ;
438
+ }
439
+
500
440
/**
501
441
* Revokes all refresh tokens for the specified user.
502
442
*
@@ -705,13 +645,12 @@ public ApiFuture<ListUsersPage> listUsersAsync(@Nullable String pageToken) {
705
645
* @throws IllegalArgumentException If the specified page token is empty, or max results value
706
646
* is invalid.
707
647
*/
708
- public ApiFuture <ListUsersPage > listUsersAsync (
709
- @ Nullable final String pageToken , final int maxResults ) {
648
+ public ApiFuture <ListUsersPage > listUsersAsync (@ Nullable String pageToken , int maxResults ) {
710
649
return listUsersOp (pageToken , maxResults ).callAsync (firebaseApp );
711
650
}
712
651
713
652
private CallableOperation <ListUsersPage , FirebaseAuthException > listUsersOp (
714
- @ Nullable String pageToken , int maxResults ) {
653
+ @ Nullable final String pageToken , final int maxResults ) {
715
654
checkNotDestroyed ();
716
655
final PageFactory factory = new PageFactory (
717
656
new DefaultUserSource (userManager , jsonFactory ), maxResults , pageToken );
@@ -874,7 +813,7 @@ public void deleteUser(@NonNull String uid) throws FirebaseAuthException {
874
813
* {@link FirebaseAuthException}.
875
814
* @throws IllegalArgumentException If the user ID string is null or empty.
876
815
*/
877
- public ApiFuture <Void > deleteUserAsync (final String uid ) {
816
+ public ApiFuture <Void > deleteUserAsync (String uid ) {
878
817
return deleteUserOp (uid ).callAsync (firebaseApp );
879
818
}
880
819
@@ -959,7 +898,7 @@ public ApiFuture<UserImportResult> importUsersAsync(List<ImportUserRecord> users
959
898
}
960
899
961
900
private CallableOperation <UserImportResult , FirebaseAuthException > importUsersOp (
962
- List <ImportUserRecord > users , UserImportOptions options ) {
901
+ final List <ImportUserRecord > users , final UserImportOptions options ) {
963
902
checkNotDestroyed ();
964
903
final UserImportRequest request = new UserImportRequest (users , options , jsonFactory );
965
904
return new CallableOperation <UserImportResult , FirebaseAuthException >() {
@@ -1130,6 +1069,11 @@ public ApiFuture<String> generateSignInWithEmailLinkAsync(
1130
1069
.callAsync (firebaseApp );
1131
1070
}
1132
1071
1072
+ @ VisibleForTesting
1073
+ FirebaseUserManager getUserManager () {
1074
+ return this .userManager ;
1075
+ }
1076
+
1133
1077
private CallableOperation <String , FirebaseAuthException > generateEmailActionLinkOp (
1134
1078
final EmailLinkType type , final String email , final ActionCodeSettings settings ) {
1135
1079
checkNotDestroyed ();
@@ -1145,9 +1089,17 @@ protected String execute() throws FirebaseAuthException {
1145
1089
};
1146
1090
}
1147
1091
1148
- @ VisibleForTesting
1149
- FirebaseUserManager getUserManager () {
1150
- return this .userManager ;
1092
+ private <T > Supplier <T > threadSafeMemoize (final Supplier <T > supplier ) {
1093
+ checkNotNull (supplier );
1094
+ return Suppliers .memoize (new Supplier <T >() {
1095
+ @ Override
1096
+ public T get () {
1097
+ synchronized (lock ) {
1098
+ checkNotDestroyed ();
1099
+ return supplier .get ();
1100
+ }
1101
+ }
1102
+ });
1151
1103
}
1152
1104
1153
1105
private void checkNotDestroyed () {
@@ -1163,12 +1115,72 @@ private void destroy() {
1163
1115
}
1164
1116
}
1165
1117
1166
- private static final String SERVICE_ID = FirebaseAuth .class .getName ();
1118
+ private static FirebaseAuth fromApp (final FirebaseApp app ) {
1119
+ return FirebaseAuth .builder ()
1120
+ .setFirebaseApp (app )
1121
+ .setTokenFactory (new Supplier <FirebaseTokenFactory >() {
1122
+ @ Override
1123
+ public FirebaseTokenFactory get () {
1124
+ return FirebaseTokenUtils .createTokenFactory (app , Clock .SYSTEM );
1125
+ }
1126
+ })
1127
+ .setIdTokenVerifier (new Supplier <FirebaseTokenVerifier >() {
1128
+ @ Override
1129
+ public FirebaseTokenVerifier get () {
1130
+ return FirebaseTokenUtils .createIdTokenVerifier (app , Clock .SYSTEM );
1131
+ }
1132
+ })
1133
+ .setCookieVerifier (new Supplier <FirebaseTokenVerifier >() {
1134
+ @ Override
1135
+ public FirebaseTokenVerifier get () {
1136
+ return FirebaseTokenUtils .createSessionCookieVerifier (app , Clock .SYSTEM );
1137
+ }
1138
+ })
1139
+ .build ();
1140
+ }
1141
+
1142
+ @ VisibleForTesting
1143
+ static Builder builder () {
1144
+ return new Builder ();
1145
+ }
1146
+
1147
+ static class Builder {
1148
+ private FirebaseApp firebaseApp ;
1149
+ private Supplier <FirebaseTokenFactory > tokenFactory ;
1150
+ private Supplier <? extends FirebaseTokenVerifier > idTokenVerifier ;
1151
+ private Supplier <? extends FirebaseTokenVerifier > cookieVerifier ;
1152
+
1153
+ private Builder () { }
1154
+
1155
+ Builder setFirebaseApp (FirebaseApp firebaseApp ) {
1156
+ this .firebaseApp = firebaseApp ;
1157
+ return this ;
1158
+ }
1159
+
1160
+ Builder setTokenFactory (Supplier <FirebaseTokenFactory > tokenFactory ) {
1161
+ this .tokenFactory = tokenFactory ;
1162
+ return this ;
1163
+ }
1164
+
1165
+ Builder setIdTokenVerifier (Supplier <? extends FirebaseTokenVerifier > idTokenVerifier ) {
1166
+ this .idTokenVerifier = idTokenVerifier ;
1167
+ return this ;
1168
+ }
1169
+
1170
+ Builder setCookieVerifier (Supplier <? extends FirebaseTokenVerifier > cookieVerifier ) {
1171
+ this .cookieVerifier = cookieVerifier ;
1172
+ return this ;
1173
+ }
1174
+
1175
+ FirebaseAuth build () {
1176
+ return new FirebaseAuth (this );
1177
+ }
1178
+ }
1167
1179
1168
1180
private static class FirebaseAuthService extends FirebaseService <FirebaseAuth > {
1169
1181
1170
1182
FirebaseAuthService (FirebaseApp app ) {
1171
- super (SERVICE_ID , new FirebaseAuth (app ));
1183
+ super (SERVICE_ID , FirebaseAuth . fromApp (app ));
1172
1184
}
1173
1185
1174
1186
@ Override
0 commit comments