Skip to content

Commit 1e26ef3

Browse files
ssbushi7agustibm
andauthored
feat(auth): Add auth emulator support (#523)
* Add Firebase Auth emulator support - UserManager & TenantClient(#510) * Adds Firebase Auth emulator support for FirebaseUserManager and FirebaseTenantClient classes. * Adds tests and minor fixes * Remove new dependency from tests * Test integrations on CI * Add unit tests for Auth emulator support * Lint fixes * Add more test * Fix and clean tests * Address review comments * Minor fixes * More review comment changes * Final fixes * Fix typo Co-authored-by: Agustí Becerra Milà <[email protected]>
1 parent d8b1583 commit 1e26ef3

15 files changed

+806
-133
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
2020
import static com.google.common.base.Preconditions.checkNotNull;
21+
import static com.google.firebase.auth.internal.Utils.isEmulatorMode;
2122

2223
import com.google.api.client.json.JsonFactory;
2324
import com.google.api.client.util.Clock;
@@ -309,7 +310,7 @@ protected FirebaseToken execute() throws FirebaseAuthException {
309310
@VisibleForTesting
310311
FirebaseTokenVerifier getIdTokenVerifier(boolean checkRevoked) {
311312
FirebaseTokenVerifier verifier = idTokenVerifier.get();
312-
if (checkRevoked) {
313+
if (checkRevoked || isEmulatorMode()) {
313314
FirebaseUserManager userManager = getUserManager();
314315
verifier = RevocationCheckDecorator.decorateIdTokenVerifier(verifier, userManager);
315316
}
@@ -389,7 +390,7 @@ public FirebaseToken execute() throws FirebaseAuthException {
389390
@VisibleForTesting
390391
FirebaseTokenVerifier getSessionCookieVerifier(boolean checkRevoked) {
391392
FirebaseTokenVerifier verifier = cookieVerifier.get();
392-
if (checkRevoked) {
393+
if (checkRevoked || isEmulatorMode()) {
393394
FirebaseUserManager userManager = getUserManager();
394395
verifier = RevocationCheckDecorator.decorateSessionCookieVerifier(verifier, userManager);
395396
}

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import com.google.common.base.Joiner;
3030
import com.google.common.base.Strings;
3131
import com.google.firebase.ErrorCode;
32+
import com.google.firebase.auth.internal.Utils;
3233
import com.google.firebase.internal.Nullable;
3334
import java.io.IOException;
3435
import java.math.BigDecimal;
@@ -94,9 +95,12 @@ private FirebaseTokenVerifierImpl(Builder builder) {
9495
*/
9596
@Override
9697
public FirebaseToken verifyToken(String token) throws FirebaseAuthException {
98+
boolean isEmulatorMode = Utils.isEmulatorMode();
9799
IdToken idToken = parse(token);
98-
checkContents(idToken);
99-
checkSignature(idToken);
100+
checkContents(idToken, isEmulatorMode);
101+
if (!isEmulatorMode) {
102+
checkSignature(idToken);
103+
}
100104
FirebaseToken firebaseToken = new FirebaseToken(idToken.getPayload());
101105
checkTenantId(firebaseToken);
102106
return firebaseToken;
@@ -160,17 +164,18 @@ private void checkSignature(IdToken token) throws FirebaseAuthException {
160164
}
161165
}
162166

163-
private void checkContents(final IdToken idToken) throws FirebaseAuthException {
167+
private void checkContents(final IdToken idToken, boolean isEmulatorMode)
168+
throws FirebaseAuthException {
164169
final Header header = idToken.getHeader();
165170
final Payload payload = idToken.getPayload();
166171

167172
final long currentTimeMillis = idTokenVerifier.getClock().currentTimeMillis();
168173
String errorMessage = null;
169174
AuthErrorCode errorCode = invalidTokenErrorCode;
170175

171-
if (header.getKeyId() == null) {
176+
if (!isEmulatorMode && header.getKeyId() == null) {
172177
errorMessage = getErrorForTokenWithoutKid(header, payload);
173-
} else if (!RS256.equals(header.getAlgorithm())) {
178+
} else if (!isEmulatorMode && !RS256.equals(header.getAlgorithm())) {
174179
errorMessage = String.format(
175180
"Firebase %s has incorrect algorithm. Expected \"%s\" but got \"%s\".",
176181
shortName,

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.google.firebase.auth.internal.ListOidcProviderConfigsResponse;
4242
import com.google.firebase.auth.internal.ListSamlProviderConfigsResponse;
4343
import com.google.firebase.auth.internal.UploadAccountResponse;
44+
import com.google.firebase.auth.internal.Utils;
4445
import com.google.firebase.internal.ApiClientUtils;
4546
import com.google.firebase.internal.HttpRequestInfo;
4647
import com.google.firebase.internal.NonNull;
@@ -72,6 +73,8 @@ final class FirebaseUserManager {
7273

7374
private static final String ID_TOOLKIT_URL =
7475
"https://identitytoolkit.googleapis.com/%s/projects/%s";
76+
private static final String ID_TOOLKIT_URL_EMULATOR =
77+
"http://%s/identitytoolkit.googleapis.com/%s/projects/%s";
7578

7679
private final String userMgtBaseUrl;
7780
private final String idpConfigMgtBaseUrl;
@@ -85,8 +88,8 @@ private FirebaseUserManager(Builder builder) {
8588
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
8689
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
8790
this.jsonFactory = checkNotNull(builder.jsonFactory, "JsonFactory must not be null");
88-
final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
89-
final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
91+
final String idToolkitUrlV1 = getIdToolkitUrl(projectId, "v1");
92+
final String idToolkitUrlV2 = getIdToolkitUrl(projectId, "v2");
9093
final String tenantId = builder.tenantId;
9194
if (tenantId == null) {
9295
this.userMgtBaseUrl = idToolkitUrlV1;
@@ -100,6 +103,13 @@ private FirebaseUserManager(Builder builder) {
100103
this.httpClient = new AuthHttpClient(jsonFactory, builder.requestFactory);
101104
}
102105

106+
private String getIdToolkitUrl(String projectId, String version) {
107+
if (Utils.isEmulatorMode()) {
108+
return String.format(ID_TOOLKIT_URL_EMULATOR, Utils.getEmulatorHost(), version, projectId);
109+
}
110+
return String.format(ID_TOOLKIT_URL, version, projectId);
111+
}
112+
103113
@VisibleForTesting
104114
void setInterceptor(HttpResponseInterceptor interceptor) {
105115
httpClient.setInterceptor(interceptor);

src/main/java/com/google/firebase/auth/internal/CryptoSigners.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.firebase.FirebaseException;
2323
import com.google.firebase.ImplFirebaseTrampolines;
2424
import com.google.firebase.auth.FirebaseAuthException;
25+
import com.google.firebase.auth.internal.Utils;
2526
import com.google.firebase.internal.AbstractPlatformErrorHandler;
2627
import com.google.firebase.internal.ApiClientUtils;
2728
import com.google.firebase.internal.ErrorHandlingHttpClient;
@@ -108,6 +109,25 @@ public String getAccount() {
108109
}
109110
}
110111

112+
/**
113+
* A {@link CryptoSigner} implementation that doesn't sign data. For use with the Auth Emulator
114+
* only
115+
*/
116+
public static class EmulatorCryptoSigner implements CryptoSigner {
117+
118+
private static final String ACCOUNT = "[email protected]";
119+
120+
@Override
121+
public byte[] sign(byte[] payload) {
122+
return "".getBytes();
123+
}
124+
125+
@Override
126+
public String getAccount() {
127+
return ACCOUNT;
128+
}
129+
}
130+
111131
private static class IAMErrorHandler
112132
extends AbstractPlatformErrorHandler<FirebaseAuthException> {
113133

@@ -126,6 +146,10 @@ protected FirebaseAuthException createException(FirebaseException base) {
126146
* documented at go/firebase-admin-sign.
127147
*/
128148
public static CryptoSigner getCryptoSigner(FirebaseApp firebaseApp) throws IOException {
149+
if (Utils.isEmulatorMode()) {
150+
return new EmulatorCryptoSigner();
151+
}
152+
129153
GoogleCredentials credentials = ImplFirebaseTrampolines.getCredentials(firebaseApp);
130154

131155
// If the SDK was initialized with a service account, use it to sign bytes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2021 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.auth.internal;
18+
19+
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.base.Strings;
21+
22+
public class Utils {
23+
@VisibleForTesting
24+
public static final String AUTH_EMULATOR_HOST = "FIREBASE_AUTH_EMULATOR_HOST";
25+
26+
public static boolean isEmulatorMode() {
27+
return !Strings.isNullOrEmpty(getEmulatorHost());
28+
}
29+
30+
public static String getEmulatorHost() {
31+
return System.getenv(AUTH_EMULATOR_HOST);
32+
}
33+
34+
}

src/main/java/com/google/firebase/auth/multitenancy/FirebaseTenantClient.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import com.google.firebase.auth.FirebaseAuthException;
3232
import com.google.firebase.auth.internal.AuthHttpClient;
3333
import com.google.firebase.auth.internal.ListTenantsResponse;
34+
import com.google.firebase.auth.internal.Utils;
3435
import com.google.firebase.internal.ApiClientUtils;
3536
import com.google.firebase.internal.HttpRequestInfo;
3637
import java.util.Map;
@@ -42,6 +43,9 @@ final class FirebaseTenantClient {
4243
private static final String ID_TOOLKIT_URL =
4344
"https://identitytoolkit.googleapis.com/%s/projects/%s";
4445

46+
private static final String ID_TOOLKIT_URL_EMULATOR =
47+
"http://%s/identitytoolkit.googleapis.com/%s/projects/%s";
48+
4549
private final String tenantMgtBaseUrl;
4650
private final AuthHttpClient httpClient;
4751

@@ -58,10 +62,17 @@ final class FirebaseTenantClient {
5862
"Project ID is required to access the auth service. Use a service account credential or "
5963
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
6064
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
61-
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
65+
this.tenantMgtBaseUrl = getTenantMgtBaseUrl(projectId);
6266
this.httpClient = new AuthHttpClient(jsonFactory, requestFactory);
6367
}
6468

69+
private String getTenantMgtBaseUrl(String projectId) {
70+
if (Utils.isEmulatorMode()) {
71+
return String.format(ID_TOOLKIT_URL_EMULATOR, Utils.getEmulatorHost(), "v2", projectId);
72+
}
73+
return String.format(ID_TOOLKIT_URL, "v2", projectId);
74+
}
75+
6576
void setInterceptor(HttpResponseInterceptor interceptor) {
6677
httpClient.setInterceptor(interceptor);
6778
}

0 commit comments

Comments
 (0)