Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.google.firebase.auth.FirebaseUserManager Maven / Gradle / Ivy
/*
* Copyright 2017 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 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;
import com.google.api.client.http.HttpResponseException;
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.json.JsonObjectParser;
import com.google.api.client.util.Key;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
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.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;
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.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
* REST API. This class does not hold any mutable state, and is thread safe.
*
* @see
* Google Identity Toolkit
*/
class FirebaseUserManager {
static final String USER_NOT_FOUND_ERROR = "user-not-found";
static final String INTERNAL_ERROR = "internal-error";
// Map of server-side error codes to SDK error codes.
// SDK error codes defined at: https://firebase.google.com/docs/auth/admin/errors
private static final Map ERROR_CODES = ImmutableMap.builder()
.put("CLAIMS_TOO_LARGE", "claims-too-large")
.put("CONFIGURATION_NOT_FOUND", "project-not-found")
.put("INSUFFICIENT_PERMISSION", "insufficient-permission")
.put("DUPLICATE_EMAIL", "email-already-exists")
.put("DUPLICATE_LOCAL_ID", "uid-already-exists")
.put("EMAIL_EXISTS", "email-already-exists")
.put("INVALID_CLAIMS", "invalid-claims")
.put("INVALID_EMAIL", "invalid-email")
.put("INVALID_PAGE_SELECTION", "invalid-page-token")
.put("INVALID_PHONE_NUMBER", "invalid-phone-number")
.put("PHONE_NUMBER_EXISTS", "phone-number-already-exists")
.put("PROJECT_NOT_FOUND", "project-not-found")
.put("USER_NOT_FOUND", USER_NOT_FOUND_ERROR)
.put("WEAK_PASSWORD", "invalid-password")
.put("UNAUTHORIZED_DOMAIN", "unauthorized-continue-uri")
.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;
static final List RESERVED_CLAIMS = ImmutableList.of(
"amr", "at_hash", "aud", "auth_time", "azp", "cnf", "c_hash", "exp", "iat",
"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 final String baseUrl;
private final JsonFactory jsonFactory;
private final HttpRequestFactory requestFactory;
private final String clientVersion = "Java/Admin/" + SdkUtils.getVersion();
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");
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);
}
this.requestFactory = requestFactory;
}
@VisibleForTesting
void setInterceptor(HttpResponseInterceptor interceptor) {
this.interceptor = interceptor;
}
UserRecord getUserById(String uid) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"localId", ImmutableList.of(uid));
GetAccountInfoResponse response = post(
"/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);
}
return new UserRecord(response.getUsers().get(0), jsonFactory);
}
UserRecord getUserByEmail(String email) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"email", ImmutableList.of(email));
GetAccountInfoResponse response = post(
"/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);
}
return new UserRecord(response.getUsers().get(0), jsonFactory);
}
UserRecord getUserByPhoneNumber(String phoneNumber) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"phoneNumber", ImmutableList.of(phoneNumber));
GetAccountInfoResponse response = post(
"/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);
}
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);
if (response != null) {
String uid = (String) response.get("localId");
if (!Strings.isNullOrEmpty(uid)) {
return uid;
}
}
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create new user");
}
void updateUser(UpdateRequest request, JsonFactory jsonFactory) throws FirebaseAuthException {
GenericJson response = post(
"/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());
}
}
void deleteUser(String uid) throws FirebaseAuthException {
final Map payload = ImmutableMap.of("localId", uid);
GenericJson response = post(
"/accounts:delete", payload, GenericJson.class);
if (response == null || !response.containsKey("kind")) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to delete user: " + uid);
}
}
/**
* @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);
if (pageToken != null) {
checkArgument(!pageToken.equals(ListUsersPage.END_OF_LIST), "invalid end of list page token");
builder.put("nextPageToken", pageToken);
}
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.");
}
return response;
}
UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthException {
checkNotNull(request);
UploadAccountResponse response = post(
"/accounts:batchCreate", request, UploadAccountResponse.class);
if (response == null) {
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to import users.");
}
return new UserImportResult(request.getUsersCount(), response);
}
String createSessionCookie(String idToken,
SessionCookieOptions options) throws FirebaseAuthException {
final Map payload = ImmutableMap.of(
"idToken", idToken, "validDuration", options.getExpiresInSeconds());
GenericJson response = post(":createSessionCookie", payload, GenericJson.class);
if (response != null) {
String cookie = (String) response.get("sessionCookie");
if (!Strings.isNullOrEmpty(cookie)) {
return cookie;
}
}
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create session cookie");
}
String getEmailActionLink(EmailLinkType type, String email,
@Nullable ActionCodeSettings settings) throws FirebaseAuthException {
ImmutableMap.Builder payload = ImmutableMap.builder()
.put("requestType", type.name())
.put("email", email)
.put("returnOobLink", true);
if (settings != null) {
payload.putAll(settings.getProperties());
}
GenericJson response = post("/accounts:sendOobCode", payload.build(), GenericJson.class);
if (response != null) {
String link = (String) response.get("oobLink");
if (!Strings.isNullOrEmpty(link)) {
return link;
}
}
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
}
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");
GenericUrl url = new GenericUrl(baseUrl + path);
return sendRequest("POST", url, content, clazz);
}
private T sendRequest(
String method, GenericUrl url,
@Nullable Object content, Class 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 {
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);
response = request.execute();
return response.parseAs(clazz);
} catch (HttpResponseException e) {
// Server responded with an HTTP error
handleHttpError(e);
return null;
} catch (IOException e) {
// All other IO errors (Connection refused, reset, parse error etc.)
throw new FirebaseAuthException(
INTERNAL_ERROR, "Error while calling user management backend service", e);
} finally {
if (response != null) {
try {
response.disconnect();
} catch (IOException ignored) {
// Ignored
}
}
}
}
private void handleHttpError(HttpResponseException e) throws FirebaseAuthException {
try {
HttpErrorResponse response = jsonFactory.fromString(e.getContent(), HttpErrorResponse.class);
String code = ERROR_CODES.get(response.getErrorCode());
if (code != null) {
throw new FirebaseAuthException(code, "User management service responded with an error", e);
}
} catch (IOException ignored) {
// Ignored
}
String msg = String.format(
"Unexpected HTTP response with status: %d; body: %s", e.getStatusCode(), e.getContent());
throw new FirebaseAuthException(INTERNAL_ERROR, msg, e);
}
static class UserImportRequest extends GenericJson {
@Key("users")
private final List> users;
UserImportRequest(List users, UserImportOptions options,
JsonFactory jsonFactory) {
checkArgument(users != null && !users.isEmpty(), "users must not be null or empty");
checkArgument(users.size() <= FirebaseUserManager.MAX_IMPORT_USERS,
"users list must not contain more than %s items", FirebaseUserManager.MAX_IMPORT_USERS);
boolean hasPassword = false;
ImmutableList.Builder> usersBuilder = ImmutableList.builder();
for (ImportUserRecord user : users) {
if (user.hasPassword()) {
hasPassword = true;
}
usersBuilder.add(user.getProperties(jsonFactory));
}
this.users = usersBuilder.build();
if (hasPassword) {
checkArgument(options != null && options.getHash() != null,
"UserImportHash option is required when at least one user has a password. Provide "
+ "a UserImportHash via UserImportOptions.withHash().");
this.putAll(options.getProperties());
}
}
int getUsersCount() {
return users.size();
}
}
enum EmailLinkType {
VERIFY_EMAIL,
EMAIL_SIGNIN,
PASSWORD_RESET,
}
}