2525import com .google .api .core .ApiFuture ;
2626import com .google .common .annotations .VisibleForTesting ;
2727import com .google .common .base .Strings ;
28+ import com .google .common .base .Supplier ;
29+ import com .google .common .base .Suppliers ;
2830import com .google .firebase .FirebaseApp ;
2931import com .google .firebase .ImplFirebaseTrampolines ;
3032import com .google .firebase .auth .FirebaseUserManager .EmailLinkType ;
3436import com .google .firebase .auth .UserRecord .CreateRequest ;
3537import com .google .firebase .auth .UserRecord .UpdateRequest ;
3638import com .google .firebase .auth .internal .FirebaseTokenFactory ;
37- import com .google .firebase .auth .internal .FirebaseTokenVerifier ;
38- import com .google .firebase .auth .internal .KeyManagers ;
3939import com .google .firebase .internal .CallableOperation ;
4040import com .google .firebase .internal .FirebaseService ;
4141import com .google .firebase .internal .NonNull ;
4242import com .google .firebase .internal .Nullable ;
43+
4344import java .io .IOException ;
4445import java .util .List ;
4546import java .util .Map ;
4647import java .util .concurrent .atomic .AtomicBoolean ;
47- import java .util .concurrent .atomic .AtomicReference ;
4848
4949/**
5050 * This class is the entry point for all server-side Firebase Authentication actions.
5656 */
5757public class FirebaseAuth {
5858
59+ private static final String SERVICE_ID = FirebaseAuth .class .getName ();
60+
5961 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" ;
6262
63- private final Clock clock ;
63+ private final Object lock = new Object ();
64+ private final AtomicBoolean destroyed = new AtomicBoolean (false );
6465
6566 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 ;
6870 private final JsonFactory jsonFactory ;
6971 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- }
7772
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 );
8878 this .jsonFactory = firebaseApp .getOptions ().getJsonFactory ();
8979 this .userManager = new FirebaseUserManager (firebaseApp );
90- this .tokenFactory = new AtomicReference <>(null );
91- this .destroyed = new AtomicBoolean (false );
92- this .lock = new Object ();
9380 }
9481
9582 /**
@@ -224,40 +211,23 @@ public ApiFuture<FirebaseToken> verifySessionCookieAsync(String cookie, boolean
224211 private CallableOperation <FirebaseToken , FirebaseAuthException > verifySessionCookieOp (
225212 final String cookie , final boolean checkRevoked ) {
226213 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 );
229216 return new CallableOperation <FirebaseToken , FirebaseAuthException >() {
230217 @ Override
231218 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 );
249220 }
250221 };
251222 }
252223
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 );
260229 }
230+ return verifier ;
261231 }
262232
263233 /**
@@ -355,7 +325,7 @@ private CallableOperation<String, FirebaseAuthException> createCustomTokenOp(
355325 final String uid , final Map <String , Object > developerClaims ) {
356326 checkNotDestroyed ();
357327 checkArgument (!Strings .isNullOrEmpty (uid ), "uid must not be null or empty" );
358- final FirebaseTokenFactory tokenFactory = ensureTokenFactory ();
328+ final FirebaseTokenFactory tokenFactory = this . tokenFactory . get ();
359329 return new CallableOperation <String , FirebaseAuthException >() {
360330 @ Override
361331 public String execute () throws FirebaseAuthException {
@@ -369,29 +339,6 @@ public String execute() throws FirebaseAuthException {
369339 };
370340 }
371341
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-
395342 /**
396343 * Parses and verifies a Firebase ID Token.
397344 *
@@ -472,31 +419,24 @@ private CallableOperation<FirebaseToken, FirebaseAuthException> verifyIdTokenOp(
472419 final String token , final boolean checkRevoked ) {
473420 checkNotDestroyed ();
474421 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 );
477423 return new CallableOperation <FirebaseToken , FirebaseAuthException >() {
478424 @ Override
479425 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 );
496427 }
497428 };
498429 }
499430
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+
500440 /**
501441 * Revokes all refresh tokens for the specified user.
502442 *
@@ -705,13 +645,12 @@ public ApiFuture<ListUsersPage> listUsersAsync(@Nullable String pageToken) {
705645 * @throws IllegalArgumentException If the specified page token is empty, or max results value
706646 * is invalid.
707647 */
708- public ApiFuture <ListUsersPage > listUsersAsync (
709- @ Nullable final String pageToken , final int maxResults ) {
648+ public ApiFuture <ListUsersPage > listUsersAsync (@ Nullable String pageToken , int maxResults ) {
710649 return listUsersOp (pageToken , maxResults ).callAsync (firebaseApp );
711650 }
712651
713652 private CallableOperation <ListUsersPage , FirebaseAuthException > listUsersOp (
714- @ Nullable String pageToken , int maxResults ) {
653+ @ Nullable final String pageToken , final int maxResults ) {
715654 checkNotDestroyed ();
716655 final PageFactory factory = new PageFactory (
717656 new DefaultUserSource (userManager , jsonFactory ), maxResults , pageToken );
@@ -874,7 +813,7 @@ public void deleteUser(@NonNull String uid) throws FirebaseAuthException {
874813 * {@link FirebaseAuthException}.
875814 * @throws IllegalArgumentException If the user ID string is null or empty.
876815 */
877- public ApiFuture <Void > deleteUserAsync (final String uid ) {
816+ public ApiFuture <Void > deleteUserAsync (String uid ) {
878817 return deleteUserOp (uid ).callAsync (firebaseApp );
879818 }
880819
@@ -959,7 +898,7 @@ public ApiFuture<UserImportResult> importUsersAsync(List<ImportUserRecord> users
959898 }
960899
961900 private CallableOperation <UserImportResult , FirebaseAuthException > importUsersOp (
962- List <ImportUserRecord > users , UserImportOptions options ) {
901+ final List <ImportUserRecord > users , final UserImportOptions options ) {
963902 checkNotDestroyed ();
964903 final UserImportRequest request = new UserImportRequest (users , options , jsonFactory );
965904 return new CallableOperation <UserImportResult , FirebaseAuthException >() {
@@ -1130,6 +1069,11 @@ public ApiFuture<String> generateSignInWithEmailLinkAsync(
11301069 .callAsync (firebaseApp );
11311070 }
11321071
1072+ @ VisibleForTesting
1073+ FirebaseUserManager getUserManager () {
1074+ return this .userManager ;
1075+ }
1076+
11331077 private CallableOperation <String , FirebaseAuthException > generateEmailActionLinkOp (
11341078 final EmailLinkType type , final String email , final ActionCodeSettings settings ) {
11351079 checkNotDestroyed ();
@@ -1145,9 +1089,17 @@ protected String execute() throws FirebaseAuthException {
11451089 };
11461090 }
11471091
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+ });
11511103 }
11521104
11531105 private void checkNotDestroyed () {
@@ -1163,12 +1115,72 @@ private void destroy() {
11631115 }
11641116 }
11651117
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+ }
11671179
11681180 private static class FirebaseAuthService extends FirebaseService <FirebaseAuth > {
11691181
11701182 FirebaseAuthService (FirebaseApp app ) {
1171- super (SERVICE_ID , new FirebaseAuth (app ));
1183+ super (SERVICE_ID , FirebaseAuth . fromApp (app ));
11721184 }
11731185
11741186 @ Override
0 commit comments