Skip to content

Add deleteTenant operation to TenantManager. #372

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 27, 2020
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
11 changes: 11 additions & 0 deletions src/main/java/com/google/firebase/auth/FirebaseUserManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
*/
class FirebaseUserManager {

static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
static final String USER_NOT_FOUND_ERROR = "user-not-found";
static final String INTERNAL_ERROR = "internal-error";

Expand All @@ -82,6 +83,7 @@ class FirebaseUserManager {
.put("INVALID_PAGE_SELECTION", "invalid-page-token")
.put("INVALID_PHONE_NUMBER", "invalid-phone-number")
.put("PHONE_NUMBER_EXISTS", "phone-number-already-exists")
.put("TENANT_NOT_FOUND", TENANT_NOT_FOUND_ERROR)
.put("PROJECT_NOT_FOUND", "project-not-found")
.put("USER_NOT_FOUND", USER_NOT_FOUND_ERROR)
.put("WEAK_PASSWORD", "invalid-password")
Expand Down Expand Up @@ -226,6 +228,15 @@ UserImportResult importUsers(UserImportRequest request) throws FirebaseAuthExcep
return new UserImportResult(request.getUsersCount(), response);
}

void deleteTenant(String tenantId) throws FirebaseAuthException {
GenericUrl url = new GenericUrl(tenantMgtBaseUrl + "/tenants/" + tenantId);
GenericJson response = sendRequest("DELETE", url, null, GenericJson.class);
if (response == null) {
throw new FirebaseAuthException(TENANT_NOT_FOUND_ERROR,
"Failed to delete tenant: " + tenantId);
}
}

ListTenantsResponse listTenants(int maxResults, String pageToken)
throws FirebaseAuthException {
ImmutableMap.Builder<String, Object> builder = ImmutableMap.<String, Object>builder()
Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/google/firebase/auth/TenantManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

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.json.JsonFactory;
import com.google.api.core.ApiFuture;
import com.google.common.base.Strings;
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.ListTenantsPage.DefaultTenantSource;
import com.google.firebase.auth.ListTenantsPage.PageFactory;
Expand Down Expand Up @@ -115,4 +117,40 @@ protected ListTenantsPage execute() throws FirebaseAuthException {
}
};
}

/**
* Deletes the tenant identified by the specified tenant ID.
*
* @param tenantId A tenant ID string.
* @throws IllegalArgumentException If the tenant ID string is null or empty.
* @throws FirebaseAuthException If an error occurs while deleting the tenant.
*/
public void deleteTenant(@NonNull String tenantId) throws FirebaseAuthException {
deleteTenantOp(tenantId).call();
}

/**
* Similar to {@link #deleteTenant(String)} but performs the operation asynchronously.
*
* @param tenantId A tenant ID string.
* @return An {@code ApiFuture} which will complete successfully when the specified tenant account
* has been deleted. If an error occurs while deleting the tenant account, the future throws a
* {@link FirebaseAuthException}.
* @throws IllegalArgumentException If the tenant ID string is null or empty.
*/
public ApiFuture<Void> deleteTenantAsync(String tenantId) {
return deleteTenantOp(tenantId).callAsync(firebaseApp);
}

private CallableOperation<Void, FirebaseAuthException> deleteTenantOp(final String tenantId) {
// TODO(micahstairs): Add a check to make sure the app has not been destroyed yet.
checkArgument(!Strings.isNullOrEmpty(tenantId), "tenantId must not be null or empty");
return new CallableOperation<Void, FirebaseAuthException>() {
@Override
protected Void execute() throws FirebaseAuthException {
userManager.deleteTenant(tenantId);
return null;
}
};
}
}
119 changes: 71 additions & 48 deletions src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

package com.google.firebase.auth;

import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

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

@After
public void tearDown() {
Expand Down Expand Up @@ -114,7 +118,7 @@ public void testGetUserWithNotFoundError() throws Exception {
FirebaseAuth.getInstance().getUserAsync("testuser").get();
fail("No error thrown for invalid response");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
}
Expand All @@ -137,7 +141,7 @@ public void testGetUserByEmailWithNotFoundError() throws Exception {
FirebaseAuth.getInstance().getUserByEmailAsync("testuser@example.com").get();
fail("No error thrown for invalid response");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
}
Expand All @@ -160,7 +164,7 @@ public void testGetUserByPhoneNumberWithNotFoundError() throws Exception {
FirebaseAuth.getInstance().getUserByPhoneNumberAsync("+1234567890").get();
fail("No error thrown for invalid response");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
}
Expand Down Expand Up @@ -476,6 +480,32 @@ public void testListZeroTenants() throws Exception {
checkRequestHeaders(interceptor);
}

@Test
public void testDeleteTenant() throws Exception {
TestResponseInterceptor interceptor = initializeAppForUserManagement("{}");

FirebaseAuth.getInstance().getTenantManager().deleteTenantAsync("TENANT_1").get();

checkRequestHeaders(interceptor);
checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/TENANT_1");
}

@Test
public void testDeleteTenantWithNotFoundError() throws Exception {
TestResponseInterceptor interceptor =
initializeAppForUserManagementWithStatusCode(404,
"{\"error\": {\"message\": \"TENANT_NOT_FOUND\"}}");
try {
FirebaseAuth.getInstance().getTenantManager().deleteTenantAsync("UNKNOWN").get();
fail("No error thrown for invalid response");
} catch (ExecutionException e) {
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertEquals(FirebaseUserManager.TENANT_NOT_FOUND_ERROR, authException.getErrorCode());
}
checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/UNKNOWN");
}

@Test
public void testCreateSessionCookie() throws Exception {
TestResponseInterceptor interceptor = initializeAppForUserManagement(
Expand Down Expand Up @@ -615,11 +645,11 @@ public void call(FirebaseAuth auth) throws Exception {
operation.call(FirebaseAuth.getInstance());
fail("No error thrown for HTTP error: " + code);
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
String msg = String.format("Unexpected HTTP response with status: %d; body: {}", code);
assertEquals(msg, authException.getMessage());
assertTrue(authException.getCause() instanceof HttpResponseException);
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
}
}
Expand All @@ -633,10 +663,10 @@ public void call(FirebaseAuth auth) throws Exception {
operation.call(FirebaseAuth.getInstance());
fail("No error thrown for HTTP error");
} catch (ExecutionException e) {
assertTrue(e.getCause().toString(), e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause().toString(), e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertEquals("User management service responded with an error", authException.getMessage());
assertTrue(authException.getCause() instanceof HttpResponseException);
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
assertEquals(FirebaseUserManager.USER_NOT_FOUND_ERROR, authException.getErrorCode());
}
}
Expand All @@ -649,33 +679,23 @@ public void testGetUserMalformedJsonError() throws Exception {
FirebaseAuth.getInstance().getUserAsync("testuser").get();
fail("No error thrown for JSON error");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertTrue(authException.getCause() instanceof IOException);
assertThat(authException.getCause(), instanceOf(IOException.class));
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
}
}

@Test
public void testGetUserUnexpectedHttpError() throws Exception {
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
response.setContent("{\"not\" json}");
response.setStatusCode(500);
MockHttpTransport transport = new MockHttpTransport.Builder()
.setLowLevelHttpResponse(response)
.build();
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setProjectId("test-project-id")
.setHttpTransport(transport)
.build());
initializeAppForUserManagementWithStatusCode(500, "{\"not\" json}");
try {
FirebaseAuth.getInstance().getUserAsync("testuser").get();
fail("No error thrown for JSON error");
} catch (ExecutionException e) {
assertTrue(e.getCause() instanceof FirebaseAuthException);
assertThat(e.getCause(), instanceOf(FirebaseAuthException.class));
FirebaseAuthException authException = (FirebaseAuthException) e.getCause();
assertTrue(authException.getCause() instanceof HttpResponseException);
assertThat(authException.getCause(), instanceOf(HttpResponseException.class));
assertEquals("Unexpected HTTP response with status: 500; body: {\"not\" json}",
authException.getMessage());
assertEquals(FirebaseUserManager.INTERNAL_ERROR, authException.getErrorCode());
Expand Down Expand Up @@ -1219,47 +1239,46 @@ public void testGenerateSignInWithEmailLinkWithSettings() throws Exception {

@Test
public void testHttpErrorWithCode() {
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setHttpTransport(new MultiRequestMockHttpTransport(ImmutableList.of(
new MockLowLevelHttpResponse()
.setContent("{\"error\": {\"message\": \"UNAUTHORIZED_DOMAIN\"}}")
.setStatusCode(500))))
.setProjectId("test-project-id")
.build());
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUserManager userManager = auth.getUserManager();
initializeAppForUserManagementWithStatusCode(500,
"{\"error\": {\"message\": \"UNAUTHORIZED_DOMAIN\"}}");
FirebaseUserManager userManager = FirebaseAuth.getInstance().getUserManager();
try {
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
fail("No exception thrown for HTTP error");
} catch (FirebaseAuthException e) {
assertEquals("unauthorized-continue-uri", e.getErrorCode());
assertTrue(e.getCause() instanceof HttpResponseException);
assertThat(e.getCause(), instanceOf(HttpResponseException.class));
}
}

@Test
public void testUnexpectedHttpError() {
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setHttpTransport(new MultiRequestMockHttpTransport(ImmutableList.of(
new MockLowLevelHttpResponse()
.setContent("{}")
.setStatusCode(500))))
.setProjectId("test-project-id")
.build());
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUserManager userManager = auth.getUserManager();
initializeAppForUserManagementWithStatusCode(500, "{}");
FirebaseUserManager userManager = FirebaseAuth.getInstance().getUserManager();
try {
userManager.getEmailActionLink(EmailLinkType.PASSWORD_RESET, "test@example.com", null);
fail("No exception thrown for HTTP error");
} catch (FirebaseAuthException e) {
assertEquals("internal-error", e.getErrorCode());
assertTrue(e.getCause() instanceof HttpResponseException);
assertThat(e.getCause(), instanceOf(HttpResponseException.class));
}
}

private static TestResponseInterceptor initializeAppForUserManagement(String ...responses) {
private static TestResponseInterceptor initializeAppForUserManagementWithStatusCode(
int statusCode, String response) {
FirebaseApp.initializeApp(new FirebaseOptions.Builder()
.setCredentials(credentials)
.setHttpTransport(
MockHttpTransport.builder().setLowLevelHttpResponse(
new MockLowLevelHttpResponse().setContent(response).setStatusCode(statusCode)).build())
.setProjectId("test-project-id")
.build());
TestResponseInterceptor interceptor = new TestResponseInterceptor();
FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
return interceptor;
}

private static TestResponseInterceptor initializeAppForUserManagement(String... responses) {
List<MockLowLevelHttpResponse> mocks = new ArrayList<>();
for (String response : responses) {
mocks.add(new MockLowLevelHttpResponse().setContent(response));
Expand All @@ -1270,10 +1289,8 @@ private static TestResponseInterceptor initializeAppForUserManagement(String ...
.setHttpTransport(transport)
.setProjectId("test-project-id")
.build());
FirebaseAuth auth = FirebaseAuth.getInstance();
FirebaseUserManager userManager = auth.getUserManager();
TestResponseInterceptor interceptor = new TestResponseInterceptor();
userManager.setInterceptor(interceptor);
FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
return interceptor;
}

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

private static void checkUrl(TestResponseInterceptor interceptor, String method, String url) {
HttpRequest request = interceptor.getResponse().getRequest();
assertEquals(method, request.getRequestMethod());
assertEquals(url, request.getUrl().toString());
}

private interface UserManagerOp {
void call(FirebaseAuth auth) throws Exception;
}
Expand Down