Skip to content

Commit fa7c858

Browse files
authored
Make user operations tenant-aware. (#387)
This makes user operations tenant-aware. I've added some integration tests to ensure that this is working correctly. This is part of the initiative to adding multi-tenancy support (see issue #332).
1 parent 0a09db1 commit fa7c858

File tree

13 files changed

+480
-120
lines changed

13 files changed

+480
-120
lines changed

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

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,13 @@ public abstract class AbstractFirebaseAuth {
5959
private final Supplier<? extends FirebaseUserManager> userManager;
6060
private final JsonFactory jsonFactory;
6161

62-
AbstractFirebaseAuth(
63-
final FirebaseApp firebaseApp,
64-
Supplier<FirebaseTokenFactory> tokenFactory,
65-
Supplier<? extends FirebaseTokenVerifier> idTokenVerifier,
66-
Supplier<? extends FirebaseTokenVerifier> cookieVerifier) {
67-
this.firebaseApp = checkNotNull(firebaseApp);
68-
this.tokenFactory = threadSafeMemoize(tokenFactory);
69-
this.idTokenVerifier = threadSafeMemoize(idTokenVerifier);
70-
this.cookieVerifier = threadSafeMemoize(cookieVerifier);
71-
this.userManager = threadSafeMemoize(new Supplier<FirebaseUserManager>() {
72-
@Override
73-
public FirebaseUserManager get() {
74-
return new FirebaseUserManager(firebaseApp);
75-
}
76-
});
77-
this.jsonFactory = firebaseApp.getOptions().getJsonFactory();
62+
AbstractFirebaseAuth(Builder builder) {
63+
this.firebaseApp = checkNotNull(builder.firebaseApp);
64+
this.tokenFactory = threadSafeMemoize(builder.tokenFactory);
65+
this.idTokenVerifier = threadSafeMemoize(builder.idTokenVerifier);
66+
this.cookieVerifier = threadSafeMemoize(builder.cookieVerifier);
67+
this.userManager = threadSafeMemoize(builder.userManager);
68+
this.jsonFactory = builder.firebaseApp.getOptions().getJsonFactory();
7869
}
7970

8071
/**
@@ -1101,7 +1092,46 @@ final void destroy() {
11011092
destroyed.set(true);
11021093
}
11031094
}
1104-
1095+
11051096
/** Performs any additional required clean up. */
11061097
protected abstract void doDestroy();
1098+
1099+
static Builder builder() {
1100+
return new Builder();
1101+
}
1102+
1103+
static class Builder {
1104+
protected FirebaseApp firebaseApp;
1105+
private Supplier<FirebaseTokenFactory> tokenFactory;
1106+
private Supplier<? extends FirebaseTokenVerifier> idTokenVerifier;
1107+
private Supplier<? extends FirebaseTokenVerifier> cookieVerifier;
1108+
private Supplier<FirebaseUserManager> userManager;
1109+
1110+
private Builder() {}
1111+
1112+
Builder setFirebaseApp(FirebaseApp firebaseApp) {
1113+
this.firebaseApp = firebaseApp;
1114+
return this;
1115+
}
1116+
1117+
Builder setTokenFactory(Supplier<FirebaseTokenFactory> tokenFactory) {
1118+
this.tokenFactory = tokenFactory;
1119+
return this;
1120+
}
1121+
1122+
Builder setIdTokenVerifier(Supplier<? extends FirebaseTokenVerifier> idTokenVerifier) {
1123+
this.idTokenVerifier = idTokenVerifier;
1124+
return this;
1125+
}
1126+
1127+
Builder setCookieVerifier(Supplier<? extends FirebaseTokenVerifier> cookieVerifier) {
1128+
this.cookieVerifier = cookieVerifier;
1129+
return this;
1130+
}
1131+
1132+
Builder setUserManager(Supplier<FirebaseUserManager> userManager) {
1133+
this.userManager = userManager;
1134+
return this;
1135+
}
1136+
}
11071137
}

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

Lines changed: 33 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
package com.google.firebase.auth;
1818

1919
import com.google.api.client.util.Clock;
20-
import com.google.common.annotations.VisibleForTesting;
2120
import com.google.common.base.Supplier;
2221
import com.google.firebase.FirebaseApp;
2322
import com.google.firebase.ImplFirebaseTrampolines;
23+
import com.google.firebase.auth.AbstractFirebaseAuth.Builder;
2424
import com.google.firebase.auth.internal.FirebaseTokenFactory;
2525
import com.google.firebase.internal.FirebaseService;
2626
import java.util.concurrent.atomic.AtomicBoolean;
@@ -40,12 +40,8 @@ public class FirebaseAuth extends AbstractFirebaseAuth {
4040
private final Supplier<TenantManager> tenantManager;
4141
private final AtomicBoolean tenantManagerCreated = new AtomicBoolean(false);
4242

43-
private FirebaseAuth(final Builder builder) {
44-
super(
45-
builder.firebaseApp,
46-
builder.tokenFactory,
47-
builder.idTokenVerifier,
48-
builder.cookieVerifier);
43+
FirebaseAuth(final Builder builder) {
44+
super(builder);
4945
tenantManager = threadSafeMemoize(new Supplier<TenantManager>() {
5046
@Override
5147
public TenantManager get() {
@@ -92,68 +88,37 @@ protected void doDestroy() {
9288
}
9389

9490
private static FirebaseAuth fromApp(final FirebaseApp app) {
95-
return FirebaseAuth.builder()
96-
.setFirebaseApp(app)
97-
.setTokenFactory(
98-
new Supplier<FirebaseTokenFactory>() {
99-
@Override
100-
public FirebaseTokenFactory get() {
101-
return FirebaseTokenUtils.createTokenFactory(app, Clock.SYSTEM);
102-
}
91+
return new FirebaseAuth(
92+
AbstractFirebaseAuth.builder()
93+
.setFirebaseApp(app)
94+
.setTokenFactory(
95+
new Supplier<FirebaseTokenFactory>() {
96+
@Override
97+
public FirebaseTokenFactory get() {
98+
return FirebaseTokenUtils.createTokenFactory(app, Clock.SYSTEM);
99+
}
100+
})
101+
.setIdTokenVerifier(
102+
new Supplier<FirebaseTokenVerifier>() {
103+
@Override
104+
public FirebaseTokenVerifier get() {
105+
return FirebaseTokenUtils.createIdTokenVerifier(app, Clock.SYSTEM);
106+
}
107+
})
108+
.setCookieVerifier(
109+
new Supplier<FirebaseTokenVerifier>() {
110+
@Override
111+
public FirebaseTokenVerifier get() {
112+
return FirebaseTokenUtils.createSessionCookieVerifier(app, Clock.SYSTEM);
113+
}
103114
})
104-
.setIdTokenVerifier(
105-
new Supplier<FirebaseTokenVerifier>() {
106-
@Override
107-
public FirebaseTokenVerifier get() {
108-
return FirebaseTokenUtils.createIdTokenVerifier(app, Clock.SYSTEM);
109-
}
110-
})
111-
.setCookieVerifier(
112-
new Supplier<FirebaseTokenVerifier>() {
113-
@Override
114-
public FirebaseTokenVerifier get() {
115-
return FirebaseTokenUtils.createSessionCookieVerifier(app, Clock.SYSTEM);
116-
}
117-
})
118-
.build();
119-
}
120-
121-
@VisibleForTesting
122-
static Builder builder() {
123-
return new Builder();
124-
}
125-
126-
static class Builder {
127-
private FirebaseApp firebaseApp;
128-
private Supplier<FirebaseTokenFactory> tokenFactory;
129-
private Supplier<? extends FirebaseTokenVerifier> idTokenVerifier;
130-
private Supplier<? extends FirebaseTokenVerifier> cookieVerifier;
131-
132-
private Builder() {}
133-
134-
Builder setFirebaseApp(FirebaseApp firebaseApp) {
135-
this.firebaseApp = firebaseApp;
136-
return this;
137-
}
138-
139-
Builder setTokenFactory(Supplier<FirebaseTokenFactory> tokenFactory) {
140-
this.tokenFactory = tokenFactory;
141-
return this;
142-
}
143-
144-
Builder setIdTokenVerifier(Supplier<? extends FirebaseTokenVerifier> idTokenVerifier) {
145-
this.idTokenVerifier = idTokenVerifier;
146-
return this;
147-
}
148-
149-
Builder setCookieVerifier(Supplier<? extends FirebaseTokenVerifier> cookieVerifier) {
150-
this.cookieVerifier = cookieVerifier;
151-
return this;
152-
}
153-
154-
FirebaseAuth build() {
155-
return new FirebaseAuth(this);
156-
}
115+
.setUserManager(
116+
new Supplier<FirebaseUserManager>() {
117+
@Override
118+
public FirebaseUserManager get() {
119+
return new FirebaseUserManager(app);
120+
}
121+
}));
157122
}
158123

159124
private static class FirebaseAuthService extends FirebaseService<FirebaseAuth> {

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public String getUid() {
4242
return (String) claims.get("sub");
4343
}
4444

45+
/** Returns the tenant ID for the this token. */
46+
public String getTenantId() {
47+
return (String) claims.get("tenant_id");
48+
}
49+
4550
/** Returns the Issuer for the this token. */
4651
public String getIssuer() {
4752
return (String) claims.get("iss");
@@ -57,14 +62,14 @@ public String getPicture() {
5762
return (String) claims.get("picture");
5863
}
5964

60-
/**
65+
/**
6166
* Returns the e-mail address for this user, or {@code null} if it's unavailable.
6267
*/
6368
public String getEmail() {
6469
return (String) claims.get("email");
6570
}
6671

67-
/**
72+
/**
6873
* Indicates if the email address returned by {@link #getEmail()} has been verified as good.
6974
*/
7075
public boolean isEmailVerified() {

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

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,21 +115,40 @@ class FirebaseUserManager {
115115
* Creates a new FirebaseUserManager instance.
116116
*
117117
* @param app A non-null {@link FirebaseApp}.
118+
* @param tenantId The associated tenant ID if the user operations should be tenant-aware,
119+
* otherwise {@code null}
118120
*/
119-
FirebaseUserManager(@NonNull FirebaseApp app) {
121+
FirebaseUserManager(@NonNull FirebaseApp app, @Nullable String tenantId) {
120122
checkNotNull(app, "FirebaseApp must not be null");
121123
String projectId = ImplFirebaseTrampolines.getProjectId(app);
122124
checkArgument(!Strings.isNullOrEmpty(projectId),
123125
"Project ID is required to access the auth service. Use a service account credential or "
124126
+ "set the project ID explicitly via FirebaseOptions. Alternatively you can also "
125127
+ "set the project ID via the GOOGLE_CLOUD_PROJECT environment variable.");
126-
this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
128+
if (tenantId == null) {
129+
this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
130+
} else {
131+
checkArgument(!tenantId.isEmpty(), "tenant ID must not be empty");
132+
this.userMgtBaseUrl =
133+
String.format(ID_TOOLKIT_URL, "v1", projectId) + getTenantUrlSuffix(tenantId);
134+
}
127135
this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
128136
this.jsonFactory = app.getOptions().getJsonFactory();
129137
HttpTransport transport = app.getOptions().getHttpTransport();
130138
this.requestFactory = transport.createRequestFactory(new FirebaseRequestInitializer(app));
131139
}
132140

141+
/**
142+
* Creates a new FirebaseUserManager instance.
143+
*
144+
* <p>This convenience constructor is for when user operations should not be tenant-aware.
145+
*
146+
* @param app A non-null {@link FirebaseApp}.
147+
*/
148+
FirebaseUserManager(@NonNull FirebaseApp app) {
149+
this(app, null);
150+
}
151+
133152
@VisibleForTesting
134153
void setInterceptor(HttpResponseInterceptor interceptor) {
135154
this.interceptor = interceptor;
@@ -230,7 +249,7 @@ UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthExcep
230249
}
231250

232251
Tenant getTenant(String tenantId) throws FirebaseAuthException {
233-
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants/" + tenantId);
252+
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(tenantId));
234253
return sendRequest("GET", url, null, Tenant.class);
235254
}
236255

@@ -242,7 +261,7 @@ Tenant createTenant(Tenant.CreateRequest request) throws FirebaseAuthException {
242261
Tenant updateTenant(Tenant.UpdateRequest request) throws FirebaseAuthException {
243262
Map<String, Object> properties = request.getProperties();
244263
checkArgument(!properties.isEmpty(), "tenant update must have at least one property set");
245-
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants/" + request.getTenantId());
264+
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(request.getTenantId()));
246265
url.put("updateMask", generateMask(properties));
247266
return sendRequest("PATCH", url, properties, Tenant.class);
248267
}
@@ -256,7 +275,7 @@ private static String generateMask(Map<String, Object> properties) {
256275
}
257276

258277
void deleteTenant(String tenantId) throws FirebaseAuthException {
259-
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants/" + tenantId);
278+
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + getTenantUrlSuffix(tenantId));
260279
sendRequest("DELETE", url, null, GenericJson.class);
261280
}
262281

@@ -312,6 +331,11 @@ String getEmailActionLink(EmailLinkType type, String email,
312331
throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
313332
}
314333

334+
private static String getTenantUrlSuffix(String tenantId) {
335+
checkArgument(!Strings.isNullOrEmpty(tenantId));
336+
return "/tenants/" + tenantId;
337+
}
338+
315339
private <T> T post(String path, Object content, Class<T> clazz) throws FirebaseAuthException {
316340
checkArgument(!Strings.isNullOrEmpty(path), "path must not be null or empty");
317341
checkNotNull(content, "content must not be null for POST requests");

0 commit comments

Comments
 (0)