() {
+ @Override
+ public FirebaseUserManager get() {
+ return FirebaseUserManager
+ .builder()
+ .setFirebaseApp(app)
+ .setTenantId(tenantId)
+ .build();
+ }
+ });
+ }
+
+ /**
+ * Creates a Firebase custom token for the given UID. This token can then be sent back to a client
+ * application to be used with the signInWithCustomToken
+ * authentication API.
+ *
+ * {@link FirebaseApp} must have been initialized with service account credentials to use call
+ * this method.
+ *
+ * @param uid The UID to store in the token. This identifies the user to other Firebase services
+ * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
+ * @return A Firebase custom token string.
+ * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
+ * been initialized with service account credentials.
+ * @throws FirebaseAuthException If an error occurs while generating the custom token.
+ */
+ public String createCustomToken(@NonNull String uid) throws FirebaseAuthException {
+ return createCustomToken(uid, null);
+ }
+
+ /**
+ * Creates a Firebase custom token for the given UID, containing the specified additional claims.
+ * This token can then be sent back to a client application to be used with the signInWithCustomToken
+ * authentication API.
+ *
+ *
This method attempts to generate a token using:
+ *
+ *
+ * - the private key of {@link FirebaseApp}'s service account credentials, if provided at
+ * initialization.
+ *
- the IAM
+ * service if a service account email was specified via {@link
+ * com.google.firebase.FirebaseOptions.Builder#setServiceAccountId(String)}.
+ *
- the App
+ * Identity service if the code is deployed in the Google App Engine standard
+ * environment.
+ *
- the local
+ * Metadata server if the code is deployed in a different GCP-managed environment like
+ * Google Compute Engine.
+ *
+ *
+ * This method throws an exception when all the above fail.
+ *
+ * @param uid The UID to store in the token. This identifies the user to other Firebase services
+ * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
+ * @param developerClaims Additional claims to be stored in the token (and made available to
+ * security rules in Database, Storage, etc.). These must be able to be serialized to JSON
+ * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.)
+ * @return A Firebase custom token string.
+ * @throws IllegalArgumentException If the specified uid is null or empty.
+ * @throws IllegalStateException If the SDK fails to discover a viable approach for signing
+ * tokens.
+ * @throws FirebaseAuthException If an error occurs while generating the custom token.
+ */
+ public String createCustomToken(
+ @NonNull String uid, @Nullable Map developerClaims)
+ throws FirebaseAuthException {
+ return createCustomTokenOp(uid, developerClaims).call();
+ }
+
+ /**
+ * Similar to {@link #createCustomToken(String)} but performs the operation asynchronously.
+ *
+ * @param uid The UID to store in the token. This identifies the user to other Firebase services
+ * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
+ * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom
+ * token, or unsuccessfully with the failure Exception.
+ * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
+ * been initialized with service account credentials.
+ */
+ public ApiFuture createCustomTokenAsync(@NonNull String uid) {
+ return createCustomTokenAsync(uid, null);
+ }
+
+ /**
+ * Similar to {@link #createCustomToken(String, Map)} but performs the operation asynchronously.
+ *
+ * @param uid The UID to store in the token. This identifies the user to other Firebase services
+ * (Realtime Database, Storage, etc.). Should be less than 128 characters.
+ * @param developerClaims Additional claims to be stored in the token (and made available to
+ * security rules in Database, Storage, etc.). These must be able to be serialized to JSON
+ * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.)
+ * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom
+ * token, or unsuccessfully with the failure Exception.
+ * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
+ * been initialized with service account credentials.
+ */
+ public ApiFuture createCustomTokenAsync(
+ @NonNull String uid, @Nullable Map developerClaims) {
+ return createCustomTokenOp(uid, developerClaims).callAsync(firebaseApp);
+ }
+
+ private CallableOperation createCustomTokenOp(
+ final String uid, final Map developerClaims) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
+ final FirebaseTokenFactory tokenFactory = this.tokenFactory.get();
+ return new CallableOperation() {
+ @Override
+ public String execute() throws FirebaseAuthException {
+ return tokenFactory.createSignedCustomAuthTokenForUser(uid, developerClaims);
+ }
+ };
+ }
+
+ /**
+ * Parses and verifies a Firebase ID Token.
+ *
+ * A Firebase application can identify itself to a trusted backend server by sending its
+ * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication
+ * client) with its requests. The backend server can then use the {@code verifyIdToken()} method
+ * to verify that the token is valid. This method ensures that the token is correctly signed, has
+ * not expired, and it was issued to the Firebase project associated with this {@link
+ * FirebaseAuth} instance.
+ *
+ *
This method does not check whether a token has been revoked. Use {@link
+ * #verifyIdToken(String, boolean)} to perform an additional revocation check.
+ *
+ * @param idToken A Firebase ID token string to parse and verify.
+ * @return A {@link FirebaseToken} representing the verified and decoded token.
+ * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
+ * instance does not have a project ID associated with it.
+ * @throws FirebaseAuthException If an error occurs while parsing or validating the token.
+ */
+ public FirebaseToken verifyIdToken(@NonNull String idToken) throws FirebaseAuthException {
+ return verifyIdToken(idToken, false);
+ }
+
+ /**
+ * Parses and verifies a Firebase ID Token.
+ *
+ *
A Firebase application can identify itself to a trusted backend server by sending its
+ * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication
+ * client) with its requests. The backend server can then use the {@code verifyIdToken()} method
+ * to verify that the token is valid. This method ensures that the token is correctly signed, has
+ * not expired, and it was issued to the Firebase project associated with this {@link
+ * FirebaseAuth} instance.
+ *
+ *
If {@code checkRevoked} is set to true, this method performs an additional check to see if
+ * the ID token has been revoked since it was issues. This requires making an additional remote
+ * API call.
+ *
+ * @param idToken A Firebase ID token string to parse and verify.
+ * @param checkRevoked A boolean denoting whether to check if the tokens were revoked.
+ * @return A {@link FirebaseToken} representing the verified and decoded token.
+ * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
+ * instance does not have a project ID associated with it.
+ * @throws FirebaseAuthException If an error occurs while parsing or validating the token.
+ */
+ public FirebaseToken verifyIdToken(@NonNull String idToken, boolean checkRevoked)
+ throws FirebaseAuthException {
+ return verifyIdTokenOp(idToken, checkRevoked).call();
+ }
+
+ /**
+ * Similar to {@link #verifyIdToken(String)} but performs the operation asynchronously.
+ *
+ * @param idToken A Firebase ID Token to verify and parse.
+ * @return An {@code ApiFuture} which will complete successfully with the parsed token, or
+ * unsuccessfully with a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
+ * instance does not have a project ID associated with it.
+ */
+ public ApiFuture verifyIdTokenAsync(@NonNull String idToken) {
+ return verifyIdTokenAsync(idToken, false);
+ }
+
+ /**
+ * Similar to {@link #verifyIdToken(String, boolean)} but performs the operation asynchronously.
+ *
+ * @param idToken A Firebase ID Token to verify and parse.
+ * @param checkRevoked A boolean denoting whether to check if the tokens were revoked.
+ * @return An {@code ApiFuture} which will complete successfully with the parsed token, or
+ * unsuccessfully with a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
+ * instance does not have a project ID associated with it.
+ */
+ public ApiFuture
+ verifyIdTokenAsync(@NonNull String idToken, boolean checkRevoked) {
+ return verifyIdTokenOp(idToken, checkRevoked).callAsync(firebaseApp);
+ }
+
+ private CallableOperation verifyIdTokenOp(
+ final String idToken, final boolean checkRevoked) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(idToken), "ID token must not be null or empty");
+ final FirebaseTokenVerifier verifier = getIdTokenVerifier(checkRevoked);
+ return new CallableOperation() {
+ @Override
+ protected FirebaseToken execute() throws FirebaseAuthException {
+ return verifier.verifyToken(idToken);
+ }
+ };
+ }
+
+ @VisibleForTesting
+ FirebaseTokenVerifier getIdTokenVerifier(boolean checkRevoked) {
+ FirebaseTokenVerifier verifier = idTokenVerifier.get();
+ if (checkRevoked) {
+ FirebaseUserManager userManager = getUserManager();
+ verifier = RevocationCheckDecorator.decorateIdTokenVerifier(verifier, userManager);
+ }
+ return verifier;
+ }
+
+ /**
+ * Revokes all refresh tokens for the specified user.
+ *
+ * Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in
+ * milliseconds since the epoch and truncated to 1 second accuracy. It is important that the
+ * server on which this is called has its clock set correctly and synchronized.
+ *
+ *
While this will revoke all sessions for a specified user and disable any new ID tokens for
+ * existing sessions from getting minted, existing ID tokens may remain active until their natural
+ * expiration (one hour). To verify that ID tokens are revoked, use {@link
+ * #verifyIdTokenAsync(String, boolean)}.
+ *
+ * @param uid The user id for which tokens are revoked.
+ * @throws IllegalArgumentException If the user ID is null or empty.
+ * @throws FirebaseAuthException If an error occurs while revoking tokens.
+ */
+ public void revokeRefreshTokens(@NonNull String uid) throws FirebaseAuthException {
+ revokeRefreshTokensOp(uid).call();
+ }
+
+ /**
+ * Similar to {@link #revokeRefreshTokens(String)} but performs the operation asynchronously.
+ *
+ * @param uid The user id for which tokens are revoked.
+ * @return An {@code ApiFuture} which will complete successfully or fail with a {@link
+ * FirebaseAuthException} in the event of an error.
+ * @throws IllegalArgumentException If the user ID is null or empty.
+ */
+ public ApiFuture revokeRefreshTokensAsync(@NonNull String uid) {
+ return revokeRefreshTokensOp(uid).callAsync(firebaseApp);
+ }
+
+ private CallableOperation revokeRefreshTokensOp(final String uid) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected Void execute() throws FirebaseAuthException {
+ int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
+ UserRecord.UpdateRequest request =
+ new UserRecord.UpdateRequest(uid).setValidSince(currentTimeSeconds);
+ userManager.updateUser(request, jsonFactory);
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Gets the user data corresponding to the specified user ID.
+ *
+ * @param uid A user ID string.
+ * @return A {@link UserRecord} instance.
+ * @throws IllegalArgumentException If the user ID string is null or empty.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public UserRecord getUser(@NonNull String uid) throws FirebaseAuthException {
+ return getUserOp(uid).call();
+ }
+
+ /**
+ * Similar to {@link #getUser(String)} but performs the operation asynchronously.
+ *
+ * @param uid A user ID string.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
+ * instance. If an error occurs while retrieving user data or if the specified user ID does
+ * not exist, the future throws a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the user ID string is null or empty.
+ */
+ public ApiFuture getUserAsync(@NonNull String uid) {
+ return getUserOp(uid).callAsync(firebaseApp);
+ }
+
+ private CallableOperation getUserOp(final String uid) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserRecord execute() throws FirebaseAuthException {
+ return userManager.getUserById(uid);
+ }
+ };
+ }
+
+ /**
+ * Gets the user data corresponding to the specified user email.
+ *
+ * @param email A user email address string.
+ * @return A {@link UserRecord} instance.
+ * @throws IllegalArgumentException If the email is null or empty.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public UserRecord getUserByEmail(@NonNull String email) throws FirebaseAuthException {
+ return getUserByEmailOp(email).call();
+ }
+
+ /**
+ * Similar to {@link #getUserByEmail(String)} but performs the operation asynchronously.
+ *
+ * @param email A user email address string.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
+ * instance. If an error occurs while retrieving user data or if the email address does not
+ * correspond to a user, the future throws a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email is null or empty.
+ */
+ public ApiFuture getUserByEmailAsync(@NonNull String email) {
+ return getUserByEmailOp(email).callAsync(firebaseApp);
+ }
+
+ private CallableOperation getUserByEmailOp(
+ final String email) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserRecord execute() throws FirebaseAuthException {
+ return userManager.getUserByEmail(email);
+ }
+ };
+ }
+
+ /**
+ * Gets the user data corresponding to the specified user phone number.
+ *
+ * @param phoneNumber A user phone number string.
+ * @return A a {@link UserRecord} instance.
+ * @throws IllegalArgumentException If the phone number is null or empty.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public UserRecord getUserByPhoneNumber(@NonNull String phoneNumber) throws FirebaseAuthException {
+ return getUserByPhoneNumberOp(phoneNumber).call();
+ }
+
+ /**
+ * Gets the user data corresponding to the specified user phone number.
+ *
+ * @param phoneNumber A user phone number string.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
+ * instance. If an error occurs while retrieving user data or if the phone number does not
+ * correspond to a user, the future throws a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the phone number is null or empty.
+ */
+ public ApiFuture getUserByPhoneNumberAsync(@NonNull String phoneNumber) {
+ return getUserByPhoneNumberOp(phoneNumber).callAsync(firebaseApp);
+ }
+
+ private CallableOperation getUserByPhoneNumberOp(
+ final String phoneNumber) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserRecord execute() throws FirebaseAuthException {
+ return userManager.getUserByPhoneNumber(phoneNumber);
+ }
+ };
+ }
+
+ /**
+ * Gets a page of users starting from the specified {@code pageToken}. Page size is limited to
+ * 1000 users.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
+ * @return A {@link ListUsersPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public ListUsersPage listUsers(@Nullable String pageToken) throws FirebaseAuthException {
+ return listUsers(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS);
+ }
+
+ /**
+ * Gets a page of users starting from the specified {@code pageToken}.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
+ * @param maxResults Maximum number of users to include in the returned page. This may not exceed
+ * 1000.
+ * @return A {@link ListUsersPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public ListUsersPage listUsers(@Nullable String pageToken, int maxResults)
+ throws FirebaseAuthException {
+ return listUsersOp(pageToken, maxResults).call();
+ }
+
+ /**
+ * Similar to {@link #listUsers(String)} but performs the operation asynchronously.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage}
+ * instance. If an error occurs while retrieving user data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty.
+ */
+ public ApiFuture listUsersAsync(@Nullable String pageToken) {
+ return listUsersAsync(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS);
+ }
+
+ /**
+ * Similar to {@link #listUsers(String, int)} but performs the operation asynchronously.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
+ * @param maxResults Maximum number of users to include in the returned page. This may not exceed
+ * 1000.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage}
+ * instance. If an error occurs while retrieving user data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ */
+ public ApiFuture listUsersAsync(@Nullable String pageToken, int maxResults) {
+ return listUsersOp(pageToken, maxResults).callAsync(firebaseApp);
+ }
+
+ private CallableOperation listUsersOp(
+ @Nullable final String pageToken, final int maxResults) {
+ checkNotDestroyed();
+ final FirebaseUserManager userManager = getUserManager();
+ final DefaultUserSource source = new DefaultUserSource(userManager, jsonFactory);
+ final ListUsersPage.Factory factory = new ListUsersPage.Factory(source, maxResults, pageToken);
+ return new CallableOperation() {
+ @Override
+ protected ListUsersPage execute() throws FirebaseAuthException {
+ return factory.create();
+ }
+ };
+ }
+
+ /**
+ * Creates a new user account with the attributes contained in the specified {@link
+ * UserRecord.CreateRequest}.
+ *
+ * @param request A non-null {@link UserRecord.CreateRequest} instance.
+ * @return A {@link UserRecord} instance corresponding to the newly created account.
+ * @throws NullPointerException if the provided request is null.
+ * @throws FirebaseAuthException if an error occurs while creating the user account.
+ */
+ public UserRecord createUser(@NonNull UserRecord.CreateRequest request)
+ throws FirebaseAuthException {
+ return createUserOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #createUser} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link UserRecord.CreateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
+ * instance corresponding to the newly created account. If an error occurs while creating the
+ * user account, the future throws a {@link FirebaseAuthException}.
+ * @throws NullPointerException if the provided request is null.
+ */
+ public ApiFuture createUserAsync(@NonNull UserRecord.CreateRequest request) {
+ return createUserOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation createUserOp(
+ final UserRecord.CreateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "create request must not be null");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserRecord execute() throws FirebaseAuthException {
+ String uid = userManager.createUser(request);
+ return userManager.getUserById(uid);
+ }
+ };
+ }
+
+ /**
+ * Updates an existing user account with the attributes contained in the specified {@link
+ * UserRecord.UpdateRequest}.
+ *
+ * @param request A non-null {@link UserRecord.UpdateRequest} instance.
+ * @return A {@link UserRecord} instance corresponding to the updated user account.
+ * @throws NullPointerException if the provided update request is null.
+ * @throws FirebaseAuthException if an error occurs while updating the user account.
+ */
+ public UserRecord updateUser(@NonNull UserRecord.UpdateRequest request)
+ throws FirebaseAuthException {
+ return updateUserOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #updateUser} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link UserRecord.UpdateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
+ * instance corresponding to the updated user account. If an error occurs while updating the
+ * user account, the future throws a {@link FirebaseAuthException}.
+ */
+ public ApiFuture updateUserAsync(@NonNull UserRecord.UpdateRequest request) {
+ return updateUserOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation updateUserOp(
+ final UserRecord.UpdateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "update request must not be null");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserRecord execute() throws FirebaseAuthException {
+ userManager.updateUser(request, jsonFactory);
+ return userManager.getUserById(request.getUid());
+ }
+ };
+ }
+
+ /**
+ * Sets the specified custom claims on an existing user account. A null claims value removes any
+ * claims currently set on the user account. The claims should serialize into a valid JSON string.
+ * The serialized claims must not be larger than 1000 characters.
+ *
+ * @param uid A user ID string.
+ * @param claims A map of custom claims or null.
+ * @throws FirebaseAuthException If an error occurs while updating custom claims.
+ * @throws IllegalArgumentException If the user ID string is null or empty, or the claims payload
+ * is invalid or too large.
+ */
+ public void setCustomUserClaims(@NonNull String uid, @Nullable Map claims)
+ throws FirebaseAuthException {
+ setCustomUserClaimsOp(uid, claims).call();
+ }
+
+ /**
+ * @deprecated Use {@link #setCustomUserClaims(String, Map)} instead.
+ */
+ public void setCustomClaims(@NonNull String uid, @Nullable Map claims)
+ throws FirebaseAuthException {
+ setCustomUserClaims(uid, claims);
+ }
+
+ /**
+ * Similar to {@link #setCustomUserClaims(String, Map)} but performs the operation asynchronously.
+ *
+ * @param uid A user ID string.
+ * @param claims A map of custom claims or null.
+ * @return An {@code ApiFuture} which will complete successfully when the user account has been
+ * updated. If an error occurs while deleting the user account, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the user ID string is null or empty.
+ */
+ public ApiFuture setCustomUserClaimsAsync(
+ @NonNull String uid, @Nullable Map claims) {
+ return setCustomUserClaimsOp(uid, claims).callAsync(firebaseApp);
+ }
+
+ private CallableOperation setCustomUserClaimsOp(
+ final String uid, final Map claims) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected Void execute() throws FirebaseAuthException {
+ final UserRecord.UpdateRequest request =
+ new UserRecord.UpdateRequest(uid).setCustomClaims(claims);
+ userManager.updateUser(request, jsonFactory);
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Deletes the user identified by the specified user ID.
+ *
+ * @param uid A user ID string.
+ * @throws IllegalArgumentException If the user ID string is null or empty.
+ * @throws FirebaseAuthException If an error occurs while deleting the user.
+ */
+ public void deleteUser(@NonNull String uid) throws FirebaseAuthException {
+ deleteUserOp(uid).call();
+ }
+
+ /**
+ * Similar to {@link #deleteUser(String)} but performs the operation asynchronously.
+ *
+ * @param uid A user ID string.
+ * @return An {@code ApiFuture} which will complete successfully when the specified user account
+ * has been deleted. If an error occurs while deleting the user account, the future throws a
+ * {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the user ID string is null or empty.
+ */
+ public ApiFuture deleteUserAsync(String uid) {
+ return deleteUserOp(uid).callAsync(firebaseApp);
+ }
+
+ private CallableOperation deleteUserOp(final String uid) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected Void execute() throws FirebaseAuthException {
+ userManager.deleteUser(uid);
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Imports the provided list of users into Firebase Auth. At most 1000 users can be imported at a
+ * time. This operation is optimized for bulk imports and will ignore checks on identifier
+ * uniqueness which could result in duplications.
+ *
+ * {@link UserImportOptions} is required to import users with passwords. See {@link
+ * #importUsers(List, UserImportOptions)}.
+ *
+ * @param users A non-empty list of users to be imported. Length must not exceed 1000.
+ * @return A {@link UserImportResult} instance.
+ * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
+ * elements. Or if at least one user specifies a password.
+ * @throws FirebaseAuthException If an error occurs while importing users.
+ */
+ public UserImportResult importUsers(List users) throws FirebaseAuthException {
+ return importUsers(users, null);
+ }
+
+ /**
+ * Imports the provided list of users into Firebase Auth. At most 1000 users can be imported at a
+ * time. This operation is optimized for bulk imports and will ignore checks on identifier
+ * uniqueness which could result in duplications.
+ *
+ * @param users A non-empty list of users to be imported. Length must not exceed 1000.
+ * @param options a {@link UserImportOptions} instance or null. Required when importing users with
+ * passwords.
+ * @return A {@link UserImportResult} instance.
+ * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
+ * elements. Or if at least one user specifies a password, and options is null.
+ * @throws FirebaseAuthException If an error occurs while importing users.
+ */
+ public UserImportResult importUsers(
+ List users, @Nullable UserImportOptions options)
+ throws FirebaseAuthException {
+ return importUsersOp(users, options).call();
+ }
+
+ /**
+ * Similar to {@link #importUsers(List)} but performs the operation asynchronously.
+ *
+ * @param users A non-empty list of users to be imported. Length must not exceed 1000.
+ * @return An {@code ApiFuture} which will complete successfully when the user accounts are
+ * imported. If an error occurs while importing the users, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
+ * elements. Or if at least one user specifies a password.
+ */
+ public ApiFuture importUsersAsync(List users) {
+ return importUsersAsync(users, null);
+ }
+
+ /**
+ * Similar to {@link #importUsers(List, UserImportOptions)} but performs the operation
+ * asynchronously.
+ *
+ * @param users A non-empty list of users to be imported. Length must not exceed 1000.
+ * @param options a {@link UserImportOptions} instance or null. Required when importing users with
+ * passwords.
+ * @return An {@code ApiFuture} which will complete successfully when the user accounts are
+ * imported. If an error occurs while importing the users, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
+ * elements. Or if at least one user specifies a password, and options is null.
+ */
+ public ApiFuture importUsersAsync(
+ List users, @Nullable UserImportOptions options) {
+ return importUsersOp(users, options).callAsync(firebaseApp);
+ }
+
+ private CallableOperation importUsersOp(
+ final List users, final UserImportOptions options) {
+ checkNotDestroyed();
+ final UserImportRequest request = new UserImportRequest(users, options, jsonFactory);
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected UserImportResult execute() throws FirebaseAuthException {
+ return userManager.importUsers(request);
+ }
+ };
+ }
+
+ /**
+ * Gets the user data corresponding to the specified identifiers.
+ *
+ * There are no ordering guarantees; in particular, the nth entry in the users result list is
+ * not guaranteed to correspond to the nth entry in the input parameters list.
+ *
+ *
A maximum of 100 identifiers may be specified. If more than 100 identifiers are
+ * supplied, this method throws an {@link IllegalArgumentException}.
+ *
+ * @param identifiers The identifiers used to indicate which user records should be returned. Must
+ * have 100 or fewer entries.
+ * @return The corresponding user records.
+ * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 100
+ * identifiers are specified.
+ * @throws NullPointerException If the identifiers parameter is null.
+ * @throws FirebaseAuthException If an error occurs while retrieving user data.
+ */
+ public GetUsersResult getUsers(@NonNull Collection identifiers)
+ throws FirebaseAuthException {
+ return getUsersOp(identifiers).call();
+ }
+
+ /**
+ * Gets the user data corresponding to the specified identifiers.
+ *
+ * There are no ordering guarantees; in particular, the nth entry in the users result list is
+ * not guaranteed to correspond to the nth entry in the input parameters list.
+ *
+ *
A maximum of 100 identifiers may be specified. If more than 100 identifiers are
+ * supplied, this method throws an {@link IllegalArgumentException}.
+ *
+ * @param identifiers The identifiers used to indicate which user records should be returned.
+ * Must have 100 or fewer entries.
+ * @return An {@code ApiFuture} that resolves to the corresponding user records.
+ * @throws IllegalArgumentException If any of the identifiers are invalid or if more than 100
+ * identifiers are specified.
+ * @throws NullPointerException If the identifiers parameter is null.
+ */
+ public ApiFuture getUsersAsync(@NonNull Collection identifiers) {
+ return getUsersOp(identifiers).callAsync(firebaseApp);
+ }
+
+ private CallableOperation getUsersOp(
+ @NonNull final Collection identifiers) {
+ checkNotDestroyed();
+ checkNotNull(identifiers, "identifiers must not be null");
+ checkArgument(identifiers.size() <= FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE,
+ "identifiers parameter must have <= " + FirebaseUserManager.MAX_GET_ACCOUNTS_BATCH_SIZE
+ + " entries.");
+
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected GetUsersResult execute() throws FirebaseAuthException {
+ Set users = userManager.getAccountInfo(identifiers);
+ Set notFound = new HashSet<>();
+ for (UserIdentifier id : identifiers) {
+ if (!isUserFound(id, users)) {
+ notFound.add(id);
+ }
+ }
+ return new GetUsersResult(users, notFound);
+ }
+ };
+ }
+
+ private boolean isUserFound(UserIdentifier id, Collection userRecords) {
+ for (UserRecord userRecord : userRecords) {
+ if (id.matches(userRecord)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Deletes the users specified by the given identifiers.
+ *
+ * Deleting a non-existing user does not generate an error (the method is idempotent).
+ * Non-existing users are considered to be successfully deleted and are therefore included in the
+ * DeleteUsersResult.getSuccessCount() value.
+ *
+ *
A maximum of 1000 identifiers may be supplied. If more than 1000 identifiers are
+ * supplied, this method throws an {@link IllegalArgumentException}.
+ *
+ *
This API has a rate limit of 1 QPS. Exceeding the limit may result in a quota exceeded
+ * error. If you want to delete more than 1000 users, we suggest adding a delay to ensure you
+ * don't exceed this limit.
+ *
+ * @param uids The uids of the users to be deleted. Must have <= 1000 entries.
+ * @return The total number of successful/failed deletions, as well as the array of errors that
+ * correspond to the failed deletions.
+ * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000
+ * identifiers are specified.
+ * @throws FirebaseAuthException If an error occurs while deleting users.
+ */
+ public DeleteUsersResult deleteUsers(List uids) throws FirebaseAuthException {
+ return deleteUsersOp(uids).call();
+ }
+
+ /**
+ * Similar to {@link #deleteUsers(List)} but performs the operation asynchronously.
+ *
+ * @param uids The uids of the users to be deleted. Must have <= 1000 entries.
+ * @return An {@code ApiFuture} that resolves to the total number of successful/failed
+ * deletions, as well as the array of errors that correspond to the failed deletions. If an
+ * error occurs while deleting the user account, the future throws a
+ * {@link FirebaseAuthException}.
+ * @throw IllegalArgumentException If any of the identifiers are invalid or if more than 1000
+ * identifiers are specified.
+ */
+ public ApiFuture deleteUsersAsync(List uids) {
+ return deleteUsersOp(uids).callAsync(firebaseApp);
+ }
+
+ private CallableOperation deleteUsersOp(
+ final List uids) {
+ checkNotDestroyed();
+ checkNotNull(uids, "uids must not be null");
+ for (String uid : uids) {
+ UserRecord.checkUid(uid);
+ }
+ checkArgument(uids.size() <= FirebaseUserManager.MAX_DELETE_ACCOUNTS_BATCH_SIZE,
+ "uids parameter must have <= " + FirebaseUserManager.MAX_DELETE_ACCOUNTS_BATCH_SIZE
+ + " entries.");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected DeleteUsersResult execute() throws FirebaseAuthException {
+ return userManager.deleteUsers(uids);
+ }
+ };
+ }
+
+ /**
+ * Generates the out-of-band email action link for password reset flows for the specified email
+ * address.
+ *
+ * @param email The email of the user whose password is to be reset.
+ * @return A password reset link.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws FirebaseAuthException If an error occurs while generating the link.
+ */
+ public String generatePasswordResetLink(@NonNull String email) throws FirebaseAuthException {
+ return generatePasswordResetLink(email, null);
+ }
+
+ /**
+ * Generates the out-of-band email action link for password reset flows for the specified email
+ * address.
+ *
+ * @param email The email of the user whose password is to be reset.
+ * @param settings The action code settings object which defines whether the link is to be handled
+ * by a mobile app and the additional state information to be passed in the deep link.
+ * @return A password reset link.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws FirebaseAuthException If an error occurs while generating the link.
+ */
+ public String generatePasswordResetLink(
+ @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException {
+ return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings).call();
+ }
+
+ /**
+ * Similar to {@link #generatePasswordResetLink(String)} but performs the operation
+ * asynchronously.
+ *
+ * @param email The email of the user whose password is to be reset.
+ * @return An {@code ApiFuture} which will complete successfully with the generated email action
+ * link. If an error occurs while generating the link, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ */
+ public ApiFuture generatePasswordResetLinkAsync(@NonNull String email) {
+ return generatePasswordResetLinkAsync(email, null);
+ }
+
+ /**
+ * Similar to {@link #generatePasswordResetLink(String, ActionCodeSettings)} but performs the
+ * operation asynchronously.
+ *
+ * @param email The email of the user whose password is to be reset.
+ * @param settings The action code settings object which defines whether the link is to be handled
+ * by a mobile app and the additional state information to be passed in the deep link.
+ * @return An {@code ApiFuture} which will complete successfully with the generated email action
+ * link. If an error occurs while generating the link, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ */
+ public ApiFuture generatePasswordResetLinkAsync(
+ @NonNull String email, @Nullable ActionCodeSettings settings) {
+ return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings)
+ .callAsync(firebaseApp);
+ }
+
+ /**
+ * Generates the out-of-band email action link for email verification flows for the specified
+ * email address.
+ *
+ * @param email The email of the user to be verified.
+ * @return An email verification link.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws FirebaseAuthException If an error occurs while generating the link.
+ */
+ public String generateEmailVerificationLink(@NonNull String email) throws FirebaseAuthException {
+ return generateEmailVerificationLink(email, null);
+ }
+
+ /**
+ * Generates the out-of-band email action link for email verification flows for the specified
+ * email address, using the action code settings provided.
+ *
+ * @param email The email of the user to be verified.
+ * @return An email verification link.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws FirebaseAuthException If an error occurs while generating the link.
+ */
+ public String generateEmailVerificationLink(
+ @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException {
+ return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings).call();
+ }
+
+ /**
+ * Similar to {@link #generateEmailVerificationLink(String)} but performs the operation
+ * asynchronously.
+ *
+ * @param email The email of the user to be verified.
+ * @return An {@code ApiFuture} which will complete successfully with the generated email action
+ * link. If an error occurs while generating the link, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ */
+ public ApiFuture generateEmailVerificationLinkAsync(@NonNull String email) {
+ return generateEmailVerificationLinkAsync(email, null);
+ }
+
+ /**
+ * Similar to {@link #generateEmailVerificationLink(String, ActionCodeSettings)} but performs the
+ * operation asynchronously.
+ *
+ * @param email The email of the user to be verified.
+ * @param settings The action code settings object which defines whether the link is to be handled
+ * by a mobile app and the additional state information to be passed in the deep link.
+ * @return An {@code ApiFuture} which will complete successfully with the generated email action
+ * link. If an error occurs while generating the link, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ */
+ public ApiFuture generateEmailVerificationLinkAsync(
+ @NonNull String email, @Nullable ActionCodeSettings settings) {
+ return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings)
+ .callAsync(firebaseApp);
+ }
+
+ /**
+ * Generates the out-of-band email action link for email link sign-in flows, using the action code
+ * settings provided.
+ *
+ * @param email The email of the user signing in.
+ * @param settings The action code settings object which defines whether the link is to be handled
+ * by a mobile app and the additional state information to be passed in the deep link.
+ * @return An email verification link.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws FirebaseAuthException If an error occurs while generating the link.
+ */
+ public String generateSignInWithEmailLink(
+ @NonNull String email, @NonNull ActionCodeSettings settings) throws FirebaseAuthException {
+ return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings).call();
+ }
+
+ /**
+ * Similar to {@link #generateSignInWithEmailLink(String, ActionCodeSettings)} but performs the
+ * operation asynchronously.
+ *
+ * @param email The email of the user signing in.
+ * @param settings The action code settings object which defines whether the link is to be handled
+ * by a mobile app and the additional state information to be passed in the deep link.
+ * @return An {@code ApiFuture} which will complete successfully with the generated email action
+ * link. If an error occurs while generating the link, the future throws a {@link
+ * FirebaseAuthException}.
+ * @throws IllegalArgumentException If the email address is null or empty.
+ * @throws NullPointerException If the settings is null.
+ */
+ public ApiFuture generateSignInWithEmailLinkAsync(
+ String email, @NonNull ActionCodeSettings settings) {
+ return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings)
+ .callAsync(firebaseApp);
+ }
+
+ private CallableOperation generateEmailActionLinkOp(
+ final EmailLinkType type, final String email, final ActionCodeSettings settings) {
+ checkNotDestroyed();
+ checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
+ if (type == EmailLinkType.EMAIL_SIGNIN) {
+ checkNotNull(settings, "ActionCodeSettings must not be null when generating sign-in links");
+ }
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected String execute() throws FirebaseAuthException {
+ return userManager.getEmailActionLink(type, email, settings);
+ }
+ };
+ }
+
+ /**
+ * Creates a new OpenID Connect auth provider config with the attributes contained in the
+ * specified {@link OidcProviderConfig.CreateRequest}.
+ *
+ * @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
+ * @return An {@link OidcProviderConfig} instance corresponding to the newly created provider
+ * config.
+ * @throws NullPointerException if the provided request is null.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not
+ * prefixed with 'oidc.'.
+ * @throws FirebaseAuthException if an error occurs while creating the provider config.
+ */
+ public OidcProviderConfig createOidcProviderConfig(
+ @NonNull OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
+ return createOidcProviderConfigOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #createOidcProviderConfig} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
+ * instance corresponding to the newly created provider config. If an error occurs while
+ * creating the provider config, the future throws a {@link FirebaseAuthException}.
+ * @throws NullPointerException if the provided request is null.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not
+ * prefixed with 'oidc.'.
+ */
+ public ApiFuture createOidcProviderConfigAsync(
+ @NonNull OidcProviderConfig.CreateRequest request) {
+ return createOidcProviderConfigOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation
+ createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "Create request must not be null.");
+ OidcProviderConfig.checkOidcProviderId(request.getProviderId());
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected OidcProviderConfig execute() throws FirebaseAuthException {
+ return userManager.createOidcProviderConfig(request);
+ }
+ };
+ }
+
+ /**
+ * Updates an existing OpenID Connect auth provider config with the attributes contained in the
+ * specified {@link OidcProviderConfig.UpdateRequest}.
+ *
+ * @param request A non-null {@link OidcProviderConfig.UpdateRequest} instance.
+ * @return A {@link OidcProviderConfig} instance corresponding to the updated provider config.
+ * @throws NullPointerException if the provided update request is null.
+ * @throws IllegalArgumentException If the provided update request is invalid.
+ * @throws FirebaseAuthException if an error occurs while updating the provider config.
+ */
+ public OidcProviderConfig updateOidcProviderConfig(
+ @NonNull OidcProviderConfig.UpdateRequest request) throws FirebaseAuthException {
+ return updateOidcProviderConfigOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #updateOidcProviderConfig} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link OidcProviderConfig.UpdateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
+ * instance corresponding to the updated provider config. If an error occurs while updating
+ * the provider config, the future throws a {@link FirebaseAuthException}.
+ * @throws NullPointerException if the provided update request is null.
+ * @throws IllegalArgumentException If the provided update request is invalid.
+ */
+ public ApiFuture updateOidcProviderConfigAsync(
+ @NonNull OidcProviderConfig.UpdateRequest request) {
+ return updateOidcProviderConfigOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation updateOidcProviderConfigOp(
+ final OidcProviderConfig.UpdateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "Update request must not be null.");
+ checkArgument(!request.getProperties().isEmpty(),
+ "Update request must have at least one property set.");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected OidcProviderConfig execute() throws FirebaseAuthException {
+ return userManager.updateOidcProviderConfig(request);
+ }
+ };
+ }
+
+ /**
+ * Gets the OpenID Connect auth provider corresponding to the specified provider ID.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@link OidcProviderConfig} instance.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'oidc'.
+ * @throws FirebaseAuthException If an error occurs while retrieving the provider config.
+ */
+ public OidcProviderConfig getOidcProviderConfig(@NonNull String providerId)
+ throws FirebaseAuthException {
+ return getOidcProviderConfigOp(providerId).call();
+ }
+
+ /**
+ * Similar to {@link #getOidcProviderConfig(String)} but performs the operation asynchronously.
+ * Page size is limited to 100 provider configs.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@code ApiFuture} which will complete successfully with an
+ * {@link OidcProviderConfig} instance. If an error occurs while retrieving the provider
+ * config or if the specified provider ID does not exist, the future throws a
+ * {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not
+ * prefixed with 'oidc.'.
+ */
+ public ApiFuture getOidcProviderConfigAsync(@NonNull String providerId) {
+ return getOidcProviderConfigOp(providerId).callAsync(firebaseApp);
+ }
+
+ private CallableOperation
+ getOidcProviderConfigOp(final String providerId) {
+ checkNotDestroyed();
+ OidcProviderConfig.checkOidcProviderId(providerId);
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected OidcProviderConfig execute() throws FirebaseAuthException {
+ return userManager.getOidcProviderConfig(providerId);
+ }
+ };
+ }
+
+ /**
+ * Gets a page of OpenID Connect auth provider configs starting from the specified
+ * {@code pageToken}. Page size is limited to 100 provider configs.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @return A {@link ListProviderConfigsPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty
+ * @throws FirebaseAuthException If an error occurs while retrieving provider config data.
+ */
+ public ListProviderConfigsPage listOidcProviderConfigs(
+ @Nullable String pageToken) throws FirebaseAuthException {
+ int maxResults = FirebaseUserManager.MAX_LIST_PROVIDER_CONFIGS_RESULTS;
+ return listOidcProviderConfigsOp(pageToken, maxResults).call();
+ }
+
+ /**
+ * Gets a page of OpenID Connect auth provider configs starting from the specified
+ * {@code pageToken}.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @param maxResults Maximum number of provider configs to include in the returned page. This may
+ * not exceed 100.
+ * @return A {@link ListProviderConfigsPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ * @throws FirebaseAuthException If an error occurs while retrieving provider config data.
+ */
+ public ListProviderConfigsPage listOidcProviderConfigs(
+ @Nullable String pageToken, int maxResults) throws FirebaseAuthException {
+ return listOidcProviderConfigsOp(pageToken, maxResults).call();
+ }
+
+ /**
+ * Similar to {@link #listOidcProviderConfigs(String)} but performs the operation asynchronously.
+ * Page size is limited to 100 provider configs.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @return An {@code ApiFuture} which will complete successfully with a
+ * {@link ListProviderConfigsPage} instance. If an error occurs while retrieving provider
+ * config data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty.
+ */
+ public ApiFuture> listOidcProviderConfigsAsync(
+ @Nullable String pageToken) {
+ int maxResults = FirebaseUserManager.MAX_LIST_PROVIDER_CONFIGS_RESULTS;
+ return listOidcProviderConfigsAsync(pageToken, maxResults);
+ }
+
+ /**
+ * Similar to {@link #listOidcProviderConfigs(String, int)} but performs the operation
+ * asynchronously.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @param maxResults Maximum number of provider configs to include in the returned page. This may
+ * not exceed 100.
+ * @return An {@code ApiFuture} which will complete successfully with a
+ * {@link ListProviderConfigsPage} instance. If an error occurs while retrieving provider
+ * config data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ */
+ public ApiFuture> listOidcProviderConfigsAsync(
+ @Nullable String pageToken,
+ int maxResults) {
+ return listOidcProviderConfigsOp(pageToken, maxResults).callAsync(firebaseApp);
+ }
+
+ private CallableOperation, FirebaseAuthException>
+ listOidcProviderConfigsOp(@Nullable final String pageToken, final int maxResults) {
+ checkNotDestroyed();
+ final FirebaseUserManager userManager = getUserManager();
+ final DefaultOidcProviderConfigSource source = new DefaultOidcProviderConfigSource(userManager);
+ final ListProviderConfigsPage.Factory factory =
+ new ListProviderConfigsPage.Factory(source, maxResults, pageToken);
+ return
+ new CallableOperation, FirebaseAuthException>() {
+ @Override
+ protected ListProviderConfigsPage execute()
+ throws FirebaseAuthException {
+ return factory.create();
+ }
+ };
+ }
+
+ /**
+ * Deletes the OpenID Connect auth provider config identified by the specified provider ID.
+ *
+ * @param providerId A provider ID string.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'oidc'.
+ * @throws FirebaseAuthException If an error occurs while deleting the provider config.
+ */
+ public void deleteOidcProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
+ deleteOidcProviderConfigOp(providerId).call();
+ }
+
+ /**
+ * Similar to {@link #deleteOidcProviderConfig} but performs the operation asynchronously.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@code ApiFuture} which will complete successfully when the specified provider
+ * config has been deleted. If an error occurs while deleting the provider config, the future
+ * throws a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with "oidc.".
+ */
+ public ApiFuture deleteOidcProviderConfigAsync(String providerId) {
+ return deleteOidcProviderConfigOp(providerId).callAsync(firebaseApp);
+ }
+
+ private CallableOperation deleteOidcProviderConfigOp(
+ final String providerId) {
+ checkNotDestroyed();
+ OidcProviderConfig.checkOidcProviderId(providerId);
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected Void execute() throws FirebaseAuthException {
+ userManager.deleteOidcProviderConfig(providerId);
+ return null;
+ }
+ };
+ }
+
+ /**
+ * Creates a new SAML Auth provider config with the attributes contained in the specified
+ * {@link SamlProviderConfig.CreateRequest}.
+ *
+ * @param request A non-null {@link SamlProviderConfig.CreateRequest} instance.
+ * @return An {@link SamlProviderConfig} instance corresponding to the newly created provider
+ * config.
+ * @throws NullPointerException if the provided request is null.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'saml'.
+ * @throws FirebaseAuthException if an error occurs while creating the provider config.
+ */
+ public SamlProviderConfig createSamlProviderConfig(
+ @NonNull SamlProviderConfig.CreateRequest request) throws FirebaseAuthException {
+ return createSamlProviderConfigOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #createSamlProviderConfig} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link SamlProviderConfig.CreateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link SamlProviderConfig}
+ * instance corresponding to the newly created provider config. If an error occurs while
+ * creating the provider config, the future throws a {@link FirebaseAuthException}.
+ * @throws NullPointerException if the provided request is null.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'saml'.
+ */
+ public ApiFuture createSamlProviderConfigAsync(
+ @NonNull SamlProviderConfig.CreateRequest request) {
+ return createSamlProviderConfigOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation
+ createSamlProviderConfigOp(final SamlProviderConfig.CreateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "Create request must not be null.");
+ SamlProviderConfig.checkSamlProviderId(request.getProviderId());
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected SamlProviderConfig execute() throws FirebaseAuthException {
+ return userManager.createSamlProviderConfig(request);
+ }
+ };
+ }
+
+ /**
+ * Updates an existing SAML Auth provider config with the attributes contained in the specified
+ * {@link SamlProviderConfig.UpdateRequest}.
+ *
+ * @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
+ * @return A {@link SamlProviderConfig} instance corresponding to the updated provider config.
+ * @throws NullPointerException if the provided update request is null.
+ * @throws IllegalArgumentException If the provided update request is invalid.
+ * @throws FirebaseAuthException if an error occurs while updating the provider config.
+ */
+ public SamlProviderConfig updateSamlProviderConfig(
+ @NonNull SamlProviderConfig.UpdateRequest request) throws FirebaseAuthException {
+ return updateSamlProviderConfigOp(request).call();
+ }
+
+ /**
+ * Similar to {@link #updateSamlProviderConfig} but performs the operation asynchronously.
+ *
+ * @param request A non-null {@link SamlProviderConfig.UpdateRequest} instance.
+ * @return An {@code ApiFuture} which will complete successfully with a {@link SamlProviderConfig}
+ * instance corresponding to the updated provider config. If an error occurs while updating
+ * the provider config, the future throws a {@link FirebaseAuthException}.
+ * @throws NullPointerException if the provided update request is null.
+ * @throws IllegalArgumentException If the provided update request is invalid.
+ */
+ public ApiFuture updateSamlProviderConfigAsync(
+ @NonNull SamlProviderConfig.UpdateRequest request) {
+ return updateSamlProviderConfigOp(request).callAsync(firebaseApp);
+ }
+
+ private CallableOperation updateSamlProviderConfigOp(
+ final SamlProviderConfig.UpdateRequest request) {
+ checkNotDestroyed();
+ checkNotNull(request, "Update request must not be null.");
+ checkArgument(!request.getProperties().isEmpty(),
+ "Update request must have at least one property set.");
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected SamlProviderConfig execute() throws FirebaseAuthException {
+ return userManager.updateSamlProviderConfig(request);
+ }
+ };
+ }
+
+ /**
+ * Gets the SAML Auth provider config corresponding to the specified provider ID.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@link SamlProviderConfig} instance.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'saml'.
+ * @throws FirebaseAuthException If an error occurs while retrieving the provider config.
+ */
+ public SamlProviderConfig getSamlProviderConfig(@NonNull String providerId)
+ throws FirebaseAuthException {
+ return getSamlProviderConfigOp(providerId).call();
+ }
+
+ /**
+ * Similar to {@link #getSamlProviderConfig(String)} but performs the operation asynchronously.
+ * Page size is limited to 100 provider configs.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@code ApiFuture} which will complete successfully with an
+ * {@link SamlProviderConfig} instance. If an error occurs while retrieving the provider
+ * config or if the specified provider ID does not exist, the future throws a
+ * {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with 'saml'.
+ */
+ public ApiFuture getSamlProviderConfigAsync(@NonNull String providerId) {
+ return getSamlProviderConfigOp(providerId).callAsync(firebaseApp);
+ }
+
+ private CallableOperation
+ getSamlProviderConfigOp(final String providerId) {
+ checkNotDestroyed();
+ SamlProviderConfig.checkSamlProviderId(providerId);
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected SamlProviderConfig execute() throws FirebaseAuthException {
+ return userManager.getSamlProviderConfig(providerId);
+ }
+ };
+ }
+
+ /**
+ * Gets a page of SAML Auth provider configs starting from the specified {@code pageToken}. Page
+ * size is limited to 100 provider configs.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @return A {@link ListProviderConfigsPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty.
+ * @throws FirebaseAuthException If an error occurs while retrieving provider config data.
+ */
+ public ListProviderConfigsPage listSamlProviderConfigs(
+ @Nullable String pageToken) throws FirebaseAuthException {
+ return listSamlProviderConfigs(
+ pageToken,
+ FirebaseUserManager.MAX_LIST_PROVIDER_CONFIGS_RESULTS);
+ }
+
+ /**
+ * Gets a page of SAML Auth provider configs starting from the specified {@code pageToken}.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @param maxResults Maximum number of provider configs to include in the returned page. This may
+ * not exceed 100.
+ * @return A {@link ListProviderConfigsPage} instance.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ * @throws FirebaseAuthException If an error occurs while retrieving provider config data.
+ */
+ public ListProviderConfigsPage listSamlProviderConfigs(
+ @Nullable String pageToken, int maxResults) throws FirebaseAuthException {
+ return listSamlProviderConfigsOp(pageToken, maxResults).call();
+ }
+
+ /**
+ * Similar to {@link #listSamlProviderConfigs(String)} but performs the operation asynchronously.
+ * Page size is limited to 100 provider configs.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @return An {@code ApiFuture} which will complete successfully with a
+ * {@link ListProviderConfigsPage} instance. If an error occurs while retrieving provider
+ * config data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty.
+ */
+ public ApiFuture> listSamlProviderConfigsAsync(
+ @Nullable String pageToken) {
+ int maxResults = FirebaseUserManager.MAX_LIST_PROVIDER_CONFIGS_RESULTS;
+ return listSamlProviderConfigsAsync(pageToken, maxResults);
+ }
+
+ /**
+ * Similar to {@link #listSamlProviderConfigs(String, int)} but performs the operation
+ * asynchronously.
+ *
+ * @param pageToken A non-empty page token string, or null to retrieve the first page of provider
+ * configs.
+ * @param maxResults Maximum number of provider configs to include in the returned page. This may
+ * not exceed 100.
+ * @return An {@code ApiFuture} which will complete successfully with a
+ * {@link ListProviderConfigsPage} instance. If an error occurs while retrieving provider
+ * config data, the future throws an exception.
+ * @throws IllegalArgumentException If the specified page token is empty, or max results value is
+ * invalid.
+ */
+ public ApiFuture> listSamlProviderConfigsAsync(
+ @Nullable String pageToken,
+ int maxResults) {
+ return listSamlProviderConfigsOp(pageToken, maxResults).callAsync(firebaseApp);
+ }
+
+ private CallableOperation, FirebaseAuthException>
+ listSamlProviderConfigsOp(@Nullable final String pageToken, final int maxResults) {
+ checkNotDestroyed();
+ final FirebaseUserManager userManager = getUserManager();
+ final DefaultSamlProviderConfigSource source = new DefaultSamlProviderConfigSource(userManager);
+ final ListProviderConfigsPage.Factory factory =
+ new ListProviderConfigsPage.Factory(source, maxResults, pageToken);
+ return
+ new CallableOperation, FirebaseAuthException>() {
+ @Override
+ protected ListProviderConfigsPage execute()
+ throws FirebaseAuthException {
+ return factory.create();
+ }
+ };
+ }
+
+ /**
+ * Deletes the SAML Auth provider config identified by the specified provider ID.
+ *
+ * @param providerId A provider ID string.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with "saml.".
+ * @throws FirebaseAuthException If an error occurs while deleting the provider config.
+ */
+ public void deleteSamlProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
+ deleteSamlProviderConfigOp(providerId).call();
+ }
+
+ /**
+ * Similar to {@link #deleteSamlProviderConfig} but performs the operation asynchronously.
+ *
+ * @param providerId A provider ID string.
+ * @return An {@code ApiFuture} which will complete successfully when the specified provider
+ * config has been deleted. If an error occurs while deleting the provider config, the future
+ * throws a {@link FirebaseAuthException}.
+ * @throws IllegalArgumentException If the provider ID string is null or empty, or is not prefixed
+ * with "saml.".
+ */
+ public ApiFuture deleteSamlProviderConfigAsync(String providerId) {
+ return deleteSamlProviderConfigOp(providerId).callAsync(firebaseApp);
+ }
+
+ private CallableOperation deleteSamlProviderConfigOp(
+ final String providerId) {
+ checkNotDestroyed();
+ SamlProviderConfig.checkSamlProviderId(providerId);
+ final FirebaseUserManager userManager = getUserManager();
+ return new CallableOperation() {
+ @Override
+ protected Void execute() throws FirebaseAuthException {
+ userManager.deleteSamlProviderConfig(providerId);
+ return null;
+ }
+ };
+ }
+
+ FirebaseApp getFirebaseApp() {
+ return this.firebaseApp;
+ }
+
+ FirebaseTokenVerifier getCookieVerifier() {
+ return this.cookieVerifier.get();
+ }
+
+ FirebaseUserManager getUserManager() {
+ return this.userManager.get();
+ }
+
+ protected Supplier threadSafeMemoize(final Supplier supplier) {
+ return Suppliers.memoize(
+ new Supplier() {
+ @Override
+ public T get() {
+ checkNotNull(supplier);
+ synchronized (lock) {
+ checkNotDestroyed();
+ return supplier.get();
+ }
+ }
+ });
+ }
+
+ void checkNotDestroyed() {
+ synchronized (lock) {
+ checkState(
+ !destroyed.get(),
+ "FirebaseAuth instance is no longer alive. This happens when "
+ + "the parent FirebaseApp instance has been deleted.");
+ }
+ }
+
+ final void destroy() {
+ synchronized (lock) {
+ doDestroy();
+ destroyed.set(true);
+ }
+ }
+
+ /** Performs any additional required clean up. */
+ protected abstract void doDestroy();
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ static class Builder {
+ protected FirebaseApp firebaseApp;
+ private Supplier tokenFactory;
+ private Supplier extends FirebaseTokenVerifier> idTokenVerifier;
+ private Supplier extends FirebaseTokenVerifier> cookieVerifier;
+ private Supplier userManager;
+
+ private Builder() {}
+
+ Builder setFirebaseApp(FirebaseApp firebaseApp) {
+ this.firebaseApp = firebaseApp;
+ return this;
+ }
+
+ Builder setTokenFactory(Supplier tokenFactory) {
+ this.tokenFactory = tokenFactory;
+ return this;
+ }
+
+ Builder setIdTokenVerifier(Supplier extends FirebaseTokenVerifier> idTokenVerifier) {
+ this.idTokenVerifier = idTokenVerifier;
+ return this;
+ }
+
+ Builder setCookieVerifier(Supplier extends FirebaseTokenVerifier> cookieVerifier) {
+ this.cookieVerifier = cookieVerifier;
+ return this;
+ }
+
+ Builder setUserManager(Supplier userManager) {
+ this.userManager = userManager;
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/google/firebase/auth/AuthErrorCode.java b/src/main/java/com/google/firebase/auth/AuthErrorCode.java
index 463c24318..c1625187f 100644
--- a/src/main/java/com/google/firebase/auth/AuthErrorCode.java
+++ b/src/main/java/com/google/firebase/auth/AuthErrorCode.java
@@ -71,6 +71,8 @@ public enum AuthErrorCode {
*/
REVOKED_SESSION_COOKIE,
+ TENANT_ID_MISMATCH,
+
/**
* A user already exists with the provided UID.
*/
diff --git a/src/main/java/com/google/firebase/auth/DeleteUsersResult.java b/src/main/java/com/google/firebase/auth/DeleteUsersResult.java
new file mode 100644
index 000000000..e8ca7dba5
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/DeleteUsersResult.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import com.google.common.collect.ImmutableList;
+import com.google.firebase.auth.internal.BatchDeleteResponse;
+import com.google.firebase.internal.NonNull;
+import java.util.List;
+
+/**
+ * Represents the result of the {@link FirebaseAuth#deleteUsersAsync(List)} API.
+ */
+public final class DeleteUsersResult {
+
+ private final int successCount;
+ private final List errors;
+
+ DeleteUsersResult(int users, BatchDeleteResponse response) {
+ ImmutableList.Builder errorsBuilder = ImmutableList.builder();
+ List responseErrors = response.getErrors();
+ if (responseErrors != null) {
+ checkArgument(users >= responseErrors.size());
+ for (BatchDeleteResponse.ErrorInfo error : responseErrors) {
+ errorsBuilder.add(new ErrorInfo(error.getIndex(), error.getMessage()));
+ }
+ }
+ errors = errorsBuilder.build();
+ successCount = users - errors.size();
+ }
+
+ /**
+ * Returns the number of users that were deleted successfully (possibly zero). Users that did not
+ * exist prior to calling {@link FirebaseAuth#deleteUsersAsync(List)} are considered to be
+ * successfully deleted.
+ */
+ public int getSuccessCount() {
+ return successCount;
+ }
+
+ /**
+ * Returns the number of users that failed to be deleted (possibly zero).
+ */
+ public int getFailureCount() {
+ return errors.size();
+ }
+
+ /**
+ * A list of {@link ErrorInfo} instances describing the errors that were encountered during
+ * the deletion. Length of this list is equal to the return value of
+ * {@link #getFailureCount()}.
+ *
+ * @return A non-null list (possibly empty).
+ */
+ @NonNull
+ public List getErrors() {
+ return errors;
+ }
+}
diff --git a/src/main/java/com/google/firebase/auth/EmailIdentifier.java b/src/main/java/com/google/firebase/auth/EmailIdentifier.java
new file mode 100644
index 000000000..8e729c220
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/EmailIdentifier.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth;
+
+import com.google.firebase.auth.internal.GetAccountInfoRequest;
+import com.google.firebase.internal.NonNull;
+
+/**
+ * Used for looking up an account by email.
+ *
+ * @see {FirebaseAuth#getUsers}
+ */
+public final class EmailIdentifier extends UserIdentifier {
+ private final String email;
+
+ public EmailIdentifier(@NonNull String email) {
+ UserRecord.checkEmail(email);
+ this.email = email;
+ }
+
+ @Override
+ public String toString() {
+ return "EmailIdentifier(" + email + ")";
+ }
+
+ @Override
+ void populate(@NonNull GetAccountInfoRequest payload) {
+ payload.addEmail(email);
+ }
+
+ @Override
+ boolean matches(@NonNull UserRecord userRecord) {
+ return email.equals(userRecord.getEmail());
+ }
+}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseAuth.java b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
index 7cd738ec5..f44bd2343 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 Google Inc.
+ * Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,32 +18,19 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
-import static com.google.common.base.Preconditions.checkState;
-import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.Clock;
import com.google.api.core.ApiFuture;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
-import com.google.common.base.Suppliers;
import com.google.firebase.FirebaseApp;
import com.google.firebase.ImplFirebaseTrampolines;
-import com.google.firebase.auth.FirebaseUserManager.EmailLinkType;
-import com.google.firebase.auth.FirebaseUserManager.UserImportRequest;
-import com.google.firebase.auth.ListUsersPage.DefaultUserSource;
-import com.google.firebase.auth.ListUsersPage.PageFactory;
-import com.google.firebase.auth.UserRecord.CreateRequest;
-import com.google.firebase.auth.UserRecord.UpdateRequest;
import com.google.firebase.auth.internal.FirebaseTokenFactory;
+import com.google.firebase.auth.multitenancy.TenantManager;
import com.google.firebase.internal.CallableOperation;
import com.google.firebase.internal.FirebaseService;
import com.google.firebase.internal.NonNull;
-import com.google.firebase.internal.Nullable;
-
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
/**
* This class is the entry point for all server-side Firebase Authentication actions.
@@ -53,27 +40,24 @@
* custom tokens for use by client-side code, verifying Firebase ID Tokens received from clients, or
* creating new FirebaseApp instances that are scoped to a particular authentication UID.
*/
-public class FirebaseAuth {
+public final class FirebaseAuth extends AbstractFirebaseAuth {
private static final String SERVICE_ID = FirebaseAuth.class.getName();
- private final Object lock = new Object();
- private final AtomicBoolean destroyed = new AtomicBoolean(false);
+ private final Supplier tenantManager;
- private final FirebaseApp firebaseApp;
- private final Supplier tokenFactory;
- private final Supplier extends FirebaseTokenVerifier> idTokenVerifier;
- private final Supplier extends FirebaseTokenVerifier> cookieVerifier;
- private final Supplier extends FirebaseUserManager> userManager;
- private final JsonFactory jsonFactory;
+ FirebaseAuth(final Builder builder) {
+ super(builder);
+ tenantManager = threadSafeMemoize(new Supplier() {
+ @Override
+ public TenantManager get() {
+ return new TenantManager(builder.firebaseApp);
+ }
+ });
+ }
- private FirebaseAuth(Builder builder) {
- this.firebaseApp = checkNotNull(builder.firebaseApp);
- this.tokenFactory = threadSafeMemoize(builder.tokenFactory);
- this.idTokenVerifier = threadSafeMemoize(builder.idTokenVerifier);
- this.cookieVerifier = threadSafeMemoize(builder.cookieVerifier);
- this.userManager = threadSafeMemoize(builder.userManager);
- this.jsonFactory = firebaseApp.getOptions().getJsonFactory();
+ public TenantManager getTenantManager() {
+ return tenantManager.get();
}
/**
@@ -92,8 +76,8 @@ public static FirebaseAuth getInstance() {
* @return A FirebaseAuth instance.
*/
public static synchronized FirebaseAuth getInstance(FirebaseApp app) {
- FirebaseAuthService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID,
- FirebaseAuthService.class);
+ FirebaseAuthService service =
+ ImplFirebaseTrampolines.getService(app, SERVICE_ID, FirebaseAuthService.class);
if (service == null) {
service = ImplFirebaseTrampolines.addService(app, new FirebaseAuthService(app));
}
@@ -101,8 +85,8 @@ public static synchronized FirebaseAuth getInstance(FirebaseApp app) {
}
/**
- * Creates a new Firebase session cookie from the given ID token and options. The returned JWT
- * can be set as a server-side session cookie with a custom cookie policy.
+ * Creates a new Firebase session cookie from the given ID token and options. The returned JWT can
+ * be set as a server-side session cookie with a custom cookie policy.
*
* @param idToken The Firebase ID token to exchange for a session cookie.
* @param options Additional options required to create the cookie.
@@ -110,8 +94,8 @@ public static synchronized FirebaseAuth getInstance(FirebaseApp app) {
* @throws IllegalArgumentException If the ID token is null or empty, or if options is null.
* @throws FirebaseAuthException If an error occurs while generating the session cookie.
*/
- public String createSessionCookie(
- @NonNull String idToken, @NonNull SessionCookieOptions options) throws FirebaseAuthException {
+ public String createSessionCookie(@NonNull String idToken, @NonNull SessionCookieOptions options)
+ throws FirebaseAuthException {
return createSessionCookieOp(idToken, options).call();
}
@@ -121,14 +105,14 @@ public String createSessionCookie(
*
* @param idToken The Firebase ID token to exchange for a session cookie.
* @param options Additional options required to create the cookie.
- * @return An {@code ApiFuture} which will complete successfully with a session cookie string.
- * If an error occurs while generating the cookie or if the specified ID token is invalid,
- * the future throws a {@link FirebaseAuthException}.
+ * @return An {@code ApiFuture} which will complete successfully with a session cookie string. If
+ * an error occurs while generating the cookie or if the specified ID token is invalid, the
+ * future throws a {@link FirebaseAuthException}.
* @throws IllegalArgumentException If the ID token is null or empty, or if options is null.
*/
public ApiFuture createSessionCookieAsync(
@NonNull String idToken, @NonNull SessionCookieOptions options) {
- return createSessionCookieOp(idToken, options).callAsync(firebaseApp);
+ return createSessionCookieOp(idToken, options).callAsync(getFirebaseApp());
}
private CallableOperation createSessionCookieOp(
@@ -151,8 +135,8 @@ protected String execute() throws FirebaseAuthException {
* If verified successfully, returns a parsed version of the cookie from which the UID and the
* other claims can be read. If the cookie is invalid, throws a {@link FirebaseAuthException}.
*
- *
This method does not check whether the cookie has been revoked. See
- * {@link #verifySessionCookie(String, boolean)}.
+ *
This method does not check whether the cookie has been revoked. See {@link
+ * #verifySessionCookie(String, boolean)}.
*
* @param cookie A Firebase session cookie string to verify and parse.
* @return A {@link FirebaseToken} representing the verified and decoded cookie.
@@ -164,20 +148,18 @@ public FirebaseToken verifySessionCookie(String cookie) throws FirebaseAuthExcep
/**
* Parses and verifies a Firebase session cookie.
*
- *
If {@code checkRevoked} is true, additionally verifies that the cookie has not been
- * revoked.
+ *
If {@code checkRevoked} is true, additionally verifies that the cookie has not been revoked.
*
*
If verified successfully, returns a parsed version of the cookie from which the UID and the
- * other claims can be read. If the cookie is invalid or has been revoked while
- * {@code checkRevoked} is true, throws a {@link FirebaseAuthException}.
+ * other claims can be read. If the cookie is invalid or has been revoked while {@code
+ * checkRevoked} is true, throws a {@link FirebaseAuthException}.
*
* @param cookie A Firebase session cookie string to verify and parse.
- * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly
- * revoked.
+ * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly revoked.
* @return A {@link FirebaseToken} representing the verified and decoded cookie.
*/
- public FirebaseToken verifySessionCookie(
- String cookie, boolean checkRevoked) throws FirebaseAuthException {
+ public FirebaseToken verifySessionCookie(String cookie, boolean checkRevoked)
+ throws FirebaseAuthException {
return verifySessionCookieOp(cookie, checkRevoked).call();
}
@@ -197,13 +179,12 @@ public ApiFuture verifySessionCookieAsync(String cookie) {
* asynchronously.
*
* @param cookie A Firebase session cookie string to verify and parse.
- * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly
- * revoked.
+ * @param checkRevoked A boolean indicating whether to check if the cookie was explicitly revoked.
* @return An {@code ApiFuture} which will complete successfully with the parsed cookie, or
* unsuccessfully with the failure Exception.
*/
public ApiFuture verifySessionCookieAsync(String cookie, boolean checkRevoked) {
- return verifySessionCookieOp(cookie, checkRevoked).callAsync(firebaseApp);
+ return verifySessionCookieOp(cookie, checkRevoked).callAsync(getFirebaseApp());
}
private CallableOperation verifySessionCookieOp(
@@ -221,7 +202,7 @@ public FirebaseToken execute() throws FirebaseAuthException {
@VisibleForTesting
FirebaseTokenVerifier getSessionCookieVerifier(boolean checkRevoked) {
- FirebaseTokenVerifier verifier = cookieVerifier.get();
+ FirebaseTokenVerifier verifier = getCookieVerifier();
if (checkRevoked) {
FirebaseUserManager userManager = getUserManager();
verifier = RevocationCheckDecorator.decorateSessionCookieVerifier(verifier, userManager);
@@ -229,962 +210,41 @@ FirebaseTokenVerifier getSessionCookieVerifier(boolean checkRevoked) {
return verifier;
}
- /**
- * Creates a Firebase custom token for the given UID. This token can then be sent back to a client
- * application to be used with the
- * signInWithCustomToken
- * authentication API.
- *
- * {@link FirebaseApp} must have been initialized with service account credentials to use
- * call this method.
- *
- * @param uid The UID to store in the token. This identifies the user to other Firebase services
- * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
- * @return A Firebase custom token string.
- * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
- * been initialized with service account credentials.
- * @throws FirebaseAuthException If an error occurs while generating the custom token.
- */
- public String createCustomToken(@NonNull String uid) throws FirebaseAuthException {
- return createCustomToken(uid, null);
- }
-
- /**
- * Creates a Firebase custom token for the given UID, containing the specified additional
- * claims. This token can then be sent back to a client application to be used with the
- * signInWithCustomToken
- * authentication API.
- *
- *
This method attempts to generate a token using:
- *
- * - the private key of {@link FirebaseApp}'s service account credentials, if provided at
- * initialization.
- *
- the IAM service
- * if a service account email was specified via
- * {@link com.google.firebase.FirebaseOptions.Builder#setServiceAccountId(String)}.
- *
- the App Identity
- * service if the code is deployed in the Google App Engine standard environment.
- *
- the
- * local Metadata server if the code is deployed in a different GCP-managed environment
- * like Google Compute Engine.
- *
- *
- * This method throws an exception when all the above fail.
- *
- * @param uid The UID to store in the token. This identifies the user to other Firebase services
- * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
- * @param developerClaims Additional claims to be stored in the token (and made available to
- * security rules in Database, Storage, etc.). These must be able to be serialized to JSON
- * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.)
- * @return A Firebase custom token string.
- * @throws IllegalArgumentException If the specified uid is null or empty.
- * @throws IllegalStateException If the SDK fails to discover a viable approach for signing
- * tokens.
- * @throws FirebaseAuthException If an error occurs while generating the custom token.
- */
- public String createCustomToken(@NonNull String uid,
- @Nullable Map developerClaims) throws FirebaseAuthException {
- return createCustomTokenOp(uid, developerClaims).call();
- }
-
- /**
- * Similar to {@link #createCustomToken(String)} but performs the operation asynchronously.
- *
- * @param uid The UID to store in the token. This identifies the user to other Firebase services
- * (Realtime Database, Firebase Auth, etc.). Should be less than 128 characters.
- * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom
- * token, or unsuccessfully with the failure Exception.
- * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
- * been initialized with service account credentials.
- */
- public ApiFuture createCustomTokenAsync(@NonNull String uid) {
- return createCustomTokenAsync(uid, null);
- }
-
- /**
- * Similar to {@link #createCustomToken(String, Map)} but performs the operation
- * asynchronously.
- *
- * @param uid The UID to store in the token. This identifies the user to other Firebase services
- * (Realtime Database, Storage, etc.). Should be less than 128 characters.
- * @param developerClaims Additional claims to be stored in the token (and made available to
- * security rules in Database, Storage, etc.). These must be able to be serialized to JSON
- * (e.g. contain only Maps, Arrays, Strings, Booleans, Numbers, etc.)
- * @return An {@code ApiFuture} which will complete successfully with the created Firebase custom
- * token, or unsuccessfully with the failure Exception.
- * @throws IllegalArgumentException If the specified uid is null or empty, or if the app has not
- * been initialized with service account credentials.
- */
- public ApiFuture createCustomTokenAsync(
- @NonNull String uid, @Nullable Map developerClaims) {
- return createCustomTokenOp(uid, developerClaims).callAsync(firebaseApp);
- }
-
- private CallableOperation createCustomTokenOp(
- final String uid, final Map developerClaims) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
- final FirebaseTokenFactory tokenFactory = this.tokenFactory.get();
- return new CallableOperation() {
- @Override
- public String execute() throws FirebaseAuthException {
- return tokenFactory.createSignedCustomAuthTokenForUser(uid, developerClaims);
- }
- };
- }
-
- /**
- * Parses and verifies a Firebase ID Token.
- *
- * A Firebase application can identify itself to a trusted backend server by sending its
- * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication
- * client) with its requests. The backend server can then use the {@code verifyIdToken()} method
- * to verify that the token is valid. This method ensures that the token is correctly signed,
- * has not expired, and it was issued to the Firebase project associated with this
- * {@link FirebaseAuth} instance.
- *
- *
This method does not check whether a token has been revoked. Use
- * {@link #verifyIdToken(String, boolean)} to perform an additional revocation check.
- *
- * @param token A Firebase ID token string to parse and verify.
- * @return A {@link FirebaseToken} representing the verified and decoded token.
- * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
- * instance does not have a project ID associated with it.
- * @throws FirebaseAuthException If an error occurs while parsing or validating the token.
- */
- public FirebaseToken verifyIdToken(@NonNull String token) throws FirebaseAuthException {
- return verifyIdToken(token, false);
- }
-
- /**
- * Parses and verifies a Firebase ID Token.
- *
- *
A Firebase application can identify itself to a trusted backend server by sending its
- * Firebase ID Token (accessible via the {@code getToken} API in the Firebase Authentication
- * client) with its requests. The backend server can then use the {@code verifyIdToken()} method
- * to verify that the token is valid. This method ensures that the token is correctly signed,
- * has not expired, and it was issued to the Firebase project associated with this
- * {@link FirebaseAuth} instance.
- *
- *
If {@code checkRevoked} is set to true, this method performs an additional check to see
- * if the ID token has been revoked since it was issues. This requires making an additional
- * remote API call.
- *
- * @param token A Firebase ID token string to parse and verify.
- * @param checkRevoked A boolean denoting whether to check if the tokens were revoked.
- * @return A {@link FirebaseToken} representing the verified and decoded token.
- * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
- * instance does not have a project ID associated with it.
- * @throws FirebaseAuthException If an error occurs while parsing or validating the token.
- */
- public FirebaseToken verifyIdToken(
- @NonNull String token, boolean checkRevoked) throws FirebaseAuthException {
- return verifyIdTokenOp(token, checkRevoked).call();
- }
-
- /**
- * Similar to {@link #verifyIdToken(String)} but performs the operation asynchronously.
- *
- * @param token A Firebase ID Token to verify and parse.
- * @return An {@code ApiFuture} which will complete successfully with the parsed token, or
- * unsuccessfully with a {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
- * instance does not have a project ID associated with it.
- */
- public ApiFuture verifyIdTokenAsync(@NonNull String token) {
- return verifyIdTokenAsync(token, false);
- }
-
- /**
- * Similar to {@link #verifyIdToken(String, boolean)} but performs the operation asynchronously.
- *
- * @param token A Firebase ID Token to verify and parse.
- * @param checkRevoked A boolean denoting whether to check if the tokens were revoked.
- * @return An {@code ApiFuture} which will complete successfully with the parsed token, or
- * unsuccessfully with a {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the token is null, empty, or if the {@link FirebaseApp}
- * instance does not have a project ID associated with it.
- */
- public ApiFuture verifyIdTokenAsync(@NonNull String token, boolean checkRevoked) {
- return verifyIdTokenOp(token, checkRevoked).callAsync(firebaseApp);
- }
-
- private CallableOperation verifyIdTokenOp(
- final String token, final boolean checkRevoked) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(token), "ID token must not be null or empty");
- final FirebaseTokenVerifier verifier = getIdTokenVerifier(checkRevoked);
- return new CallableOperation() {
- @Override
- protected FirebaseToken execute() throws FirebaseAuthException {
- return verifier.verifyToken(token);
- }
- };
- }
-
- @VisibleForTesting
- FirebaseTokenVerifier getIdTokenVerifier(boolean checkRevoked) {
- FirebaseTokenVerifier verifier = idTokenVerifier.get();
- if (checkRevoked) {
- FirebaseUserManager userManager = getUserManager();
- verifier = RevocationCheckDecorator.decorateIdTokenVerifier(verifier, userManager);
- }
- return verifier;
- }
-
- /**
- * Revokes all refresh tokens for the specified user.
- *
- * Updates the user's tokensValidAfterTimestamp to the current UTC time expressed in
- * milliseconds since the epoch and truncated to 1 second accuracy. It is important that the
- * server on which this is called has its clock set correctly and synchronized.
- *
- *
While this will revoke all sessions for a specified user and disable any new ID tokens for
- * existing sessions from getting minted, existing ID tokens may remain active until their
- * natural expiration (one hour).
- * To verify that ID tokens are revoked, use {@link #verifyIdTokenAsync(String, boolean)}.
- *
- * @param uid The user id for which tokens are revoked.
- * @throws IllegalArgumentException If the user ID is null or empty.
- * @throws FirebaseAuthException If an error occurs while revoking tokens.
- */
- public void revokeRefreshTokens(@NonNull String uid) throws FirebaseAuthException {
- revokeRefreshTokensOp(uid).call();
- }
-
- /**
- * Similar to {@link #revokeRefreshTokens(String)} but performs the operation asynchronously.
- *
- * @param uid The user id for which tokens are revoked.
- * @return An {@code ApiFuture} which will complete successfully or fail with a
- * {@link FirebaseAuthException} in the event of an error.
- * @throws IllegalArgumentException If the user ID is null or empty.
- */
- public ApiFuture revokeRefreshTokensAsync(@NonNull String uid) {
- return revokeRefreshTokensOp(uid).callAsync(firebaseApp);
- }
-
- private CallableOperation revokeRefreshTokensOp(final String uid) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected Void execute() throws FirebaseAuthException {
- int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
- UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds);
- userManager.updateUser(request, jsonFactory);
- return null;
- }
- };
- }
-
- /**
- * Gets the user data corresponding to the specified user ID.
- *
- * @param uid A user ID string.
- * @return A {@link UserRecord} instance.
- * @throws IllegalArgumentException If the user ID string is null or empty.
- * @throws FirebaseAuthException If an error occurs while retrieving user data.
- */
- public UserRecord getUser(@NonNull String uid) throws FirebaseAuthException {
- return getUserOp(uid).call();
- }
-
- /**
- * Similar to {@link #getUser(String)} but performs the operation asynchronously.
- *
- * @param uid A user ID string.
- * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
- * instance. If an error occurs while retrieving user data or if the specified user ID does
- * not exist, the future throws a {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the user ID string is null or empty.
- */
- public ApiFuture getUserAsync(@NonNull String uid) {
- return getUserOp(uid).callAsync(firebaseApp);
- }
-
- private CallableOperation getUserOp(final String uid) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserRecord execute() throws FirebaseAuthException {
- return userManager.getUserById(uid);
- }
- };
- }
-
- /**
- * Gets the user data corresponding to the specified user email.
- *
- * @param email A user email address string.
- * @return A {@link UserRecord} instance.
- * @throws IllegalArgumentException If the email is null or empty.
- * @throws FirebaseAuthException If an error occurs while retrieving user data.
- */
- public UserRecord getUserByEmail(@NonNull String email) throws FirebaseAuthException {
- return getUserByEmailOp(email).call();
- }
-
- /**
- * Similar to {@link #getUserByEmail(String)} but performs the operation asynchronously.
- *
- * @param email A user email address string.
- * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
- * instance. If an error occurs while retrieving user data or if the email address does not
- * correspond to a user, the future throws a {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email is null or empty.
- */
- public ApiFuture getUserByEmailAsync(@NonNull String email) {
- return getUserByEmailOp(email).callAsync(firebaseApp);
- }
-
- private CallableOperation getUserByEmailOp(
- final String email) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserRecord execute() throws FirebaseAuthException {
- return userManager.getUserByEmail(email);
- }
- };
- }
-
- /**
- * Gets the user data corresponding to the specified user phone number.
- *
- * @param phoneNumber A user phone number string.
- * @return A a {@link UserRecord} instance.
- * @throws IllegalArgumentException If the phone number is null or empty.
- * @throws FirebaseAuthException If an error occurs while retrieving user data.
- */
- public UserRecord getUserByPhoneNumber(@NonNull String phoneNumber) throws FirebaseAuthException {
- return getUserByPhoneNumberOp(phoneNumber).call();
- }
-
- /**
- * Gets the user data corresponding to the specified user phone number.
- *
- * @param phoneNumber A user phone number string.
- * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
- * instance. If an error occurs while retrieving user data or if the phone number does not
- * correspond to a user, the future throws a {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the phone number is null or empty.
- */
- public ApiFuture getUserByPhoneNumberAsync(@NonNull String phoneNumber) {
- return getUserByPhoneNumberOp(phoneNumber).callAsync(firebaseApp);
- }
-
- private CallableOperation getUserByPhoneNumberOp(
- final String phoneNumber) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(phoneNumber), "phone number must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserRecord execute() throws FirebaseAuthException {
- return userManager.getUserByPhoneNumber(phoneNumber);
- }
- };
- }
-
- /**
- * Gets a page of users starting from the specified {@code pageToken}. Page size will be
- * limited to 1000 users.
- *
- * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
- * @return A {@link ListUsersPage} instance.
- * @throws IllegalArgumentException If the specified page token is empty.
- * @throws FirebaseAuthException If an error occurs while retrieving user data.
- */
- public ListUsersPage listUsers(@Nullable String pageToken) throws FirebaseAuthException {
- return listUsers(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS);
- }
-
- /**
- * Gets a page of users starting from the specified {@code pageToken}.
- *
- * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
- * @param maxResults Maximum number of users to include in the returned page. This may not
- * exceed 1000.
- * @return A {@link ListUsersPage} instance.
- * @throws IllegalArgumentException If the specified page token is empty, or max results value
- * is invalid.
- * @throws FirebaseAuthException If an error occurs while retrieving user data.
- */
- public ListUsersPage listUsers(
- @Nullable String pageToken, int maxResults) throws FirebaseAuthException {
- return listUsersOp(pageToken, maxResults).call();
- }
-
- /**
- * Similar to {@link #listUsers(String)} but performs the operation asynchronously.
- *
- * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
- * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage}
- * instance. If an error occurs while retrieving user data, the future throws an exception.
- * @throws IllegalArgumentException If the specified page token is empty.
- */
- public ApiFuture listUsersAsync(@Nullable String pageToken) {
- return listUsersAsync(pageToken, FirebaseUserManager.MAX_LIST_USERS_RESULTS);
- }
-
- /**
- * Similar to {@link #listUsers(String, int)} but performs the operation asynchronously.
- *
- * @param pageToken A non-empty page token string, or null to retrieve the first page of users.
- * @param maxResults Maximum number of users to include in the returned page. This may not
- * exceed 1000.
- * @return An {@code ApiFuture} which will complete successfully with a {@link ListUsersPage}
- * instance. If an error occurs while retrieving user data, the future throws an exception.
- * @throws IllegalArgumentException If the specified page token is empty, or max results value
- * is invalid.
- */
- public ApiFuture listUsersAsync(@Nullable String pageToken, int maxResults) {
- return listUsersOp(pageToken, maxResults).callAsync(firebaseApp);
- }
-
- private CallableOperation listUsersOp(
- @Nullable final String pageToken, final int maxResults) {
- checkNotDestroyed();
- final FirebaseUserManager userManager = getUserManager();
- final PageFactory factory = new PageFactory(
- new DefaultUserSource(userManager, jsonFactory), maxResults, pageToken);
- return new CallableOperation() {
- @Override
- protected ListUsersPage execute() throws FirebaseAuthException {
- return factory.create();
- }
- };
- }
-
- /**
- * Creates a new user account with the attributes contained in the specified
- * {@link CreateRequest}.
- *
- * @param request A non-null {@link CreateRequest} instance.
- * @return A {@link UserRecord} instance corresponding to the newly created account.
- * @throws NullPointerException if the provided request is null.
- * @throws FirebaseAuthException if an error occurs while creating the user account.
- */
- public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException {
- return createUserOp(request).call();
- }
-
- /**
- * Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously.
- *
- * @param request A non-null {@link CreateRequest} instance.
- * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
- * instance corresponding to the newly created account. If an error occurs while creating the
- * user account, the future throws a {@link FirebaseAuthException}.
- * @throws NullPointerException if the provided request is null.
- */
- public ApiFuture createUserAsync(@NonNull CreateRequest request) {
- return createUserOp(request).callAsync(firebaseApp);
- }
-
- private CallableOperation createUserOp(
- final CreateRequest request) {
- checkNotDestroyed();
- checkNotNull(request, "create request must not be null");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserRecord execute() throws FirebaseAuthException {
- String uid = userManager.createUser(request);
- return userManager.getUserById(uid);
- }
- };
- }
-
- /**
- * Updates an existing user account with the attributes contained in the specified
- * {@link UpdateRequest}.
- *
- * @param request A non-null {@link UpdateRequest} instance.
- * @return A {@link UserRecord} instance corresponding to the updated user account.
- * account, the task fails with a {@link FirebaseAuthException}.
- * @throws NullPointerException if the provided update request is null.
- * @throws FirebaseAuthException if an error occurs while updating the user account.
- */
- public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException {
- return updateUserOp(request).call();
- }
-
- /**
- * Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously.
- *
- * @param request A non-null {@link UpdateRequest} instance.
- * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
- * instance corresponding to the updated user account. If an error occurs while updating the
- * user account, the future throws a {@link FirebaseAuthException}.
- */
- public ApiFuture updateUserAsync(@NonNull UpdateRequest request) {
- return updateUserOp(request).callAsync(firebaseApp);
- }
-
- private CallableOperation updateUserOp(
- final UpdateRequest request) {
- checkNotDestroyed();
- checkNotNull(request, "update request must not be null");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserRecord execute() throws FirebaseAuthException {
- userManager.updateUser(request, jsonFactory);
- return userManager.getUserById(request.getUid());
- }
- };
- }
-
- /**
- * Sets the specified custom claims on an existing user account. A null claims value removes
- * any claims currently set on the user account. The claims should serialize into a valid JSON
- * string. The serialized claims must not be larger than 1000 characters.
- *
- * @param uid A user ID string.
- * @param claims A map of custom claims or null.
- * @throws FirebaseAuthException If an error occurs while updating custom claims.
- * @throws IllegalArgumentException If the user ID string is null or empty, or the claims
- * payload is invalid or too large.
- */
- public void setCustomUserClaims(@NonNull String uid,
- @Nullable Map claims) throws FirebaseAuthException {
- setCustomUserClaimsOp(uid, claims).call();
- }
-
- /**
- * Similar to {@link #setCustomUserClaims(String, Map)} but performs the operation asynchronously.
- *
- * @param uid A user ID string.
- * @param claims A map of custom claims or null.
- * @return An {@code ApiFuture} which will complete successfully when the user account has been
- * updated. If an error occurs while deleting the user account, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the user ID string is null or empty.
- */
- public ApiFuture setCustomUserClaimsAsync(
- @NonNull String uid, @Nullable Map claims) {
- return setCustomUserClaimsOp(uid, claims).callAsync(firebaseApp);
- }
-
- private CallableOperation setCustomUserClaimsOp(
- final String uid, final Map claims) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected Void execute() throws FirebaseAuthException {
- final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims);
- userManager.updateUser(request, jsonFactory);
- return null;
- }
- };
- }
-
- /**
- * Deletes the user identified by the specified user ID.
- *
- * @param uid A user ID string.
- * @throws IllegalArgumentException If the user ID string is null or empty.
- * @throws FirebaseAuthException If an error occurs while deleting the user.
- */
- public void deleteUser(@NonNull String uid) throws FirebaseAuthException {
- deleteUserOp(uid).call();
- }
-
- /**
- * Similar to {@link #deleteUser(String)} but performs the operation asynchronously.
- *
- * @param uid A user ID string.
- * @return An {@code ApiFuture} which will complete successfully when the specified user account
- * has been deleted. If an error occurs while deleting the user account, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the user ID string is null or empty.
- */
- public ApiFuture deleteUserAsync(String uid) {
- return deleteUserOp(uid).callAsync(firebaseApp);
- }
-
- private CallableOperation deleteUserOp(final String uid) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(uid), "uid must not be null or empty");
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected Void execute() throws FirebaseAuthException {
- userManager.deleteUser(uid);
- return null;
- }
- };
- }
-
- /**
- * Imports the provided list of users into Firebase Auth. At most 1000 users can be imported at a
- * time. This operation is optimized for bulk imports and will ignore checks on identifier
- * uniqueness which could result in duplications.
- *
- * {@link UserImportOptions} is required to import users with passwords. See
- * {@link #importUsers(List, UserImportOptions)}.
- *
- * @param users A non-empty list of users to be imported. Length must not exceed 1000.
- * @return A {@link UserImportResult} instance.
- * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
- * elements. Or if at least one user specifies a password.
- * @throws FirebaseAuthException If an error occurs while importing users.
- */
- public UserImportResult importUsers(List users) throws FirebaseAuthException {
- return importUsers(users, null);
- }
-
- /**
- * Imports the provided list of users into Firebase Auth. At most 1000 users can be imported at a
- * time. This operation is optimized for bulk imports and will ignore checks on identifier
- * uniqueness which could result in duplications.
- *
- * @param users A non-empty list of users to be imported. Length must not exceed 1000.
- * @param options a {@link UserImportOptions} instance or null. Required when importing users
- * with passwords.
- * @return A {@link UserImportResult} instance.
- * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
- * elements. Or if at least one user specifies a password, and options is null.
- * @throws FirebaseAuthException If an error occurs while importing users.
- */
- public UserImportResult importUsers(List users,
- @Nullable UserImportOptions options) throws FirebaseAuthException {
- return importUsersOp(users, options).call();
- }
-
- /**
- * Similar to {@link #importUsers(List)} but performs the operation asynchronously.
- *
- * @param users A non-empty list of users to be imported. Length must not exceed 1000.
- * @return An {@code ApiFuture} which will complete successfully when the user accounts are
- * imported. If an error occurs while importing the users, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
- * elements. Or if at least one user specifies a password.
- */
- public ApiFuture importUsersAsync(List users) {
- return importUsersAsync(users, null);
- }
-
- /**
- * Similar to {@link #importUsers(List, UserImportOptions)} but performs the operation
- * asynchronously.
- *
- * @param users A non-empty list of users to be imported. Length must not exceed 1000.
- * @param options a {@link UserImportOptions} instance or null. Required when importing users
- * with passwords.
- * @return An {@code ApiFuture} which will complete successfully when the user accounts are
- * imported. If an error occurs while importing the users, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the users list is null, empty or has more than 1000
- * elements. Or if at least one user specifies a password, and options is null.
- */
- public ApiFuture importUsersAsync(List users,
- @Nullable UserImportOptions options) {
- return importUsersOp(users, options).callAsync(firebaseApp);
- }
-
- private CallableOperation importUsersOp(
- final List users, final UserImportOptions options) {
- checkNotDestroyed();
- final UserImportRequest request = new UserImportRequest(users, options, jsonFactory);
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected UserImportResult execute() throws FirebaseAuthException {
- return userManager.importUsers(request);
- }
- };
- }
-
- /**
- * Generates the out-of-band email action link for password reset flows for the specified email
- * address.
- *
- * @param email The email of the user whose password is to be reset.
- * @return A password reset link.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws FirebaseAuthException If an error occurs while generating the link.
- */
- public String generatePasswordResetLink(@NonNull String email) throws FirebaseAuthException {
- return generatePasswordResetLink(email, null);
- }
-
- /**
- * Generates the out-of-band email action link for password reset flows for the specified email
- * address.
- *
- * @param email The email of the user whose password is to be reset.
- * @param settings The action code settings object which defines whether
- * the link is to be handled by a mobile app and the additional state information to be
- * passed in the deep link.
- * @return A password reset link.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws FirebaseAuthException If an error occurs while generating the link.
- */
- public String generatePasswordResetLink(
- @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException {
- return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings).call();
- }
-
- /**
- * Similar to {@link #generatePasswordResetLink(String)} but performs the operation
- * asynchronously.
- *
- * @param email The email of the user whose password is to be reset.
- * @return An {@code ApiFuture} which will complete successfully with the generated email action
- * link. If an error occurs while generating the link, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email address is null or empty.
- */
- public ApiFuture generatePasswordResetLinkAsync(@NonNull String email) {
- return generatePasswordResetLinkAsync(email, null);
- }
-
- /**
- * Similar to {@link #generatePasswordResetLink(String, ActionCodeSettings)} but performs the
- * operation asynchronously.
- *
- * @param email The email of the user whose password is to be reset.
- * @param settings The action code settings object which defines whether
- * the link is to be handled by a mobile app and the additional state information to be
- * passed in the deep link.
- * @return An {@code ApiFuture} which will complete successfully with the generated email action
- * link. If an error occurs while generating the link, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email address is null or empty.
- */
- public ApiFuture generatePasswordResetLinkAsync(
- @NonNull String email, @Nullable ActionCodeSettings settings) {
- return generateEmailActionLinkOp(EmailLinkType.PASSWORD_RESET, email, settings)
- .callAsync(firebaseApp);
- }
-
- /**
- * Generates the out-of-band email action link for email verification flows for the specified
- * email address.
- *
- * @param email The email of the user to be verified.
- * @return An email verification link.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws FirebaseAuthException If an error occurs while generating the link.
- */
- public String generateEmailVerificationLink(@NonNull String email) throws FirebaseAuthException {
- return generateEmailVerificationLink(email, null);
- }
-
- /**
- * Generates the out-of-band email action link for email verification flows for the specified
- * email address, using the action code settings provided.
- *
- * @param email The email of the user to be verified.
- * @return An email verification link.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws FirebaseAuthException If an error occurs while generating the link.
- */
- public String generateEmailVerificationLink(
- @NonNull String email, @Nullable ActionCodeSettings settings) throws FirebaseAuthException {
- return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings).call();
- }
-
- /**
- * Similar to {@link #generateEmailVerificationLink(String)} but performs the
- * operation asynchronously.
- *
- * @param email The email of the user to be verified.
- * @return An {@code ApiFuture} which will complete successfully with the generated email action
- * link. If an error occurs while generating the link, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email address is null or empty.
- */
- public ApiFuture generateEmailVerificationLinkAsync(@NonNull String email) {
- return generateEmailVerificationLinkAsync(email, null);
- }
-
- /**
- * Similar to {@link #generateEmailVerificationLink(String, ActionCodeSettings)} but performs the
- * operation asynchronously.
- *
- * @param email The email of the user to be verified.
- * @param settings The action code settings object which defines whether
- * the link is to be handled by a mobile app and the additional state information to be
- * passed in the deep link.
- * @return An {@code ApiFuture} which will complete successfully with the generated email action
- * link. If an error occurs while generating the link, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email address is null or empty.
- */
- public ApiFuture generateEmailVerificationLinkAsync(
- @NonNull String email, @Nullable ActionCodeSettings settings) {
- return generateEmailActionLinkOp(EmailLinkType.VERIFY_EMAIL, email, settings)
- .callAsync(firebaseApp);
- }
-
- /**
- * Generates the out-of-band email action link for email link sign-in flows, using the action
- * code settings provided.
- *
- * @param email The email of the user signing in.
- * @param settings The action code settings object which defines whether
- * the link is to be handled by a mobile app and the additional state information to be
- * passed in the deep link.
- * @return An email verification link.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws FirebaseAuthException If an error occurs while generating the link.
- */
- public String generateSignInWithEmailLink(
- @NonNull String email, @NonNull ActionCodeSettings settings) throws FirebaseAuthException {
- return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings).call();
- }
-
- /**
- * Similar to {@link #generateSignInWithEmailLink(String, ActionCodeSettings)} but performs the
- * operation asynchronously.
- *
- * @param email The email of the user signing in.
- * @param settings The action code settings object which defines whether
- * the link is to be handled by a mobile app and the additional state information to be
- * passed in the deep link.
- * @return An {@code ApiFuture} which will complete successfully with the generated email action
- * link. If an error occurs while generating the link, the future throws a
- * {@link FirebaseAuthException}.
- * @throws IllegalArgumentException If the email address is null or empty.
- * @throws NullPointerException If the settings is null.
- */
- public ApiFuture generateSignInWithEmailLinkAsync(
- String email, @NonNull ActionCodeSettings settings) {
- return generateEmailActionLinkOp(EmailLinkType.EMAIL_SIGNIN, email, settings)
- .callAsync(firebaseApp);
- }
-
- @VisibleForTesting
- FirebaseUserManager getUserManager() {
- return this.userManager.get();
- }
-
- private CallableOperation generateEmailActionLinkOp(
- final EmailLinkType type, final String email, final ActionCodeSettings settings) {
- checkNotDestroyed();
- checkArgument(!Strings.isNullOrEmpty(email), "email must not be null or empty");
- if (type == EmailLinkType.EMAIL_SIGNIN) {
- checkNotNull(settings, "ActionCodeSettings must not be null when generating sign-in links");
- }
- final FirebaseUserManager userManager = getUserManager();
- return new CallableOperation() {
- @Override
- protected String execute() throws FirebaseAuthException {
- return userManager.getEmailActionLink(type, email, settings);
- }
- };
- }
-
- private Supplier threadSafeMemoize(final Supplier supplier) {
- return Suppliers.memoize(new Supplier() {
- @Override
- public T get() {
- checkNotNull(supplier);
- synchronized (lock) {
- checkNotDestroyed();
- return supplier.get();
- }
- }
- });
- }
-
- private void checkNotDestroyed() {
- synchronized (lock) {
- checkState(!destroyed.get(), "FirebaseAuth instance is no longer alive. This happens when "
- + "the parent FirebaseApp instance has been deleted.");
- }
- }
-
- private void destroy() {
- synchronized (lock) {
- destroyed.set(true);
- }
- }
+ @Override
+ protected void doDestroy() { }
private static FirebaseAuth fromApp(final FirebaseApp app) {
- return FirebaseAuth.builder()
- .setFirebaseApp(app)
- .setTokenFactory(new Supplier() {
- @Override
- public FirebaseTokenFactory get() {
- return FirebaseTokenUtils.createTokenFactory(app, Clock.SYSTEM);
- }
- })
- .setIdTokenVerifier(new Supplier() {
- @Override
- public FirebaseTokenVerifier get() {
- return FirebaseTokenUtils.createIdTokenVerifier(app, Clock.SYSTEM);
- }
- })
- .setCookieVerifier(new Supplier() {
- @Override
- public FirebaseTokenVerifier get() {
- return FirebaseTokenUtils.createSessionCookieVerifier(app, Clock.SYSTEM);
- }
- })
- .setUserManager(new Supplier() {
- @Override
- public FirebaseUserManager get() {
- return new FirebaseUserManager(app);
- }
- })
- .build();
- }
-
- @VisibleForTesting
- static Builder builder() {
- return new Builder();
- }
-
- static class Builder {
- private FirebaseApp firebaseApp;
- private Supplier tokenFactory;
- private Supplier extends FirebaseTokenVerifier> idTokenVerifier;
- private Supplier extends FirebaseTokenVerifier> cookieVerifier;
- private Supplier userManager;
-
- private Builder() { }
-
- Builder setFirebaseApp(FirebaseApp firebaseApp) {
- this.firebaseApp = firebaseApp;
- return this;
- }
-
- Builder setTokenFactory(Supplier tokenFactory) {
- this.tokenFactory = tokenFactory;
- return this;
- }
-
- Builder setIdTokenVerifier(Supplier extends FirebaseTokenVerifier> idTokenVerifier) {
- this.idTokenVerifier = idTokenVerifier;
- return this;
- }
-
- Builder setCookieVerifier(Supplier extends FirebaseTokenVerifier> cookieVerifier) {
- this.cookieVerifier = cookieVerifier;
- return this;
- }
-
- Builder setUserManager(Supplier userManager) {
- this.userManager = userManager;
- return this;
- }
-
- FirebaseAuth build() {
- return new FirebaseAuth(this);
- }
+ return new FirebaseAuth(
+ AbstractFirebaseAuth.builder()
+ .setFirebaseApp(app)
+ .setTokenFactory(
+ new Supplier() {
+ @Override
+ public FirebaseTokenFactory get() {
+ return FirebaseTokenUtils.createTokenFactory(app, Clock.SYSTEM);
+ }
+ })
+ .setIdTokenVerifier(
+ new Supplier() {
+ @Override
+ public FirebaseTokenVerifier get() {
+ return FirebaseTokenUtils.createIdTokenVerifier(app, Clock.SYSTEM);
+ }
+ })
+ .setCookieVerifier(
+ new Supplier() {
+ @Override
+ public FirebaseTokenVerifier get() {
+ return FirebaseTokenUtils.createSessionCookieVerifier(app, Clock.SYSTEM);
+ }
+ })
+ .setUserManager(
+ new Supplier() {
+ @Override
+ public FirebaseUserManager get() {
+ return FirebaseUserManager.builder().setFirebaseApp(app).build();
+ }
+ }));
}
private static class FirebaseAuthService extends FirebaseService {
diff --git a/src/main/java/com/google/firebase/auth/FirebaseToken.java b/src/main/java/com/google/firebase/auth/FirebaseToken.java
index 3d7b0b254..835fa41c9 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseToken.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseToken.java
@@ -42,6 +42,15 @@ public String getUid() {
return (String) claims.get("sub");
}
+ /** Returns the tenant ID for the this token. */
+ public String getTenantId() {
+ Map firebase = (Map) claims.get("firebase");
+ if (firebase == null) {
+ return null;
+ }
+ return (String) firebase.get("tenant");
+ }
+
/** Returns the Issuer for the this token. */
public String getIssuer() {
return (String) claims.get("iss");
@@ -57,14 +66,14 @@ public String getPicture() {
return (String) claims.get("picture");
}
- /**
+ /**
* Returns the e-mail address for this user, or {@code null} if it's unavailable.
*/
public String getEmail() {
return (String) claims.get("email");
}
- /**
+ /**
* Indicates if the email address returned by {@link #getEmail()} has been verified as good.
*/
public boolean isEmailVerified() {
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
index cff1a6cfd..2718513c4 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenUtils.java
@@ -30,6 +30,7 @@
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.auth.internal.CryptoSigners;
import com.google.firebase.auth.internal.FirebaseTokenFactory;
+import com.google.firebase.internal.Nullable;
import java.io.IOException;
@@ -52,11 +53,17 @@ final class FirebaseTokenUtils {
private FirebaseTokenUtils() { }
static FirebaseTokenFactory createTokenFactory(FirebaseApp firebaseApp, Clock clock) {
+ return createTokenFactory(firebaseApp, clock, null);
+ }
+
+ static FirebaseTokenFactory createTokenFactory(
+ FirebaseApp firebaseApp, Clock clock, @Nullable String tenantId) {
try {
return new FirebaseTokenFactory(
firebaseApp.getOptions().getJsonFactory(),
clock,
- CryptoSigners.getCryptoSigner(firebaseApp));
+ CryptoSigners.getCryptoSigner(firebaseApp),
+ tenantId);
} catch (IOException e) {
throw new IllegalStateException(
"Failed to initialize FirebaseTokenFactory. Make sure to initialize the SDK "
@@ -68,6 +75,11 @@ static FirebaseTokenFactory createTokenFactory(FirebaseApp firebaseApp, Clock cl
}
static FirebaseTokenVerifierImpl createIdTokenVerifier(FirebaseApp app, Clock clock) {
+ return createIdTokenVerifier(app, clock, null);
+ }
+
+ static FirebaseTokenVerifierImpl createIdTokenVerifier(
+ FirebaseApp app, Clock clock, @Nullable String tenantId) {
String projectId = ImplFirebaseTrampolines.getProjectId(app);
checkState(!Strings.isNullOrEmpty(projectId),
"Must initialize FirebaseApp with a project ID to call verifyIdToken()");
@@ -84,6 +96,7 @@ static FirebaseTokenVerifierImpl createIdTokenVerifier(FirebaseApp app, Clock cl
.setIdTokenVerifier(idTokenVerifier)
.setInvalidTokenErrorCode(AuthErrorCode.INVALID_ID_TOKEN)
.setExpiredTokenErrorCode(AuthErrorCode.EXPIRED_ID_TOKEN)
+ .setTenantId(tenantId)
.build();
}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
index b34cfb516..273ec1532 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseTokenVerifierImpl.java
@@ -29,6 +29,7 @@
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.firebase.ErrorCode;
+import com.google.firebase.internal.Nullable;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.GeneralSecurityException;
@@ -55,6 +56,7 @@ final class FirebaseTokenVerifierImpl implements FirebaseTokenVerifier {
private final String docUrl;
private final AuthErrorCode invalidTokenErrorCode;
private final AuthErrorCode expiredTokenErrorCode;
+ private final String tenantId;
private FirebaseTokenVerifierImpl(Builder builder) {
this.jsonFactory = checkNotNull(builder.jsonFactory);
@@ -69,6 +71,7 @@ private FirebaseTokenVerifierImpl(Builder builder) {
this.docUrl = builder.docUrl;
this.invalidTokenErrorCode = checkNotNull(builder.invalidTokenErrorCode);
this.expiredTokenErrorCode = checkNotNull(builder.expiredTokenErrorCode);
+ this.tenantId = Strings.nullToEmpty(builder.tenantId);
}
/**
@@ -94,7 +97,9 @@ public FirebaseToken verifyToken(String token) throws FirebaseAuthException {
IdToken idToken = parse(token);
checkContents(idToken);
checkSignature(idToken);
- return new FirebaseToken(idToken.getPayload());
+ FirebaseToken firebaseToken = new FirebaseToken(idToken.getPayload());
+ checkTenantId(firebaseToken);
+ return firebaseToken;
}
GooglePublicKeysManager getPublicKeysManager() {
@@ -317,6 +322,17 @@ private boolean containsLegacyUidField(IdToken.Payload payload) {
return false;
}
+ private void checkTenantId(final FirebaseToken firebaseToken) throws FirebaseAuthException {
+ String tokenTenantId = Strings.nullToEmpty(firebaseToken.getTenantId());
+ if (!this.tenantId.equals(tokenTenantId)) {
+ String message = String.format(
+ "The tenant ID ('%s') of the token did not match the expected value ('%s')",
+ tokenTenantId,
+ tenantId);
+ throw newException(message, AuthErrorCode.TENANT_ID_MISMATCH);
+ }
+ }
+
static Builder builder() {
return new Builder();
}
@@ -331,6 +347,7 @@ static final class Builder {
private String docUrl;
private AuthErrorCode invalidTokenErrorCode;
private AuthErrorCode expiredTokenErrorCode;
+ private String tenantId;
private Builder() { }
@@ -364,16 +381,21 @@ Builder setDocUrl(String docUrl) {
return this;
}
- public Builder setInvalidTokenErrorCode(AuthErrorCode invalidTokenErrorCode) {
+ Builder setInvalidTokenErrorCode(AuthErrorCode invalidTokenErrorCode) {
this.invalidTokenErrorCode = invalidTokenErrorCode;
return this;
}
- public Builder setExpiredTokenErrorCode(AuthErrorCode expiredTokenErrorCode) {
+ Builder setExpiredTokenErrorCode(AuthErrorCode expiredTokenErrorCode) {
this.expiredTokenErrorCode = expiredTokenErrorCode;
return this;
}
+ Builder setTenantId(@Nullable String tenantId) {
+ this.tenantId = tenantId;
+ return this;
+ }
+
FirebaseTokenVerifierImpl build() {
return new FirebaseTokenVerifierImpl(this);
}
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index 650f9d019..55b854e76 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -20,15 +20,14 @@
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpContent;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponseInterceptor;
-import com.google.api.client.http.json.JsonHttpContent;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.util.Key;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -36,20 +35,23 @@
import com.google.firebase.FirebaseApp;
import com.google.firebase.ImplFirebaseTrampolines;
import com.google.firebase.IncomingHttpResponse;
-import com.google.firebase.auth.UserRecord.CreateRequest;
-import com.google.firebase.auth.UserRecord.UpdateRequest;
+import com.google.firebase.auth.internal.AuthHttpClient;
+import com.google.firebase.auth.internal.BatchDeleteResponse;
import com.google.firebase.auth.internal.DownloadAccountResponse;
+import com.google.firebase.auth.internal.GetAccountInfoRequest;
import com.google.firebase.auth.internal.GetAccountInfoResponse;
+import com.google.firebase.auth.internal.ListOidcProviderConfigsResponse;
+import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse;
import com.google.firebase.auth.internal.UploadAccountResponse;
import com.google.firebase.internal.ApiClientUtils;
-import com.google.firebase.internal.ErrorHandlingHttpClient;
-import com.google.firebase.internal.HttpRequestInfo;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;
-import com.google.firebase.internal.SdkUtils;
+import java.util.Collection;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* FirebaseUserManager provides methods for interacting with the Google Identity Toolkit via its
@@ -60,6 +62,9 @@
*/
class FirebaseUserManager {
+ static final int MAX_LIST_PROVIDER_CONFIGS_RESULTS = 100;
+ static final int MAX_GET_ACCOUNTS_BATCH_SIZE = 100;
+ static final int MAX_DELETE_ACCOUNTS_BATCH_SIZE = 1000;
static final int MAX_LIST_USERS_RESULTS = 1000;
static final int MAX_IMPORT_USERS = 1000;
@@ -68,45 +73,41 @@ class FirebaseUserManager {
"iss", "jti", "nbf", "nonce", "sub", "firebase");
private static final String ID_TOOLKIT_URL =
- "https://identitytoolkit.googleapis.com/v1/projects/%s";
- private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
- private static final String CLIENT_VERSION = "Java/Admin/" + SdkUtils.getVersion();
+ "https://identitytoolkit.googleapis.com/%s/projects/%s";
- private final String baseUrl;
+ private final String userMgtBaseUrl;
+ private final String idpConfigMgtBaseUrl;
private final JsonFactory jsonFactory;
- private final ErrorHandlingHttpClient httpClient;
+ private final AuthHttpClient httpClient;
- private HttpResponseInterceptor interceptor;
-
- /**
- * Creates a new FirebaseUserManager instance.
- *
- * @param app A non-null {@link FirebaseApp}.
- */
- FirebaseUserManager(@NonNull FirebaseApp app) {
- this(app, null);
- }
-
- FirebaseUserManager(@NonNull FirebaseApp app, @Nullable HttpRequestFactory requestFactory) {
- checkNotNull(app, "FirebaseApp must not be null");
+ FirebaseUserManager(Builder builder) {
+ FirebaseApp app = checkNotNull(builder.app, "FirebaseApp must not be null");
String projectId = ImplFirebaseTrampolines.getProjectId(app);
checkArgument(!Strings.isNullOrEmpty(projectId),
"Project ID is required to access the auth service. Use a service account credential or "
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
- this.baseUrl = String.format(ID_TOOLKIT_URL, projectId);
this.jsonFactory = app.getOptions().getJsonFactory();
- if (requestFactory == null) {
- requestFactory = ApiClientUtils.newAuthorizedRequestFactory(app);
+ final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
+ final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
+ final String tenantId = builder.tenantId;
+ if (tenantId == null) {
+ this.userMgtBaseUrl = idToolkitUrlV1;
+ this.idpConfigMgtBaseUrl = idToolkitUrlV2;
+ } else {
+ checkArgument(!tenantId.isEmpty(), "Tenant ID must not be empty.");
+ this.userMgtBaseUrl = idToolkitUrlV1 + "/tenants/" + tenantId;
+ this.idpConfigMgtBaseUrl = idToolkitUrlV2 + "/tenants/" + tenantId;
}
- AuthErrorHandler authErrorHandler = new AuthErrorHandler(jsonFactory);
- this.httpClient = new ErrorHandlingHttpClient<>(requestFactory, jsonFactory, authErrorHandler);
+ HttpRequestFactory requestFactory = builder.requestFactory == null
+ ? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
+ this.httpClient = new AuthHttpClient(jsonFactory, requestFactory);
}
@VisibleForTesting
void setInterceptor(HttpResponseInterceptor interceptor) {
- this.interceptor = interceptor;
+ httpClient.setInterceptor(interceptor);
}
UserRecord getUserById(String uid) throws FirebaseAuthException {
@@ -127,13 +128,35 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException
return lookupUserAccount(payload, "phone number: " + phoneNumber);
}
- String createUser(CreateRequest request) throws FirebaseAuthException {
- GenericJson response = post(
- "/accounts", request.getProperties(), GenericJson.class);
+ Set getAccountInfo(@NonNull Collection identifiers)
+ throws FirebaseAuthException {
+ if (identifiers.isEmpty()) {
+ return new HashSet<>();
+ }
+
+ GetAccountInfoRequest payload = new GetAccountInfoRequest();
+ for (UserIdentifier id : identifiers) {
+ id.populate(payload);
+ }
+
+ GetAccountInfoResponse response = post(
+ "/accounts:lookup", payload, GetAccountInfoResponse.class);
+ Set results = new HashSet<>();
+ if (response.getUsers() != null) {
+ for (GetAccountInfoResponse.User user : response.getUsers()) {
+ results.add(new UserRecord(user, jsonFactory));
+ }
+ }
+ return results;
+ }
+
+ String createUser(UserRecord.CreateRequest request) throws FirebaseAuthException {
+ GenericJson response = post("/accounts", request.getProperties(), GenericJson.class);
return (String) response.get("localId");
}
- void updateUser(UpdateRequest request, JsonFactory jsonFactory) throws FirebaseAuthException {
+ void updateUser(UserRecord.UpdateRequest request, JsonFactory jsonFactory)
+ throws FirebaseAuthException {
post("/accounts:update", request.getProperties(jsonFactory), GenericJson.class);
}
@@ -142,6 +165,15 @@ void deleteUser(String uid) throws FirebaseAuthException {
post("/accounts:delete", payload, GenericJson.class);
}
+ DeleteUsersResult deleteUsers(@NonNull List uids) throws FirebaseAuthException {
+ final Map payload = ImmutableMap.of(
+ "localIds", uids,
+ "force", true);
+ BatchDeleteResponse response = post(
+ "/accounts:batchDelete", payload, BatchDeleteResponse.class);
+ return new DeleteUsersResult(uids.size(), response);
+ }
+
DownloadAccountResponse listUsers(int maxResults, String pageToken) throws FirebaseAuthException {
ImmutableMap.Builder builder = ImmutableMap.builder()
.put("maxResults", maxResults);
@@ -150,16 +182,15 @@ DownloadAccountResponse listUsers(int maxResults, String pageToken) throws Fireb
builder.put("nextPageToken", pageToken);
}
- GenericUrl url = new GenericUrl(baseUrl + "/accounts:batchGet");
+ GenericUrl url = new GenericUrl(userMgtBaseUrl + "/accounts:batchGet");
url.putAll(builder.build());
- return sendRequest(
- HttpMethods.GET, url.toString(), null, DownloadAccountResponse.class);
+ return httpClient.sendRequest(HttpMethods.GET, url, null, DownloadAccountResponse.class);
}
UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthException {
checkNotNull(request);
UploadAccountResponse response = post(
- "/accounts:batchCreate", request, UploadAccountResponse.class);
+ "/accounts:batchCreate", request, UploadAccountResponse.class);
return new UserImportResult(request.getUsersCount(), response);
}
@@ -187,8 +218,8 @@ String getEmailActionLink(EmailLinkType type, String email,
private UserRecord lookupUserAccount(
Map payload, String identifier) throws FirebaseAuthException {
- IncomingHttpResponse response = sendRequest(
- HttpMethods.POST, baseUrl + "/accounts:lookup", payload);
+ IncomingHttpResponse response = httpClient.sendRequest(
+ HttpMethods.POST, new GenericUrl(userMgtBaseUrl + "/accounts:lookup"), payload);
GetAccountInfoResponse parsed = httpClient.parse(response, GetAccountInfoResponse.class);
if (parsed.getUsers() == null || parsed.getUsers().isEmpty()) {
throw new FirebaseAuthException(ErrorCode.NOT_FOUND,
@@ -201,28 +232,103 @@ private UserRecord lookupUserAccount(
return new UserRecord(parsed.getUsers().get(0), jsonFactory);
}
- private T post(String path, Object content, Class clazz) throws FirebaseAuthException {
- checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
- checkNotNull(content, "content must not be null for POST requests");
- return sendRequest(HttpMethods.POST, baseUrl + path, content, clazz);
+ OidcProviderConfig createOidcProviderConfig(
+ OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
+ GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
+ url.set("oauthIdpConfigId", request.getProviderId());
+ return httpClient.sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
+ }
+
+ SamlProviderConfig createSamlProviderConfig(
+ SamlProviderConfig.CreateRequest request) throws FirebaseAuthException {
+ GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/inboundSamlConfigs");
+ url.set("inboundSamlConfigId", request.getProviderId());
+ return httpClient.sendRequest("POST", url, request.getProperties(), SamlProviderConfig.class);
+ }
+
+ OidcProviderConfig updateOidcProviderConfig(OidcProviderConfig.UpdateRequest request)
+ throws FirebaseAuthException {
+ Map properties = request.getProperties();
+ GenericUrl url =
+ new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(request.getProviderId()));
+ url.put("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
+ return httpClient.sendRequest("PATCH", url, properties, OidcProviderConfig.class);
}
- private T sendRequest(
- String method, String url, @Nullable Object content, Class clazz)
+ SamlProviderConfig updateSamlProviderConfig(SamlProviderConfig.UpdateRequest request)
throws FirebaseAuthException {
+ Map properties = request.getProperties();
+ GenericUrl url =
+ new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(request.getProviderId()));
+ url.put("updateMask", Joiner.on(",").join(AuthHttpClient.generateMask(properties)));
+ return httpClient.sendRequest("PATCH", url, properties, SamlProviderConfig.class);
+ }
+
+ OidcProviderConfig getOidcProviderConfig(String providerId) throws FirebaseAuthException {
+ GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getOidcUrlSuffix(providerId));
+ return httpClient.sendRequest("GET", url, null, OidcProviderConfig.class);
+ }
- IncomingHttpResponse response = sendRequest(method, url, content);
- return httpClient.parse(response, clazz);
+ SamlProviderConfig getSamlProviderConfig(String providerId) throws FirebaseAuthException {
+ GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + getSamlUrlSuffix(providerId));
+ return httpClient.sendRequest("GET", url, null, SamlProviderConfig.class);
}
- private IncomingHttpResponse sendRequest(
- String method, String url, @Nullable Object content) throws FirebaseAuthException {
+ ListOidcProviderConfigsResponse listOidcProviderConfigs(int maxResults, String pageToken)
+ throws FirebaseAuthException {
+ ImmutableMap.Builder builder =
+ ImmutableMap.builder().put("pageSize", maxResults);
+ if (pageToken != null) {
+ checkArgument(!pageToken.equals(
+ ListProviderConfigsPage.END_OF_LIST), "Invalid end of list page token.");
+ builder.put("nextPageToken", pageToken);
+ }
- HttpContent httpContent = content != null ? new JsonHttpContent(jsonFactory, content) : null;
- HttpRequestInfo requestInfo = HttpRequestInfo.buildRequest(method, url, httpContent)
- .addHeader(CLIENT_VERSION_HEADER, CLIENT_VERSION)
- .setResponseInterceptor(interceptor);
- return httpClient.send(requestInfo);
+ GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
+ url.putAll(builder.build());
+ return httpClient.sendRequest("GET", url, null, ListOidcProviderConfigsResponse.class);
+ }
+
+ ListSamlProviderConfigsResponse listSamlProviderConfigs(int maxResults, String pageToken)
+ throws FirebaseAuthException {
+ ImmutableMap.Builder builder =
+ ImmutableMap.