Skip to content

Commit 87ce0ed

Browse files
authored
Remove (base64) 'REDACTED' passwords from user records. (#314)
1 parent d01b062 commit 87ce0ed

File tree

4 files changed

+55
-5
lines changed

4 files changed

+55
-5
lines changed

CONTRIBUTING.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,13 @@ Create a new project in the [Firebase console](https://console.firebase.google.c
136136
not already have one. Use a separate, dedicated project for integration tests since the test suite
137137
makes a large number of writes to the Firebase realtime database. Download the service account
138138
private key from the "Settings > Service Accounts" page of the project, and save it as
139-
`integration_cert.json` at the root of the codebase. Also obtain the web API key of the project
140-
from the "Settings > General" page, and save it as `integration_apikey.txt` at the root of the
141-
codebase. Now run the following command to invoke the integration test suite:
139+
`integration_cert.json` at the root of the codebase. Grant your service account the `Firebase
140+
Authentication Admin` role at
141+
[Google Cloud Platform Console / IAM & admin](https://console.cloud.google.com/iam-admin). This is
142+
required to ensure that exported user records contain the password hashes of the user accounts.
143+
Also obtain the web API key of the project from the "Settings > General" page, and save it as
144+
`integration_apikey.txt` at the root of the codebase. Now run the following command to invoke the
145+
integration test suite:
142146

143147
```
144148
mvn verify

src/main/java/com/google/firebase/auth/ExportedUserRecord.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.firebase.auth;
1818

1919
import com.google.api.client.json.JsonFactory;
20+
import com.google.common.io.BaseEncoding;
2021
import com.google.firebase.auth.internal.DownloadAccountResponse.User;
2122
import com.google.firebase.internal.Nullable;
2223

@@ -28,10 +29,17 @@ public class ExportedUserRecord extends UserRecord {
2829

2930
private final String passwordHash;
3031
private final String passwordSalt;
32+
private static final String REDACTED_BASE64 = BaseEncoding.base64Url().encode(
33+
"REDACTED".getBytes());
3134

3235
ExportedUserRecord(User response, JsonFactory jsonFactory) {
3336
super(response, jsonFactory);
34-
this.passwordHash = response.getPasswordHash();
37+
String passwordHash = response.getPasswordHash();
38+
if (passwordHash != null && !passwordHash.equals(REDACTED_BASE64)) {
39+
this.passwordHash = passwordHash;
40+
} else {
41+
this.passwordHash = null;
42+
}
3543
this.passwordSalt = response.getPasswordSalt();
3644
}
3745

src/test/java/com/google/firebase/auth/FirebaseAuthIT.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ public void testListUsers() throws Exception {
264264
for (ExportedUserRecord user : page.getValues()) {
265265
if (uids.contains(user.getUid())) {
266266
collected.incrementAndGet();
267-
assertNotNull(user.getPasswordHash());
267+
assertNotNull("Missing passwordHash field. A common cause would be "
268+
+ "forgetting to add the \"Firebase Authentication Admin\" permission. See "
269+
+ "instructions in CONTRIBUTING.md", user.getPasswordHash());
268270
assertNotNull(user.getPasswordSalt());
269271
}
270272
}

src/test/java/com/google/firebase/auth/ListUsersPageTest.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.api.client.googleapis.util.Utils;
2727
import com.google.api.client.json.JsonFactory;
2828
import com.google.common.collect.ImmutableList;
29+
import com.google.common.io.BaseEncoding;
2930
import com.google.firebase.auth.ListUsersPage.ListUsersResult;
3031
import com.google.firebase.auth.internal.DownloadAccountResponse;
3132
import java.io.IOException;
@@ -38,6 +39,9 @@
3839

3940
public class ListUsersPageTest {
4041

42+
private static final String REDACTED_BASE64 = BaseEncoding.base64Url().encode(
43+
"REDACTED".getBytes());
44+
4145
@Test
4246
public void testSinglePage() throws FirebaseAuthException, IOException {
4347
TestUserSource source = new TestUserSource(3);
@@ -55,6 +59,30 @@ public void testSinglePage() throws FirebaseAuthException, IOException {
5559
assertNull(source.calls.get(0));
5660
}
5761

62+
@Test
63+
public void testRedactedPasswords() throws FirebaseAuthException, IOException {
64+
ListUsersResult result = new ListUsersResult(
65+
ImmutableList.of(
66+
newUser("user0", REDACTED_BASE64),
67+
newUser("user1", REDACTED_BASE64),
68+
newUser("user2", REDACTED_BASE64)),
69+
ListUsersPage.END_OF_LIST);
70+
TestUserSource source = new TestUserSource(result);
71+
ListUsersPage page = new ListUsersPage.PageFactory(source).create();
72+
assertFalse(page.hasNextPage());
73+
assertEquals(ListUsersPage.END_OF_LIST, page.getNextPageToken());
74+
assertNull(page.getNextPage());
75+
76+
ImmutableList<ExportedUserRecord> users = ImmutableList.copyOf(page.getValues());
77+
assertEquals(3, users.size());
78+
for (int i = 0; i < 3; i++) {
79+
assertEquals("user" + i, users.get(i).getUid());
80+
assertNull(users.get(i).getPasswordHash());
81+
}
82+
assertEquals(1, source.calls.size());
83+
assertNull(source.calls.get(0));
84+
}
85+
5886
@Test
5987
public void testMultiplePages() throws FirebaseAuthException, IOException {
6088
ListUsersResult result = new ListUsersResult(
@@ -326,6 +354,14 @@ private static ExportedUserRecord newUser(String uid) throws IOException {
326354
return new ExportedUserRecord(parsed, jsonFactory);
327355
}
328356

357+
private static ExportedUserRecord newUser(String uid, String passwordHash) throws IOException {
358+
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
359+
DownloadAccountResponse.User parsed = jsonFactory.fromString(
360+
String.format("{\"localId\":\"%s\", \"passwordHash\":\"%s\"}", uid, passwordHash),
361+
DownloadAccountResponse.User.class);
362+
return new ExportedUserRecord(parsed, jsonFactory);
363+
}
364+
329365
private static class TestUserSource implements ListUsersPage.UserSource {
330366

331367
private ListUsersResult result;

0 commit comments

Comments
 (0)