Skip to content

Commit a04fcfa

Browse files
authored
Add deleteTenant operation to TenantManager. (#372)
This adds deleteTenant to the TenantManager class. I've added the relevant unit tests to FirebaseUserManagerTest. This is part of the initiative to adding multi-tenancy support (see issue #332).
1 parent 2679904 commit a04fcfa

File tree

3 files changed

+120
-48
lines changed

3 files changed

+120
-48
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
*/
6666
class FirebaseUserManager {
6767

68+
static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
6869
static final String USER_NOT_FOUND_ERROR = "user-not-found";
6970
static final String INTERNAL_ERROR = "internal-error";
7071

@@ -82,6 +83,7 @@ class FirebaseUserManager {
8283
.put("INVALID_PAGE_SELECTION", "invalid-page-token")
8384
.put("INVALID_PHONE_NUMBER", "invalid-phone-number")
8485
.put("PHONE_NUMBER_EXISTS", "phone-number-already-exists")
86+
.put("TENANT_NOT_FOUND", TENANT_NOT_FOUND_ERROR)
8587
.put("PROJECT_NOT_FOUND", "project-not-found")
8688
.put("USER_NOT_FOUND", USER_NOT_FOUND_ERROR)
8789
.put("WEAK_PASSWORD", "invalid-password")
@@ -226,6 +228,15 @@ UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthExcep
226228
return new UserImportResult(request.getUsersCount(), response);
227229
}
228230

231+
void deleteTenant(String tenantId) throws FirebaseAuthException {
232+
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants/" + tenantId);
233+
GenericJson response = sendRequest("DELETE", url, null, GenericJson.class);
234+
if (response == null) {
235+
throw new FirebaseAuthException(TENANT_NOT_FOUND_ERROR,
236+
"Failed to delete tenant: " + tenantId);
237+
}
238+
}
239+
229240
ListTenantsResponse listTenants(int maxResults, String pageToken)
230241
throws FirebaseAuthException {
231242
ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder()

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package com.google.firebase.auth;
1818

19+
import static com.google.common.base.Preconditions.checkArgument;
1920
import static com.google.common.base.Preconditions.checkNotNull;
2021

2122
import com.google.api.client.json.JsonFactory;
2223
import com.google.api.core.ApiFuture;
24+
import com.google.common.base.Strings;
2325
import com.google.firebase.FirebaseApp;
2426
import com.google.firebase.auth.ListTenantsPage.DefaultTenantSource;
2527
import com.google.firebase.auth.ListTenantsPage.PageFactory;
@@ -115,4 +117,40 @@ protected ListTenantsPage execute() throws FirebaseAuthException {
115117
}
116118
};
117119
}
120+
121+
/**
122+
* Deletes the tenant identified by the specified tenant ID.
123+
*
124+
* @param tenantId A tenant ID string.
125+
* @throws IllegalArgumentException If the tenant ID string is null or empty.
126+
* @throws FirebaseAuthException If an error occurs while deleting the tenant.
127+
*/
128+
public void deleteTenant(@NonNull String tenantId) throws FirebaseAuthException {
129+
deleteTenantOp(tenantId).call();
130+
}
131+
132+
/**
133+
* Similar to {@link #deleteTenant(String)} but performs the operation asynchronously.
134+
*
135+
* @param tenantId A tenant ID string.
136+
* @return An {@code ApiFuture} which will complete successfully when the specified tenant account
137+
* has been deleted. If an error occurs while deleting the tenant account, the future throws a
138+
* {@link FirebaseAuthException}.
139+
* @throws IllegalArgumentException If the tenant ID string is null or empty.
140+
*/
141+
public ApiFuture<Void> deleteTenantAsync(String tenantId) {
142+
return deleteTenantOp(tenantId).callAsync(firebaseApp);
143+
}
144+
145+
private CallableOperation<Void, FirebaseAuthException> deleteTenantOp(final String tenantId) {
146+
// TODO(micahstairs): Add a check to make sure the app has not been destroyed yet.
147+
checkArgument(!Strings.isNullOrEmpty(tenantId), "tenantId must not be null or empty");
148+
return new CallableOperation<Void, FirebaseAuthException>() {
149+
@Override
150+
protected Void execute() throws FirebaseAuthException {
151+
userManager.deleteTenant(tenantId);
152+
return null;
153+
}
154+
};
155+
}
118156
}

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

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
package com.google.firebase.auth;
1818

19+
import static org.hamcrest.core.IsInstanceOf.instanceOf;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertNotNull;
2223
import static org.junit.Assert.assertNull;
24+
import static org.junit.Assert.assertThat;
2325
import static org.junit.Assert.assertTrue;
2426
import static org.junit.Assert.fail;
2527

@@ -74,6 +76,8 @@ public class FirebaseUserManagerTest {
7476
.build();
7577
private static final Map<String, Object> ACTION_CODE_SETTINGS_MAP =
7678
ACTION_CODE_SETTINGS.getProperties();
79+
private static final String TENANTS_BASE_URL =
80+
"https://identitytoolkit.googleapis.com/v2/projects/test-project-id/tenants";
7781

7882
@After
7983
public void tearDown() {
@@ -114,7 +118,7 @@ public void testGetUserWithNotFoundError() throws Exception {
114118
FirebaseAuth.getInstance().getUserAsync("testuser").get();
115119
fail("No error thrown for invalid response");
116120
} catch (ExecutionException e) {
117-
assertTrue(e.getCause() instanceof FirebaseAuthException);
121+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
118122
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
119123
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
120124
}
@@ -137,7 +141,7 @@ public void testGetUserByEmailWithNotFoundError() throws Exception {
137141
FirebaseAuth.getInstance().getUserByEmailAsync("testuser@example.com").get();
138142
fail("No error thrown for invalid response");
139143
} catch (ExecutionException e) {
140-
assertTrue(e.getCause() instanceof FirebaseAuthException);
144+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
141145
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
142146
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
143147
}
@@ -160,7 +164,7 @@ public void testGetUserByPhoneNumberWithNotFoundError() throws Exception {
160164
FirebaseAuth.getInstance().getUserByPhoneNumberAsync("+1234567890").get();
161165
fail("No error thrown for invalid response");
162166
} catch (ExecutionException e) {
163-
assertTrue(e.getCause() instanceof FirebaseAuthException);
167+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
164168
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
165169
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
166170
}
@@ -476,6 +480,32 @@ public void testListZeroTenants() throws Exception {
476480
checkRequestHeaders(interceptor);
477481
}
478482

483+
@Test
484+
public void testDeleteTenant() throws Exception {
485+
TestResponseInterceptor interceptor = initializeAppForUserManagement("{}");
486+
487+
FirebaseAuth.getInstance().getTenantManager().deleteTenantAsync("TENANT_1").get();
488+
489+
checkRequestHeaders(interceptor);
490+
checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/TENANT_1");
491+
}
492+
493+
@Test
494+
public void testDeleteTenantWithNotFoundError() throws Exception {
495+
TestResponseInterceptor interceptor =
496+
initializeAppForUserManagementWithStatusCode(404,
497+
"{\"error\": {\"message\": \"TENANT_NOT_FOUND\"}}");
498+
try {
499+
FirebaseAuth.getInstance().getTenantManager().deleteTenantAsync("UNKNOWN").get();
500+
fail("No error thrown for invalid response");
501+
} catch (ExecutionException e) {
502+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
503+
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
504+
assertEquals(FirebaseUserManager.TENANT_NOT_FOUND_ERROR, authException.getErrorCode());
505+
}
506+
checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/UNKNOWN");
507+
}
508+
479509
@Test
480510
public void testCreateSessionCookie() throws Exception {
481511
TestResponseInterceptor interceptor = initializeAppForUserManagement(
@@ -615,11 +645,11 @@ public void call(FirebaseAuth auth) throws Exception {
615645
operation.call(FirebaseAuth.getInstance());
616646
fail("No error thrown for HTTP error: " + code);
617647
} catch (ExecutionException e) {
618-
assertTrue(e.getCause() instanceof FirebaseAuthException);
648+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
619649
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
620650
String msg = String.format("Unexpected HTTP response with status: %d; body: {}", code);
621651
assertEquals(msg, authException.getMessage());
622-
assertTrue(authException.getCause() instanceof HttpResponseException);
652+
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
623653
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
624654
}
625655
}
@@ -633,10 +663,10 @@ public void call(FirebaseAuth auth) throws Exception {
633663
operation.call(FirebaseAuth.getInstance());
634664
fail("No error thrown for HTTP error");
635665
} catch (ExecutionException e) {
636-
assertTrue(e.getCause().toString(), e.getCause() instanceof FirebaseAuthException);
666+
assertThat(e.getCause().toString(), e.getCause(), instanceOf(FirebaseAuthException.class));
637667
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
638668
assertEquals("User management service responded with an error", authException.getMessage());
639-
assertTrue(authException.getCause() instanceof HttpResponseException);
669+
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
640670
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
641671
}
642672
}
@@ -649,33 +679,23 @@ public void testGetUserMalformedJsonError() throws Exception {
649679
FirebaseAuth.getInstance().getUserAsync("testuser").get();
650680
fail("No error thrown for JSON error");
651681
} catch (ExecutionException e) {
652-
assertTrue(e.getCause() instanceof FirebaseAuthException);
682+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
653683
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
654-
assertTrue(authException.getCause() instanceof IOException);
684+
assertThat(authException.getCause(), instanceOf(IOException.class));
655685
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
656686
}
657687
}
658688

659689
@Test
660690
public void testGetUserUnexpectedHttpError() throws Exception {
661-
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
662-
response.setContent("{\"not\" json}");
663-
response.setStatusCode(500);
664-
MockHttpTransport transport = new MockHttpTransport.Builder()
665-
.setLowLevelHttpResponse(response)
666-
.build();
667-
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
668-
.setCredentials(credentials)
669-
.setProjectId("test-project-id")
670-
.setHttpTransport(transport)
671-
.build());
691+
initializeAppForUserManagementWithStatusCode(500, "{\"not\" json}");
672692
try {
673693
FirebaseAuth.getInstance().getUserAsync("testuser").get();
674694
fail("No error thrown for JSON error");
675695
} catch (ExecutionException e) {
676-
assertTrue(e.getCause() instanceof FirebaseAuthException);
696+
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
677697
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
678-
assertTrue(authException.getCause() instanceof HttpResponseException);
698+
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
679699
assertEquals("Unexpected HTTP response with status: 500; body: {\"not\" json}",
680700
authException.getMessage());
681701
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
@@ -1219,47 +1239,46 @@ public void testGenerateSignInWithEmailLinkWithSettings() throws Exception {
12191239

12201240
@Test
12211241
public void testHttpErrorWithCode() {
1222-
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
1223-
.setCredentials(credentials)
1224-
.setHttpTransport(new MultiRequestMockHttpTransport(ImmutableList.of(
1225-
new MockLowLevelHttpResponse()
1226-
.setContent("{\"error\": {\"message\": \"UNAUTHORIZED_DOMAIN\"}}")
1227-
.setStatusCode(500))))
1228-
.setProjectId("test-project-id")
1229-
.build());
1230-
FirebaseAuth auth = FirebaseAuth.getInstance();
1231-
FirebaseUserManager userManager = auth.getUserManager();
1242+
initializeAppForUserManagementWithStatusCode(500,
1243+
"{\"error\": {\"message\": \"UNAUTHORIZED_DOMAIN\"}}");
1244+
FirebaseUserManager userManager = FirebaseAuth.getInstance().getUserManager();
12321245
try {
12331246
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
12341247
fail("No exception thrown for HTTP error");
12351248
} catch (FirebaseAuthException e) {
12361249
assertEquals("unauthorized-continue-uri", e.getErrorCode());
1237-
assertTrue(e.getCause() instanceof HttpResponseException);
1250+
assertThat(e.getCause(), instanceOf(HttpResponseException.class));
12381251
}
12391252
}
12401253

12411254
@Test
12421255
public void testUnexpectedHttpError() {
1243-
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
1244-
.setCredentials(credentials)
1245-
.setHttpTransport(new MultiRequestMockHttpTransport(ImmutableList.of(
1246-
new MockLowLevelHttpResponse()
1247-
.setContent("{}")
1248-
.setStatusCode(500))))
1249-
.setProjectId("test-project-id")
1250-
.build());
1251-
FirebaseAuth auth = FirebaseAuth.getInstance();
1252-
FirebaseUserManager userManager = auth.getUserManager();
1256+
initializeAppForUserManagementWithStatusCode(500, "{}");
1257+
FirebaseUserManager userManager = FirebaseAuth.getInstance().getUserManager();
12531258
try {
12541259
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
12551260
fail("No exception thrown for HTTP error");
12561261
} catch (FirebaseAuthException e) {
12571262
assertEquals("internal-error", e.getErrorCode());
1258-
assertTrue(e.getCause() instanceof HttpResponseException);
1263+
assertThat(e.getCause(), instanceOf(HttpResponseException.class));
12591264
}
12601265
}
12611266

1262-
private static TestResponseInterceptor initializeAppForUserManagement(String ...responses) {
1267+
private static TestResponseInterceptor initializeAppForUserManagementWithStatusCode(
1268+
int statusCode, String response) {
1269+
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
1270+
.setCredentials(credentials)
1271+
.setHttpTransport(
1272+
MockHttpTransport.builder().setLowLevelHttpResponse(
1273+
new MockLowLevelHttpResponse().setContent(response).setStatusCode(statusCode)).build())
1274+
.setProjectId("test-project-id")
1275+
.build());
1276+
TestResponseInterceptor interceptor = new TestResponseInterceptor();
1277+
FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
1278+
return interceptor;
1279+
}
1280+
1281+
private static TestResponseInterceptor initializeAppForUserManagement(String... responses) {
12631282
List<MockLowLevelHttpResponse> mocks = new ArrayList<>();
12641283
for (String response : responses) {
12651284
mocks.add(new MockLowLevelHttpResponse().setContent(response));
@@ -1270,10 +1289,8 @@ private static TestResponseInterceptor initializeAppForUserManagement(String ...
12701289
.setHttpTransport(transport)
12711290
.setProjectId("test-project-id")
12721291
.build());
1273-
FirebaseAuth auth = FirebaseAuth.getInstance();
1274-
FirebaseUserManager userManager = auth.getUserManager();
12751292
TestResponseInterceptor interceptor = new TestResponseInterceptor();
1276-
userManager.setInterceptor(interceptor);
1293+
FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
12771294
return interceptor;
12781295
}
12791296

@@ -1326,6 +1343,12 @@ private static void checkRequestHeaders(TestResponseInterceptor interceptor) {
13261343
assertEquals(clientVersion, headers.getFirstHeaderStringValue("X-Client-Version"));
13271344
}
13281345

1346+
private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) {
1347+
HttpRequest request = interceptor.getResponse().getRequest();
1348+
assertEquals(method, request.getRequestMethod());
1349+
assertEquals(url, request.getUrl().toString());
1350+
}
1351+
13291352
private interface UserManagerOp {
13301353
void call(FirebaseAuth auth) throws Exception;
13311354
}

0 commit comments

Comments
 (0)