From ab3af3cda5937b4e335e0a9256e19cb76c9de4bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 May 2020 15:36:27 -0700 Subject: [PATCH 1/7] Bump netty.version from 4.1.34.Final to 4.1.45.Final (#373) Bumps `netty.version` from 4.1.34.Final to 4.1.45.Final. Updates `netty-codec-http` from 4.1.34.Final to 4.1.45.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.34.Final...netty-4.1.45.Final) Updates `netty-handler` from 4.1.34.Final to 4.1.45.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.34.Final...netty-4.1.45.Final) Updates `netty-transport` from 4.1.34.Final to 4.1.45.Final - [Release notes](https://github.com/netty/netty/releases) - [Commits](https://github.com/netty/netty/compare/netty-4.1.34.Final...netty-4.1.45.Final) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index af25b87d7..b71b4e773 100644 --- a/pom.xml +++ b/pom.xml @@ -59,7 +59,7 @@ UTF-8 UTF-8 ${skipTests} - 4.1.34.Final + 4.1.45.Final From f8052c76330364675b13b2724d6d951269ecd6e3 Mon Sep 17 00:00:00 2001 From: rsgowman Date: Tue, 12 May 2020 16:56:35 -0400 Subject: [PATCH 2/7] feat(auth): Add bulk get/delete methods (#365) This PR allows callers to retrieve a list of users by unique identifier (uid, email, phone, federated provider uid) as well as to delete a list of users. RELEASE NOTE: Added getUsers() and deleteUsers() APIs for retrieving and deleting user accounts in bulk. --- .../firebase/auth/DeleteUsersResult.java | 74 ++++++ .../google/firebase/auth/EmailIdentifier.java | 49 ++++ .../google/firebase/auth/FirebaseAuth.java | 143 ++++++++++- .../firebase/auth/FirebaseUserManager.java | 52 +++- .../google/firebase/auth/GetUsersResult.java | 52 ++++ .../google/firebase/auth/PhoneIdentifier.java | 49 ++++ .../firebase/auth/ProviderIdentifier.java | 56 +++++ .../google/firebase/auth/UidIdentifier.java | 49 ++++ .../google/firebase/auth/UserIdentifier.java | 31 +++ .../google/firebase/auth/UserMetadata.java | 15 +- .../com/google/firebase/auth/UserRecord.java | 16 +- .../auth/internal/BatchDeleteResponse.java | 51 ++++ .../auth/internal/GetAccountInfoRequest.java | 80 ++++++ .../auth/internal/GetAccountInfoResponse.java | 7 + .../google/firebase/auth/FirebaseAuthIT.java | 128 +++++++++- .../auth/FirebaseUserManagerTest.java | 227 ++++++++++++++++++ .../com/google/firebase/auth/GetUsersIT.java | 152 ++++++++++++ .../firebase/auth/ImportUserRecordTest.java | 2 +- 18 files changed, 1220 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/google/firebase/auth/DeleteUsersResult.java create mode 100644 src/main/java/com/google/firebase/auth/EmailIdentifier.java create mode 100644 src/main/java/com/google/firebase/auth/GetUsersResult.java create mode 100644 src/main/java/com/google/firebase/auth/PhoneIdentifier.java create mode 100644 src/main/java/com/google/firebase/auth/ProviderIdentifier.java create mode 100644 src/main/java/com/google/firebase/auth/UidIdentifier.java create mode 100644 src/main/java/com/google/firebase/auth/UserIdentifier.java create mode 100644 src/main/java/com/google/firebase/auth/internal/BatchDeleteResponse.java create mode 100644 src/main/java/com/google/firebase/auth/internal/GetAccountInfoRequest.java create mode 100644 src/test/java/com/google/firebase/auth/GetUsersIT.java 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 f7f6231ad..923778af4 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseAuth.java +++ b/src/main/java/com/google/firebase/auth/FirebaseAuth.java @@ -42,8 +42,11 @@ import com.google.firebase.internal.Nullable; import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -601,7 +604,82 @@ protected UserRecord execute() throws FirebaseAuthException { } /** - * Gets a page of users starting from the specified {@code pageToken}. Page size will be + * 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; + } + + /** + * 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. @@ -842,8 +920,67 @@ protected Void execute() throws FirebaseAuthException { } /** - * 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 + * 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); + } + }; + } + + /** + * Imports the provided list of users into Firebase Auth. You can import a maximum of 1000 users + * at a time. This operation is optimized for bulk imports and does not check identifier * uniqueness which could result in duplications. * *

{@link UserImportOptions} is required to import users with passwords. See diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java index 03c2813bc..ab8759c4f 100644 --- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java +++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java @@ -39,9 +39,10 @@ import com.google.firebase.ImplFirebaseTrampolines; import com.google.firebase.auth.UserRecord.CreateRequest; import com.google.firebase.auth.UserRecord.UpdateRequest; +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.HttpErrorResponse; import com.google.firebase.auth.internal.UploadAccountResponse; import com.google.firebase.internal.ApiClientUtils; @@ -50,8 +51,11 @@ import com.google.firebase.internal.SdkUtils; import java.io.IOException; +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 @@ -86,6 +90,8 @@ class FirebaseUserManager { .put("INVALID_DYNAMIC_LINK_DOMAIN", "invalid-dynamic-link-domain") .build(); + 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; @@ -171,6 +177,33 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException return new UserRecord(response.getUsers().get(0), jsonFactory); } + 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); + + if (response == null) { + throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to parse server response"); + } + + Set results = new HashSet<>(); + if (response.getUsers() != null) { + for (GetAccountInfoResponse.User user : response.getUsers()) { + results.add(new UserRecord(user, jsonFactory)); + } + } + return results; + } + String createUser(CreateRequest request) throws FirebaseAuthException { GenericJson response = post( "/accounts", request.getProperties(), GenericJson.class); @@ -200,6 +233,23 @@ void deleteUser(String uid) throws FirebaseAuthException { } } + /** + * @pre uids != null + * @pre uids.size() <= MAX_DELETE_ACCOUNTS_BATCH_SIZE + */ + DeleteUsersResult deleteUsers(@NonNull List uids) throws FirebaseAuthException { + final Map payload = ImmutableMap.of( + "localIds", uids, + "force", true); + BatchDeleteResponse response = post( + "/accounts:batchDelete", payload, BatchDeleteResponse.class); + if (response == null) { + throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to delete users"); + } + + return new DeleteUsersResult(uids.size(), response); + } + DownloadAccountResponse listUsers(int maxResults, String pageToken) throws FirebaseAuthException { ImmutableMap.Builder builder = ImmutableMap.builder() .put("maxResults", maxResults); diff --git a/src/main/java/com/google/firebase/auth/GetUsersResult.java b/src/main/java/com/google/firebase/auth/GetUsersResult.java new file mode 100644 index 000000000..3ceec01cb --- /dev/null +++ b/src/main/java/com/google/firebase/auth/GetUsersResult.java @@ -0,0 +1,52 @@ +/* + * 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.checkNotNull; + +import com.google.firebase.internal.NonNull; +import java.util.Set; + +/** + * Represents the result of the {@link FirebaseAuth#getUsersAsync(Collection)} API. + */ +public final class GetUsersResult { + private final Set users; + private final Set notFound; + + GetUsersResult(@NonNull Set users, @NonNull Set notFound) { + this.users = checkNotNull(users); + this.notFound = checkNotNull(notFound); + } + + /** + * Set of user records corresponding to the set of users that were requested. Only users + * that were found are listed here. The result set is unordered. + */ + @NonNull + public Set getUsers() { + return this.users; + } + + /** + * Set of identifiers that were requested, but not found. + */ + @NonNull + public Set getNotFound() { + return this.notFound; + } +} diff --git a/src/main/java/com/google/firebase/auth/PhoneIdentifier.java b/src/main/java/com/google/firebase/auth/PhoneIdentifier.java new file mode 100644 index 000000000..bdc84fe92 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/PhoneIdentifier.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 phone number. + * + * @see {FirebaseAuth#getUsers} + */ +public final class PhoneIdentifier extends UserIdentifier { + private final String phoneNumber; + + public PhoneIdentifier(@NonNull String phoneNumber) { + UserRecord.checkPhoneNumber(phoneNumber); + this.phoneNumber = phoneNumber; + } + + @Override + public String toString() { + return "PhoneIdentifier(" + phoneNumber + ")"; + } + + @Override + void populate(@NonNull GetAccountInfoRequest payload) { + payload.addPhoneNumber(phoneNumber); + } + + @Override + boolean matches(@NonNull UserRecord userRecord) { + return phoneNumber.equals(userRecord.getPhoneNumber()); + } +} diff --git a/src/main/java/com/google/firebase/auth/ProviderIdentifier.java b/src/main/java/com/google/firebase/auth/ProviderIdentifier.java new file mode 100644 index 000000000..25e00026d --- /dev/null +++ b/src/main/java/com/google/firebase/auth/ProviderIdentifier.java @@ -0,0 +1,56 @@ +/* + * 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 provider. + * + * @see {FirebaseAuth#getUsers} + */ +public final class ProviderIdentifier extends UserIdentifier { + private final String providerId; + private final String providerUid; + + public ProviderIdentifier(@NonNull String providerId, @NonNull String providerUid) { + UserRecord.checkProvider(providerId, providerUid); + this.providerId = providerId; + this.providerUid = providerUid; + } + + @Override + public String toString() { + return "ProviderIdentifier(" + providerId + ", " + providerUid + ")"; + } + + @Override + void populate(@NonNull GetAccountInfoRequest payload) { + payload.addFederatedUserId(providerId, providerUid); + } + + @Override + boolean matches(@NonNull UserRecord userRecord) { + for (UserInfo userInfo : userRecord.getProviderData()) { + if (providerId.equals(userInfo.getProviderId()) && providerUid.equals(userInfo.getUid())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/com/google/firebase/auth/UidIdentifier.java b/src/main/java/com/google/firebase/auth/UidIdentifier.java new file mode 100644 index 000000000..a4f7069d9 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/UidIdentifier.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 uid. + * + * @see {FirebaseAuth#getUsers} + */ +public final class UidIdentifier extends UserIdentifier { + private final String uid; + + public UidIdentifier(@NonNull String uid) { + UserRecord.checkUid(uid); + this.uid = uid; + } + + @Override + public String toString() { + return "UidIdentifier(" + uid + ")"; + } + + @Override + void populate(@NonNull GetAccountInfoRequest payload) { + payload.addUid(uid); + } + + @Override + boolean matches(@NonNull UserRecord userRecord) { + return uid.equals(userRecord.getUid()); + } +} diff --git a/src/main/java/com/google/firebase/auth/UserIdentifier.java b/src/main/java/com/google/firebase/auth/UserIdentifier.java new file mode 100644 index 000000000..7ec9699e6 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/UserIdentifier.java @@ -0,0 +1,31 @@ +/* + * 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; + +/** + * Identifies a user to be looked up. + */ +public abstract class UserIdentifier { + public abstract String toString(); + + abstract void populate(@NonNull GetAccountInfoRequest payload); + + abstract boolean matches(@NonNull UserRecord userRecord); +} diff --git a/src/main/java/com/google/firebase/auth/UserMetadata.java b/src/main/java/com/google/firebase/auth/UserMetadata.java index a2872371f..85a24a0fd 100644 --- a/src/main/java/com/google/firebase/auth/UserMetadata.java +++ b/src/main/java/com/google/firebase/auth/UserMetadata.java @@ -23,14 +23,16 @@ public class UserMetadata { private final long creationTimestamp; private final long lastSignInTimestamp; + private final long lastRefreshTimestamp; public UserMetadata(long creationTimestamp) { - this(creationTimestamp, 0L); + this(creationTimestamp, 0L, 0L); } - public UserMetadata(long creationTimestamp, long lastSignInTimestamp) { + public UserMetadata(long creationTimestamp, long lastSignInTimestamp, long lastRefreshTimestamp) { this.creationTimestamp = creationTimestamp; this.lastSignInTimestamp = lastSignInTimestamp; + this.lastRefreshTimestamp = lastRefreshTimestamp; } /** @@ -50,4 +52,13 @@ public long getCreationTimestamp() { public long getLastSignInTimestamp() { return lastSignInTimestamp; } + + /** + * Returns the time at which the user was last active (ID token refreshed). + *  + * @return Milliseconds since epoch timestamp, or 0 if the user was never active. + */ + public long getLastRefreshTimestamp() { + return lastRefreshTimestamp; + } } diff --git a/src/main/java/com/google/firebase/auth/UserRecord.java b/src/main/java/com/google/firebase/auth/UserRecord.java index e00450079..64e7c278c 100644 --- a/src/main/java/com/google/firebase/auth/UserRecord.java +++ b/src/main/java/com/google/firebase/auth/UserRecord.java @@ -20,6 +20,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import com.google.api.client.json.JsonFactory; +import com.google.api.client.util.DateTime; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -80,7 +81,15 @@ public class UserRecord implements UserInfo { } } this.tokensValidAfterTimestamp = response.getValidSince() * 1000; - this.userMetadata = new UserMetadata(response.getCreatedAt(), response.getLastLoginAt()); + + String lastRefreshAtRfc3339 = response.getLastRefreshAt(); + long lastRefreshAtMillis = 0; + if (!Strings.isNullOrEmpty(lastRefreshAtRfc3339)) { + lastRefreshAtMillis = DateTime.parseRfc3339(lastRefreshAtRfc3339).getValue(); + } + + this.userMetadata = new UserMetadata( + response.getCreatedAt(), response.getLastLoginAt(), lastRefreshAtMillis); this.customClaims = parseCustomClaims(response.getCustomClaims(), jsonFactory); } @@ -247,6 +256,11 @@ static void checkPhoneNumber(String phoneNumber) { "phone number must be a valid, E.164 compliant identifier starting with a '+' sign"); } + static void checkProvider(String providerId, String providerUid) { + checkArgument(!Strings.isNullOrEmpty(providerId), "providerId must be a non-empty string"); + checkArgument(!Strings.isNullOrEmpty(providerUid), "providerUid must be a non-empty string"); + } + static void checkUrl(String photoUrl) { checkArgument(!Strings.isNullOrEmpty(photoUrl), "url cannot be null or empty"); try { diff --git a/src/main/java/com/google/firebase/auth/internal/BatchDeleteResponse.java b/src/main/java/com/google/firebase/auth/internal/BatchDeleteResponse.java new file mode 100644 index 000000000..728cf6358 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/internal/BatchDeleteResponse.java @@ -0,0 +1,51 @@ +/* + * 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.internal; + +import com.google.api.client.util.Key; +import java.util.List; + +/** + * Represents the response from Google identity Toolkit for a batch delete request. + */ +public class BatchDeleteResponse { + + @Key("errors") + private List errors; + + public List getErrors() { + return errors; + } + + public static class ErrorInfo { + @Key("index") + private int index; + + @Key("message") + private String message; + + // A 'localId' field also exists here, but is not currently exposed in the Admin SDK. + + public int getIndex() { + return index; + } + + public String getMessage() { + return message; + } + } +} diff --git a/src/main/java/com/google/firebase/auth/internal/GetAccountInfoRequest.java b/src/main/java/com/google/firebase/auth/internal/GetAccountInfoRequest.java new file mode 100644 index 000000000..67c4d0ee7 --- /dev/null +++ b/src/main/java/com/google/firebase/auth/internal/GetAccountInfoRequest.java @@ -0,0 +1,80 @@ +/* + * 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.internal; + +import com.google.api.client.util.Key; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents the request to look up account information. + */ +public final class GetAccountInfoRequest { + + @Key("localId") + private List uids = null; + + @Key("email") + private List emails = null; + + @Key("phoneNumber") + private List phoneNumbers = null; + + @Key("federatedUserId") + private List federatedUserIds = null; + + private static final class FederatedUserId { + @Key("providerId") + private String providerId = null; + + @Key("rawId") + private String rawId = null; + + FederatedUserId(String providerId, String rawId) { + this.providerId = providerId; + this.rawId = rawId; + } + } + + public void addUid(String uid) { + if (uids == null) { + uids = new ArrayList<>(); + } + uids.add(uid); + } + + public void addEmail(String email) { + if (emails == null) { + emails = new ArrayList<>(); + } + emails.add(email); + } + + public void addPhoneNumber(String phoneNumber) { + if (phoneNumbers == null) { + phoneNumbers = new ArrayList<>(); + } + phoneNumbers.add(phoneNumber); + } + + public void addFederatedUserId(String providerId, String providerUid) { + if (federatedUserIds == null) { + federatedUserIds = new ArrayList<>(); + } + federatedUserIds.add(new FederatedUserId(providerId, providerUid)); + } +} diff --git a/src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java b/src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java index 3d17c50f6..e84335891 100644 --- a/src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java +++ b/src/main/java/com/google/firebase/auth/internal/GetAccountInfoResponse.java @@ -73,6 +73,9 @@ public static class User { @Key("lastLoginAt") private long lastLoginAt; + @Key("lastRefreshAt") + private String lastRefreshAt; + @Key("validSince") private long validSince; @@ -119,6 +122,10 @@ public long getLastLoginAt() { return lastLoginAt; } + public String getLastRefreshAt() { + return lastRefreshAt; + } + public long getValidSince() { return validSince; } diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index 6a8361d0d..aa45f51b3 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -137,6 +137,78 @@ public void testDeleteNonExistingUser() throws Exception { } } + @Test + public void testDeleteUsers() throws Exception { + UserRecord user1 = newUserWithParams(); + UserRecord user2 = newUserWithParams(); + UserRecord user3 = newUserWithParams(); + + DeleteUsersResult deleteUsersResult = + slowDeleteUsersAsync(ImmutableList.of(user1.getUid(), user2.getUid(), user3.getUid())) + .get(); + + assertEquals(3, deleteUsersResult.getSuccessCount()); + assertEquals(0, deleteUsersResult.getFailureCount()); + assertTrue(deleteUsersResult.getErrors().isEmpty()); + + GetUsersResult getUsersResult = + auth.getUsersAsync( + ImmutableList.of(new UidIdentifier(user1.getUid()), + new UidIdentifier(user2.getUid()), new UidIdentifier(user3.getUid()))) + .get(); + + assertTrue(getUsersResult.getUsers().isEmpty()); + assertEquals(3, getUsersResult.getNotFound().size()); + } + + @Test + public void testDeleteExistingAndNonExistingUsers() throws Exception { + UserRecord user1 = newUserWithParams(); + + DeleteUsersResult deleteUsersResult = + slowDeleteUsersAsync(ImmutableList.of(user1.getUid(), "uid-that-doesnt-exist")).get(); + + assertEquals(2, deleteUsersResult.getSuccessCount()); + assertEquals(0, deleteUsersResult.getFailureCount()); + assertTrue(deleteUsersResult.getErrors().isEmpty()); + + GetUsersResult getUsersResult = + auth.getUsersAsync(ImmutableList.of(new UidIdentifier(user1.getUid()), + new UidIdentifier("uid-that-doesnt-exist"))) + .get(); + + assertTrue(getUsersResult.getUsers().isEmpty()); + assertEquals(2, getUsersResult.getNotFound().size()); + } + + @Test + public void testDeleteUsersIsIdempotent() throws Exception { + UserRecord user1 = newUserWithParams(); + + DeleteUsersResult result = slowDeleteUsersAsync(ImmutableList.of(user1.getUid())).get(); + + assertEquals(1, result.getSuccessCount()); + assertEquals(0, result.getFailureCount()); + assertTrue(result.getErrors().isEmpty()); + + // Delete the user again to ensure that everything still counts as a success. + result = slowDeleteUsersAsync(ImmutableList.of(user1.getUid())).get(); + + assertEquals(1, result.getSuccessCount()); + assertEquals(0, result.getFailureCount()); + assertTrue(result.getErrors().isEmpty()); + } + + /** + * The {@code batchDelete} endpoint has a rate limit of 1 QPS. Use this test + * helper to ensure you don't exceed the quota. + */ + // TODO(rsgowman): When/if the rate limit is relaxed, eliminate this helper. + private ApiFuture slowDeleteUsersAsync(List uids) throws Exception { + TimeUnit.SECONDS.sleep(1); + return auth.deleteUsersAsync(uids); + } + @Test public void testCreateUserWithParams() throws Exception { RandomUser randomUser = RandomUser.create(); @@ -248,6 +320,35 @@ public void testUserLifecycle() throws Exception { } } + @Test + public void testLastRefreshTime() throws Exception { + RandomUser user = RandomUser.create(); + UserRecord newUserRecord = auth.createUser(new CreateRequest() + .setUid(user.uid) + .setEmail(user.email) + .setEmailVerified(false) + .setPassword("password")); + + try { + // New users should not have a lastRefreshTimestamp set. + assertEquals(0, newUserRecord.getUserMetadata().getLastRefreshTimestamp()); + + // Login to cause the lastRefreshTimestamp to be set. + signInWithPassword(newUserRecord.getEmail(), "password"); + + UserRecord userRecord = auth.getUser(newUserRecord.getUid()); + + // Ensure the lastRefreshTimestamp is approximately "now" (with a tollerance of 10 minutes). + long now = System.currentTimeMillis(); + long tollerance = TimeUnit.MINUTES.toMillis(10); + long lastRefreshTimestamp = userRecord.getUserMetadata().getLastRefreshTimestamp(); + assertTrue(now - tollerance <= lastRefreshTimestamp); + assertTrue(lastRefreshTimestamp <= now + tollerance); + } finally { + auth.deleteUser(newUserRecord.getUid()); + } + } + @Test public void testListUsers() throws Exception { final List uids = new ArrayList<>(); @@ -607,7 +708,7 @@ private Map parseLinkParameters(String link) throws Exception { return result; } - private String randomPhoneNumber() { + static String randomPhoneNumber() { Random random = new Random(); StringBuilder builder = new StringBuilder("+1"); for (int i = 0; i < 10; i++) { @@ -637,7 +738,7 @@ private String signInWithPassword(String email, String password) throws IOExcept GenericUrl url = new GenericUrl(VERIFY_PASSWORD_URL + "?key=" + IntegrationTestUtils.getApiKey()); Map content = ImmutableMap.of( - "email", email, "password", password); + "email", email, "password", password, "returnSecureToken", true); HttpRequest request = transport.createRequestFactory().buildPostRequest(url, new JsonHttpContent(jsonFactory, content)); request.setParser(new JsonObjectParser(jsonFactory)); @@ -696,9 +797,9 @@ private void checkRecreate(String uid) throws Exception { } } - private static class RandomUser { - private final String uid; - private final String email; + static class RandomUser { + final String uid; + final String email; private RandomUser(String uid, String email) { this.uid = uid; @@ -712,4 +813,21 @@ static RandomUser create() { return new RandomUser(uid, email); } } + + static UserRecord newUserWithParams() throws Exception { + return newUserWithParams(auth); + } + + static UserRecord newUserWithParams(FirebaseAuth auth) throws Exception { + // TODO(rsgowman): This function could be used throughout this file (similar to the other + // ports). + RandomUser randomUser = RandomUser.create(); + return auth.createUser(new CreateRequest() + .setUid(randomUser.uid) + .setEmail(randomUser.email) + .setPhoneNumber(randomPhoneNumber()) + .setDisplayName("Random User") + .setPhotoUrl("https://example.com/photo.png") + .setPassword("password")); + } } diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java index de0b7fa29..67d0448e9 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java +++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java @@ -33,6 +33,7 @@ import com.google.api.client.testing.http.MockHttpTransport; import com.google.api.client.testing.http.MockLowLevelHttpResponse; import com.google.auth.oauth2.GoogleCredentials; +import com.google.common.base.Strings; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -41,6 +42,8 @@ import com.google.firebase.FirebaseOptions; import com.google.firebase.TestOnlyImplFirebaseTrampolines; import com.google.firebase.auth.FirebaseUserManager.EmailLinkType; +import com.google.firebase.auth.UidIdentifier; +import com.google.firebase.auth.UserIdentifier; import com.google.firebase.auth.UserRecord.CreateRequest; import com.google.firebase.auth.UserRecord.UpdateRequest; import com.google.firebase.internal.SdkUtils; @@ -52,6 +55,8 @@ import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; @@ -167,6 +172,153 @@ public void testGetUserByPhoneNumberWithNotFoundError() throws Exception { } } + @Test + public void testGetUsersExceeds100() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + List identifiers = new ArrayList<>(); + for (int i = 0; i < 101; i++) { + identifiers.add(new UidIdentifier("uid_" + i)); + } + + try { + FirebaseAuth.getInstance().getUsers(identifiers); + fail("No error thrown for too many supplied identifiers"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testGetUsersNull() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + try { + FirebaseAuth.getInstance().getUsers(null); + fail("No error thrown for null identifiers"); + } catch (NullPointerException expected) { + // expected + } + } + + @Test + public void testGetUsersEmpty() throws Exception { + initializeAppForUserManagement(); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(new ArrayList()); + assertTrue(result.getUsers().isEmpty()); + assertTrue(result.getNotFound().isEmpty()); + } + + @Test + public void testGetUsersAllNonExisting() throws Exception { + initializeAppForUserManagement("{ \"users\": [] }"); + List ids = ImmutableList.of( + new UidIdentifier("id-that-doesnt-exist")); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(ids); + assertTrue(result.getUsers().isEmpty()); + assertEquals(ids.size(), result.getNotFound().size()); + assertTrue(result.getNotFound().containsAll(ids)); + } + + @Test + public void testGetUsersMultipleIdentifierTypes() throws Exception { + initializeAppForUserManagement(("" + + "{ " + + " 'users': [{ " + + " 'localId': 'uid1', " + + " 'email': 'user1@example.com', " + + " 'phoneNumber': '+15555550001' " + + " }, { " + + " 'localId': 'uid2', " + + " 'email': 'user2@example.com', " + + " 'phoneNumber': '+15555550002' " + + " }, { " + + " 'localId': 'uid3', " + + " 'email': 'user3@example.com', " + + " 'phoneNumber': '+15555550003' " + + " }, { " + + " 'localId': 'uid4', " + + " 'email': 'user4@example.com', " + + " 'phoneNumber': '+15555550004', " + + " 'providerUserInfo': [{ " + + " 'providerId': 'google.com', " + + " 'rawId': 'google_uid4' " + + " }] " + + " }] " + + "} " + ).replace("'", "\"")); + + UidIdentifier doesntExist = new UidIdentifier("this-uid-doesnt-exist"); + List ids = ImmutableList.of( + new UidIdentifier("uid1"), + new EmailIdentifier("user2@example.com"), + new PhoneIdentifier("+15555550003"), + new ProviderIdentifier("google.com", "google_uid4"), + doesntExist); + GetUsersResult result = FirebaseAuth.getInstance().getUsers(ids); + Collection uids = userRecordsToUids(result.getUsers()); + assertTrue(uids.containsAll(ImmutableList.of("uid1", "uid2", "uid3", "uid4"))); + assertEquals(1, result.getNotFound().size()); + assertTrue(result.getNotFound().contains(doesntExist)); + } + + private Collection userRecordsToUids(Collection userRecords) { + Collection uids = new HashSet<>(); + for (UserRecord userRecord : userRecords) { + uids.add(userRecord.getUid()); + } + return uids; + } + + @Test + public void testInvalidUidIdentifier() throws Exception { + try { + new UidIdentifier("too long " + Strings.repeat(".", 128)); + fail("No error thrown for invalid uid"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testInvalidEmailIdentifier() throws Exception { + try { + new EmailIdentifier("invalid email addr"); + fail("No error thrown for invalid email"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testInvalidPhoneIdentifier() throws Exception { + try { + new PhoneIdentifier("invalid phone number"); + fail("No error thrown for invalid phone number"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testInvalidProviderIdentifier() throws Exception { + try { + new ProviderIdentifier("", "valid-uid"); + fail("No error thrown for invalid provider id"); + } catch (IllegalArgumentException expected) { + // expected + } + + try { + new ProviderIdentifier("valid-id", ""); + fail("No error thrown for invalid provider uid"); + } catch (IllegalArgumentException expected) { + // expected + } + } + @Test public void testListUsers() throws Exception { final TestResponseInterceptor interceptor = initializeAppForUserManagement( @@ -278,6 +430,81 @@ public void testDeleteUser() throws Exception { checkRequestHeaders(interceptor); } + @Test + public void testDeleteUsersExceeds1000() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + List ids = new ArrayList<>(); + for (int i = 0; i < 1001; i++) { + ids.add("id" + i); + } + try { + FirebaseAuth.getInstance().deleteUsersAsync(ids); + fail("No error thrown for too many uids"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testDeleteUsersInvalidId() throws Exception { + FirebaseApp.initializeApp(new FirebaseOptions.Builder() + .setCredentials(credentials) + .build()); + try { + FirebaseAuth.getInstance().deleteUsersAsync( + ImmutableList.of("too long " + Strings.repeat(".", 128))); + fail("No error thrown for too long uid"); + } catch (IllegalArgumentException expected) { + // expected + } + } + + @Test + public void testDeleteUsersIndexesErrorsCorrectly() throws Exception { + initializeAppForUserManagement(("" + + "{ " + + " 'errors': [{ " + + " 'index': 0, " + + " 'localId': 'uid1', " + + " 'message': 'NOT_DISABLED : Disable the account before batch deletion.' " + + " }, { " + + " 'index': 2, " + + " 'localId': 'uid3', " + + " 'message': 'something awful' " + + " }] " + + "} " + ).replace("'", "\"")); + + DeleteUsersResult result = FirebaseAuth.getInstance().deleteUsersAsync(ImmutableList.of( + "uid1", "uid2", "uid3", "uid4" + )).get(); + + assertEquals(2, result.getSuccessCount()); + assertEquals(2, result.getFailureCount()); + assertEquals(2, result.getErrors().size()); + assertEquals(0, result.getErrors().get(0).getIndex()); + assertEquals( + "NOT_DISABLED : Disable the account before batch deletion.", + result.getErrors().get(0).getReason()); + assertEquals(2, result.getErrors().get(1).getIndex()); + assertEquals("something awful", result.getErrors().get(1).getReason()); + } + + @Test + public void testDeleteUsersSuccess() throws Exception { + initializeAppForUserManagement("{}"); + + DeleteUsersResult result = FirebaseAuth.getInstance().deleteUsersAsync(ImmutableList.of( + "uid1", "uid2", "uid3" + )).get(); + + assertEquals(3, result.getSuccessCount()); + assertEquals(0, result.getFailureCount()); + assertTrue(result.getErrors().isEmpty()); + } + @Test public void testImportUsers() throws Exception { TestResponseInterceptor interceptor = initializeAppForUserManagement("{}"); diff --git a/src/test/java/com/google/firebase/auth/GetUsersIT.java b/src/test/java/com/google/firebase/auth/GetUsersIT.java new file mode 100644 index 000000000..efe2f783f --- /dev/null +++ b/src/test/java/com/google/firebase/auth/GetUsersIT.java @@ -0,0 +1,152 @@ +/* + * 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 org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.google.common.collect.ImmutableList; +import com.google.firebase.FirebaseApp; +import com.google.firebase.testing.IntegrationTestUtils; +import java.util.Collection; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +public class GetUsersIT { + private static FirebaseAuth auth; + private static UserRecord testUser1; + private static UserRecord testUser2; + private static UserRecord testUser3; + private static String importUserUid; + + @BeforeClass + public static void setUpClass() throws Exception { + FirebaseApp masterApp = IntegrationTestUtils.ensureDefaultApp(); + auth = FirebaseAuth.getInstance(masterApp); + + testUser1 = FirebaseAuthIT.newUserWithParams(auth); + testUser2 = FirebaseAuthIT.newUserWithParams(auth); + testUser3 = FirebaseAuthIT.newUserWithParams(auth); + + FirebaseAuthIT.RandomUser randomUser = FirebaseAuthIT.RandomUser.create(); + importUserUid = randomUser.uid; + String phone = FirebaseAuthIT.randomPhoneNumber(); + UserImportResult result = auth.importUsers(ImmutableList.of( + ImportUserRecord.builder() + .setUid(randomUser.uid) + .setEmail(randomUser.email) + .setPhoneNumber(phone) + .addUserProvider( + UserProvider.builder() + .setProviderId("google.com") + .setUid("google_" + randomUser.uid) + .build()) + .build() + )); + assertEquals(1, result.getSuccessCount()); + assertEquals(0, result.getFailureCount()); + } + + @AfterClass + public static void cleanup() throws Exception { + // TODO(rsgowman): deleteUsers (plural) would make more sense here, but it's currently rate + // limited to 1qps. When/if that's relaxed, change this to just delete them all at once. + auth.deleteUser(testUser1.getUid()); + auth.deleteUser(testUser2.getUid()); + auth.deleteUser(testUser3.getUid()); + auth.deleteUser(importUserUid); + } + + @Test + public void testVariousIdentifiers() throws Exception { + GetUsersResult result = auth.getUsersAsync(ImmutableList.of( + new UidIdentifier(testUser1.getUid()), + new EmailIdentifier(testUser2.getEmail()), + new PhoneIdentifier(testUser3.getPhoneNumber()), + new ProviderIdentifier("google.com", "google_" + importUserUid) + )).get(); + + Collection expectedUids = ImmutableList.of( + testUser1.getUid(), testUser2.getUid(), testUser3.getUid(), importUserUid); + + assertTrue(sameUsers(result.getUsers(), expectedUids)); + assertEquals(0, result.getNotFound().size()); + } + + @Test + public void testIgnoresNonExistingUsers() throws Exception { + UidIdentifier doesntExistId = new UidIdentifier("uid_that_doesnt_exist"); + GetUsersResult result = auth.getUsersAsync(ImmutableList.of( + new UidIdentifier(testUser1.getUid()), + doesntExistId, + new UidIdentifier(testUser3.getUid()) + )).get(); + + Collection expectedUids = ImmutableList.of(testUser1.getUid(), testUser3.getUid()); + + assertTrue(sameUsers(result.getUsers(), expectedUids)); + assertEquals(1, result.getNotFound().size()); + assertTrue(result.getNotFound().contains(doesntExistId)); + } + + @Test + public void testOnlyNonExistingUsers() throws Exception { + UidIdentifier doesntExistId = new UidIdentifier("uid_that_doesnt_exist"); + GetUsersResult result = auth.getUsersAsync(ImmutableList.of( + doesntExistId + )).get(); + + assertEquals(0, result.getUsers().size()); + assertEquals(1, result.getNotFound().size()); + assertTrue(result.getNotFound().contains(doesntExistId)); + } + + @Test + public void testDedupsDuplicateUsers() throws Exception { + GetUsersResult result = auth.getUsersAsync(ImmutableList.of( + new UidIdentifier(testUser1.getUid()), + new UidIdentifier(testUser1.getUid()) + )).get(); + + Collection expectedUids = ImmutableList.of(testUser1.getUid()); + + assertEquals(1, result.getUsers().size()); + assertTrue(sameUsers(result.getUsers(), expectedUids)); + assertEquals(0, result.getNotFound().size()); + } + + /** + * Checks to see if the userRecords collection contains the given uids. + * + *

Behaviour is undefined if there are duplicate entries in either of the parameters. + */ + private boolean sameUsers(Collection userRecords, Collection uids) { + if (userRecords.size() != uids.size()) { + return false; + } + + for (UserRecord userRecord : userRecords) { + if (!uids.contains(userRecord.getUid())) { + return false; + } + } + + return true; + } +} diff --git a/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java b/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java index 011a5cc04..e2ae36c09 100644 --- a/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java +++ b/src/test/java/com/google/firebase/auth/ImportUserRecordTest.java @@ -62,7 +62,7 @@ public void testAllProperties() throws IOException { .setDisplayName("Test User") .setPhotoUrl("https://test.com/user.png") .setPhoneNumber("+1234567890") - .setUserMetadata(new UserMetadata(date.getTime(), date.getTime())) + .setUserMetadata(new UserMetadata(date.getTime(), date.getTime(), date.getTime())) .setDisabled(false) .setEmailVerified(true) .setPasswordHash("password".getBytes()) From 30801e312b53215d60c8c9a79fa9f3b7c9cd1d00 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Wed, 13 May 2020 10:37:37 -0700 Subject: [PATCH 3/7] chore: Setting the version of the Maven Javadoc plugin (#412) --- pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pom.xml b/pom.xml index b71b4e773..d89d227e1 100644 --- a/pom.xml +++ b/pom.xml @@ -99,6 +99,7 @@ maven-javadoc-plugin + 2.10.4 site @@ -303,6 +304,7 @@ maven-javadoc-plugin + 2.10.4 attach-javadocs From a4a5315f621ef48e026ab9058590b7977a6d8873 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 14 May 2020 11:01:54 -0700 Subject: [PATCH 4/7] [chore] Release 6.13.0 (#413) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d89d227e1..ca1657fb8 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ com.google.firebase firebase-admin - 6.12.3-SNAPSHOT + 6.13.0 jar firebase-admin From 200d1f9efebb03201026d7f3b92857b954099676 Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 14 May 2020 12:05:15 -0700 Subject: [PATCH 5/7] [chore] Release 6.13.0 take 2 (#414) * Upgated the gpg keys * Added temp verify script * Disabled tty for gpg import * Removing temp verification script * Updated publish commands --- .github/resources/firebase.asc.gpg | Bin 4056 -> 4061 bytes .github/scripts/publish_artifacts.sh | 2 +- .github/workflows/release.yml | 16 ++++++++-------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/resources/firebase.asc.gpg b/.github/resources/firebase.asc.gpg index a946776c7fd1498a0399836a94785dc8fd5293ed..8fd7d0769b6056896ebff240b9955656f7c98a60 100644 GIT binary patch literal 4061 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 KX);zY-!1|?!O3F) diff --git a/.github/scripts/publish_artifacts.sh b/.github/scripts/publish_artifacts.sh index f4a2f1734..cd1a5b75c 100755 --- a/.github/scripts/publish_artifacts.sh +++ b/.github/scripts/publish_artifacts.sh @@ -20,7 +20,7 @@ set -u gpg --quiet --batch --yes --decrypt --passphrase="${GPG_PRIVATE_KEY}" \ --output firebase.asc .github/resources/firebase.asc.gpg -gpg --import firebase.asc +gpg --import --no-tty --batch --yes firebase.asc # Does the following: # 1. Compiles the source (compile phase) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0e1c307bc..985ce94d6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -91,6 +91,14 @@ jobs: id: preflight run: ./.github/scripts/publish_preflight_check.sh + - name: Publish to Maven Central + run: ./.github/scripts/publish_artifacts.sh + env: + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + NEXUS_OSSRH_USERNAME: ${{ secrets.NEXUS_OSSRH_USERNAME }} + NEXUS_OSSRH_PASSWORD: ${{ secrets.NEXUS_OSSRH_PASSWORD }} + # We pull this action from a custom fork of a contributor until # https://github.com/actions/create-release/pull/32 is merged. Also note that v1 of # this action does not support the "body" parameter. @@ -105,14 +113,6 @@ jobs: draft: false prerelease: false - - name: Publish to Maven Central - run: ./.github/scripts/publish_artifacts.sh - env: - GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} - GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} - NEXUS_OSSRH_USERNAME: ${{ secrets.NEXUS_OSSRH_USERNAME }} - NEXUS_OSSRH_PASSWORD: ${{ secrets.NEXUS_OSSRH_PASSWORD }} - # Post to Twitter if explicitly opted-in by adding the label 'release:tweet'. - name: Post to Twitter if: success() && From 68c05d7e19c4f2e91a14b650753019f15ef2e05a Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 14 May 2020 12:16:43 -0700 Subject: [PATCH 6/7] [chore] Release 6.13.0 take 3 (#415) --- .github/resources/settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/resources/settings.xml b/.github/resources/settings.xml index 708bbcb75..4afbaca26 100644 --- a/.github/resources/settings.xml +++ b/.github/resources/settings.xml @@ -21,7 +21,7 @@ gpg - B652FFD3865AF7A75830876F5F55C8F6985BB9DD + A9B90B41060565F56F348F948B6B459CFD695DE8 ${env.GPG_PASSPHRASE} From 8c9608dbe300cc7d0c6ddff98e3ab51dc41c05ea Mon Sep 17 00:00:00 2001 From: Hiranya Jayathilaka Date: Thu, 14 May 2020 12:31:01 -0700 Subject: [PATCH 7/7] [chore] Release 6.13.0 take 4 (#416) --- pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pom.xml b/pom.xml index ca1657fb8..6a2989169 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,12 @@ sign + + + --pinentry-mode + loopback + +