Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
code.
- [fixed] FCM errors sent by the back-end now include more details
that are helpful when debugging problems.
- [changed] Migrated the `FirebaseAuth` user management API to the
new Identity Toolkit endpoint.

# v6.5.0

Expand Down
54 changes: 38 additions & 16 deletions src/main/java/com/google/firebase/auth/FirebaseUserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
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.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
Expand All @@ -36,6 +37,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.firebase.FirebaseApp;
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.DownloadAccountResponse;
Expand All @@ -45,7 +47,9 @@
import com.google.firebase.auth.internal.UploadAccountResponse;
import com.google.firebase.internal.FirebaseRequestInitializer;
import com.google.firebase.internal.NonNull;
import com.google.firebase.internal.Nullable;
import com.google.firebase.internal.SdkUtils;

import java.io.IOException;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -91,9 +95,10 @@ class FirebaseUserManager {
"iss", "jti", "nbf", "nonce", "sub", "firebase");

private static final String ID_TOOLKIT_URL =
"https://www.googleapis.com/identitytoolkit/v3/relyingparty/";
"https://identitytoolkit.googleapis.com/v1/projects/%s";
private static final String CLIENT_VERSION_HEADER = "X-Client-Version";

private final String baseUrl;
private final JsonFactory jsonFactory;
private final HttpRequestFactory requestFactory;
private final String clientVersion = "Java/Admin/" + SdkUtils.getVersion();
Expand All @@ -107,6 +112,12 @@ class FirebaseUserManager {
*/
FirebaseUserManager(@NonNull FirebaseApp app) {
checkNotNull(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();
HttpTransport transport = app.getOptions().getHttpTransport();
this.requestFactory = transport.createRequestFactory(new FirebaseRequestInitializer(app));
Expand All @@ -121,7 +132,7 @@ UserRecord getUserById(String uid) throws FirebaseAuthException {
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
"localId", ImmutableList.of(uid));
GetAccountInfoResponse response = post(
"getAccountInfo", payload, GetAccountInfoResponse.class);
"/accounts:lookup", payload, GetAccountInfoResponse.class);
if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
"No user record found for the provided user ID: " + uid);
Expand All @@ -133,7 +144,7 @@ UserRecord getUserByEmail(String email) throws FirebaseAuthException {
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
"email", ImmutableList.of(email));
GetAccountInfoResponse response = post(
"getAccountInfo", payload, GetAccountInfoResponse.class);
"/accounts:lookup", payload, GetAccountInfoResponse.class);
if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
"No user record found for the provided email: " + email);
Expand All @@ -145,7 +156,7 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
"phoneNumber", ImmutableList.of(phoneNumber));
GetAccountInfoResponse response = post(
"getAccountInfo", payload, GetAccountInfoResponse.class);
"/accounts:lookup", payload, GetAccountInfoResponse.class);
if (response == null || response.getUsers() == null || response.getUsers().isEmpty()) {
throw new FirebaseAuthException(USER_NOT_FOUND_ERROR,
"No user record found for the provided phone number: " + phoneNumber);
Expand All @@ -155,7 +166,7 @@ UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException

String createUser(CreateRequest request) throws FirebaseAuthException {
GenericJson response = post(
"signupNewUser", request.getProperties(), GenericJson.class);
"/accounts", request.getProperties(), GenericJson.class);
if (response != null) {
String uid = (String) response.get("localId");
if (!Strings.isNullOrEmpty(uid)) {
Expand All @@ -167,7 +178,7 @@ String createUser(CreateRequest request) throws FirebaseAuthException {

void updateUser(UpdateRequest request, JsonFactory jsonFactory) throws FirebaseAuthException {
GenericJson response = post(
"setAccountInfo", request.getProperties(jsonFactory), GenericJson.class);
"/accounts:update", request.getProperties(jsonFactory), GenericJson.class);
if (response == null || !request.getUid().equals(response.get("localId"))) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to update user: " + request.getUid());
}
Expand All @@ -176,7 +187,7 @@ void updateUser(UpdateRequest request, JsonFactory jsonFactory) throws FirebaseA
void deleteUser(String uid) throws FirebaseAuthException {
final Map<String, Object> payload = ImmutableMap.<String, Object>of("localId", uid);
GenericJson response = post(
"deleteAccount", payload, GenericJson.class);
"/accounts:delete", payload, GenericJson.class);
if (response == null || !response.containsKey("kind")) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to delete user: " + uid);
}
Expand All @@ -190,8 +201,10 @@ DownloadAccountResponse listUsers(int maxResults, String pageToken) throws Fireb
builder.put("nextPageToken", pageToken);
}

DownloadAccountResponse response = post(
"downloadAccount", builder.build(), DownloadAccountResponse.class);
GenericUrl url = new GenericUrl(baseUrl + "/accounts:batchGet");
url.putAll(builder.build());
DownloadAccountResponse response = sendRequest(
"GET", url, null, DownloadAccountResponse.class);
if (response == null) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to retrieve users.");
}
Expand All @@ -200,7 +213,8 @@ DownloadAccountResponse listUsers(int maxResults, String pageToken) throws Fireb

UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthException {
checkNotNull(request);
UploadAccountResponse response = post("uploadAccount", request, UploadAccountResponse.class);
UploadAccountResponse response = post(
"/accounts:batchCreate", request, UploadAccountResponse.class);
if (response == null) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to import users.");
}
Expand All @@ -211,7 +225,7 @@ String createSessionCookie(String idToken,
SessionCookieOptions options) throws FirebaseAuthException {
final Map<String, Object> payload = ImmutableMap.<String, Object>of(
"idToken", idToken, "validDuration", options.getExpiresInSeconds());
GenericJson response = post("createSessionCookie", payload, GenericJson.class);
GenericJson response = post(":createSessionCookie", payload, GenericJson.class);
if (response != null) {
String cookie = (String) response.get("sessionCookie");
if (!Strings.isNullOrEmpty(cookie)) {
Expand All @@ -223,14 +237,22 @@ String createSessionCookie(String idToken,

private <T> T post(String path, Object content, Class<T> clazz) throws FirebaseAuthException {
checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
checkNotNull(content, "content must not be null");
checkNotNull(clazz, "response class must not be null");
checkNotNull(content, "content must not be null for POST requests");
GenericUrl url = new GenericUrl(baseUrl + path);
return sendRequest("POST", url, content, clazz);
}

GenericUrl url = new GenericUrl(ID_TOOLKIT_URL + path);
private <T> T sendRequest(
String method, GenericUrl url,
@Nullable Object content, Class<T> clazz) throws FirebaseAuthException {

checkArgument(!Strings.isNullOrEmpty(method), "method must not be null or empty");
checkNotNull(url, "url must not be null");
checkNotNull(clazz, "response class must not be null");
HttpResponse response = null;
try {
HttpRequest request = requestFactory.buildPostRequest(url,
new JsonHttpContent(jsonFactory, content));
HttpContent httpContent = content != null ? new JsonHttpContent(jsonFactory, content) : null;
HttpRequest request = requestFactory.buildRequest(method, url, httpContent);
request.setParser(new JsonObjectParser(jsonFactory));
request.getHeaders().set(CLIENT_VERSION_HEADER, clientVersion);
request.setResponseInterceptor(interceptor);
Expand Down
6 changes: 4 additions & 2 deletions src/test/java/com/google/firebase/auth/FirebaseAuthTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import com.google.auth.oauth2.UserCredentials;
import com.google.common.base.Defaults;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.ImplFirebaseTrampolines;
Expand Down Expand Up @@ -98,7 +97,10 @@ public static Collection<Object[]> data() throws Exception {
/* isCertCredential */ true
},
{
new FirebaseOptions.Builder().setCredentials(createRefreshTokenCredential()).build(),
new FirebaseOptions.Builder()
.setCredentials(createRefreshTokenCredential())
.setProjectId("test-project-id")
.build(),
/* isCertCredential */ false
},
{
Expand Down
39 changes: 24 additions & 15 deletions src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import static org.junit.Assert.fail;

import com.google.api.client.googleapis.util.Utils;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponseException;
Expand Down Expand Up @@ -67,6 +68,18 @@ public void tearDown() {
TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
}

@Test
public void testProjectIdRequired() {
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.build());
try {
FirebaseAuth.getInstance();
fail("No error thrown for missing project ID");
} catch (IllegalArgumentException expected) {
}
}

@Test
public void testGetUser() throws Exception {
TestResponseInterceptor interceptor = initializeAppForUserManagement(
Expand Down Expand Up @@ -149,12 +162,9 @@ public void testListUsers() throws Exception {
assertEquals("", page.getNextPageToken());
checkRequestHeaders(interceptor);

ByteArrayOutputStream out = new ByteArrayOutputStream();
interceptor.getResponse().getRequest().getContent().writeTo(out);
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
GenericJson parsed = jsonFactory.fromString(new String(out.toByteArray()), GenericJson.class);
assertEquals(new BigDecimal(999), parsed.get("maxResults"));
assertNull(parsed.get("nextPageToken"));
GenericUrl url = interceptor.getResponse().getRequest().getUrl();
assertEquals(999, url.getFirst("maxResults"));
assertNull(url.getFirst("nextPageToken"));
}

@Test
Expand All @@ -171,12 +181,9 @@ public void testListUsersWithPageToken() throws Exception {
assertEquals("", page.getNextPageToken());
checkRequestHeaders(interceptor);

ByteArrayOutputStream out = new ByteArrayOutputStream();
interceptor.getResponse().getRequest().getContent().writeTo(out);
JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
GenericJson parsed = jsonFactory.fromString(new String(out.toByteArray()), GenericJson.class);
assertEquals(new BigDecimal(999), parsed.get("maxResults"));
assertEquals("token", parsed.get("nextPageToken"));
GenericUrl url = interceptor.getResponse().getRequest().getUrl();
assertEquals(999, url.getFirst("maxResults"));
assertEquals("token", url.getFirst("nextPageToken"));
}

@Test
Expand Down Expand Up @@ -427,9 +434,7 @@ public void testCreateSessionCookie() throws Exception {

@Test
public void testCreateSessionCookieInvalidArguments() {
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.build());
initializeAppForUserManagement();
SessionCookieOptions options = SessionCookieOptions.builder()
.setExpiresIn(TimeUnit.HOURS.toMillis(1))
.build();
Expand Down Expand Up @@ -532,6 +537,7 @@ public void call(FirebaseAuth auth) throws Exception {
.build();
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId("test-project-id")
.setHttpTransport(transport)
.build());

Expand Down Expand Up @@ -596,6 +602,7 @@ public void testGetUserUnexpectedHttpError() throws Exception {
.build();
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId("test-project-id")
.setHttpTransport(transport)
.build());
try {
Expand All @@ -617,6 +624,7 @@ public void testTimeout() throws Exception {
new MockLowLevelHttpResponse().setContent(TestUtils.loadResource("getUser.json"))));
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId("test-project-id")
.setHttpTransport(transport)
.setConnectTimeout(30000)
.setReadTimeout(60000)
Expand Down Expand Up @@ -989,6 +997,7 @@ private static TestResponseInterceptor initializeAppForUserManagement(String ...
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setHttpTransport(transport)
.setProjectId("test-project-id")
.build());
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUserManager userManager = auth.getUserManager();
Expand Down