From b20c01065aea8a99b87df33c0dc3edf20728fb7e Mon Sep 17 00:00:00 2001 From: Narges Simjour Date: Tue, 1 Oct 2019 12:49:14 -0400 Subject: [PATCH] Remove (base64) 'REDACTED' passwords from user records. --- CONTRIBUTING.md | 10 ++++-- .../firebase/auth/ExportedUserRecord.java | 10 +++++- .../google/firebase/auth/FirebaseAuthIT.java | 4 ++- .../firebase/auth/ListUsersPageTest.java | 36 +++++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 467c0d377..11012bfff 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -136,9 +136,13 @@ Create a new project in the [Firebase console](https://console.firebase.google.c not already have one. Use a separate, dedicated project for integration tests since the test suite makes a large number of writes to the Firebase realtime database. Download the service account private key from the "Settings > Service Accounts" page of the project, and save it as -`integration_cert.json` at the root of the codebase. Also obtain the web API key of the project -from the "Settings > General" page, and save it as `integration_apikey.txt` at the root of the -codebase. Now run the following command to invoke the integration test suite: +`integration_cert.json` at the root of the codebase. Grant your service account the `Firebase +Authentication Admin` role at +[Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). This is +required to ensure that exported user records contain the password hashes of the user accounts. +Also obtain the web API key of the project from the "Settings > General" page, and save it as +`integration_apikey.txt` at the root of the codebase. Now run the following command to invoke the +integration test suite: ``` mvn verify diff --git a/src/main/java/com/google/firebase/auth/ExportedUserRecord.java b/src/main/java/com/google/firebase/auth/ExportedUserRecord.java index e367f55ae..9fdfc9c5e 100644 --- a/src/main/java/com/google/firebase/auth/ExportedUserRecord.java +++ b/src/main/java/com/google/firebase/auth/ExportedUserRecord.java @@ -17,6 +17,7 @@ package com.google.firebase.auth; import com.google.api.client.json.JsonFactory; +import com.google.common.io.BaseEncoding; import com.google.firebase.auth.internal.DownloadAccountResponse.User; import com.google.firebase.internal.Nullable; @@ -28,10 +29,17 @@ public class ExportedUserRecord extends UserRecord { private final String passwordHash; private final String passwordSalt; + private static final String REDACTED_BASE64 = BaseEncoding.base64Url().encode( + "REDACTED".getBytes()); ExportedUserRecord(User response, JsonFactory jsonFactory) { super(response, jsonFactory); - this.passwordHash = response.getPasswordHash(); + String passwordHash = response.getPasswordHash(); + if (passwordHash != null && !passwordHash.equals(REDACTED_BASE64)) { + this.passwordHash = passwordHash; + } else { + this.passwordHash = null; + } this.passwordSalt = response.getPasswordSalt(); } diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java index badb9ead6..6a8361d0d 100644 --- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java +++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java @@ -264,7 +264,9 @@ public void testListUsers() throws Exception { for (ExportedUserRecord user : page.getValues()) { if (uids.contains(user.getUid())) { collected.incrementAndGet(); - assertNotNull(user.getPasswordHash()); + assertNotNull("Missing passwordHash field. A common cause would be " + + "forgetting to add the \"Firebase Authentication Admin\" permission. See " + + "instructions in CONTRIBUTING.md", user.getPasswordHash()); assertNotNull(user.getPasswordSalt()); } } diff --git a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java index 1c6282fa2..fb4a7d275 100644 --- a/src/test/java/com/google/firebase/auth/ListUsersPageTest.java +++ b/src/test/java/com/google/firebase/auth/ListUsersPageTest.java @@ -26,6 +26,7 @@ import com.google.api.client.googleapis.util.Utils; import com.google.api.client.json.JsonFactory; import com.google.common.collect.ImmutableList; +import com.google.common.io.BaseEncoding; import com.google.firebase.auth.ListUsersPage.ListUsersResult; import com.google.firebase.auth.internal.DownloadAccountResponse; import java.io.IOException; @@ -38,6 +39,9 @@ public class ListUsersPageTest { + private static final String REDACTED_BASE64 = BaseEncoding.base64Url().encode( + "REDACTED".getBytes()); + @Test public void testSinglePage() throws FirebaseAuthException, IOException { TestUserSource source = new TestUserSource(3); @@ -55,6 +59,30 @@ public void testSinglePage() throws FirebaseAuthException, IOException { assertNull(source.calls.get(0)); } + @Test + public void testRedactedPasswords() throws FirebaseAuthException, IOException { + ListUsersResult result = new ListUsersResult( + ImmutableList.of( + newUser("user0", REDACTED_BASE64), + newUser("user1", REDACTED_BASE64), + newUser("user2", REDACTED_BASE64)), + ListUsersPage.END_OF_LIST); + TestUserSource source = new TestUserSource(result); + ListUsersPage page = new ListUsersPage.PageFactory(source).create(); + assertFalse(page.hasNextPage()); + assertEquals(ListUsersPage.END_OF_LIST, page.getNextPageToken()); + assertNull(page.getNextPage()); + + ImmutableList users = ImmutableList.copyOf(page.getValues()); + assertEquals(3, users.size()); + for (int i = 0; i < 3; i++) { + assertEquals("user" + i, users.get(i).getUid()); + assertNull(users.get(i).getPasswordHash()); + } + assertEquals(1, source.calls.size()); + assertNull(source.calls.get(0)); + } + @Test public void testMultiplePages() throws FirebaseAuthException, IOException { ListUsersResult result = new ListUsersResult( @@ -326,6 +354,14 @@ private static ExportedUserRecord newUser(String uid) throws IOException { return new ExportedUserRecord(parsed, jsonFactory); } + private static ExportedUserRecord newUser(String uid, String passwordHash) throws IOException { + JsonFactory jsonFactory = Utils.getDefaultJsonFactory(); + DownloadAccountResponse.User parsed = jsonFactory.fromString( + String.format("{\"localId\":\"%s\", \"passwordHash\":\"%s\"}", uid, passwordHash), + DownloadAccountResponse.User.class); + return new ExportedUserRecord(parsed, jsonFactory); + } + private static class TestUserSource implements ListUsersPage.UserSource { private ListUsersResult result;