Repository: firebase/firebase-admin-java
Pull request #400 (2020-04-28 21:00:59)

Add operations to create and delete OIDC provider configs.
This adds an operation to create OIDC provider configs (can be done using either the tenant-aware or standard Firebase client), as well as an operation to delete provider configs.

I spent some time trying to see if we could make the create operation generic so that it could support both OIDC and SAML provider configs, however, I'm concerned that such a solution would add complexity to the external API, rather than making it simpler. I think it would also introduce some sort of `instanceof` check internally.

This work is part of adding multi-tenancy support (see issue #332).

micahstairs: I've merged the other PR into this and changed the base branch to idp-config.

Patch:
diff --git a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
index b9b563e..cde7a90 100644
--- a/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
+++ b/src/main/java/com/google/firebase/auth/AbstractFirebaseAuth.java
@@ -31,8 +31,7 @@ import com.google.firebase.auth.FirebaseUserManager.EmailLinkType;
 import com.google.firebase.auth.FirebaseUserManager.UserImportRequest;
 import com.google.firebase.auth.ListUsersPage.DefaultUserSource;
 import com.google.firebase.auth.ListUsersPage.PageFactory;
-import com.google.firebase.auth.UserRecord.CreateRequest;
-import com.google.firebase.auth.UserRecord.UpdateRequest;
+import com.google.firebase.auth.UserRecord;
 import com.google.firebase.auth.internal.FirebaseTokenFactory;
 import com.google.firebase.internal.CallableOperation;
 import com.google.firebase.internal.NonNull;
@@ -320,7 +319,8 @@ public abstract class AbstractFirebaseAuth {
       @Override
       protected Void execute() throws FirebaseAuthException {
         int currentTimeSeconds = (int) (System.currentTimeMillis() / 1000);
-        UpdateRequest request = new UpdateRequest(uid).setValidSince(currentTimeSeconds);
+        UserRecord.UpdateRequest request =
+            new UserRecord.UpdateRequest(uid).setValidSince(currentTimeSeconds);
         userManager.updateUser(request, jsonFactory);
         return null;
       }
@@ -512,32 +512,33 @@ public abstract class AbstractFirebaseAuth {
 
   /**
    * Creates a new user account with the attributes contained in the specified {@link
-   * CreateRequest}.
+   * UserRecord.CreateRequest}.
    *
-   * @param request A non-null {@link CreateRequest} instance.
+   * @param request A non-null {@link UserRecord.CreateRequest} instance.
    * @return A {@link UserRecord} instance corresponding to the newly created account.
    * @throws NullPointerException if the provided request is null.
    * @throws FirebaseAuthException if an error occurs while creating the user account.
    */
-  public UserRecord createUser(@NonNull CreateRequest request) throws FirebaseAuthException {
+  public UserRecord createUser(@NonNull UserRecord.CreateRequest request)
+      throws FirebaseAuthException {
     return createUserOp(request).call();
   }
 
   /**
-   * Similar to {@link #createUser(CreateRequest)} but performs the operation asynchronously.
+   * Similar to {@link #createUser} but performs the operation asynchronously.
    *
-   * @param request A non-null {@link CreateRequest} instance.
+   * @param request A non-null {@link UserRecord.CreateRequest} instance.
    * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
    *     instance corresponding to the newly created account. If an error occurs while creating the
    *     user account, the future throws a {@link FirebaseAuthException}.
    * @throws NullPointerException if the provided request is null.
    */
-  public ApiFuture<UserRecord> createUserAsync(@NonNull CreateRequest request) {
+  public ApiFuture<UserRecord> createUserAsync(@NonNull UserRecord.CreateRequest request) {
     return createUserOp(request).callAsync(firebaseApp);
   }
 
   private CallableOperation<UserRecord, FirebaseAuthException> createUserOp(
-      final CreateRequest request) {
+      final UserRecord.CreateRequest request) {
     checkNotDestroyed();
     checkNotNull(request, "create request must not be null");
     final FirebaseUserManager userManager = getUserManager();
@@ -552,31 +553,32 @@ public abstract class AbstractFirebaseAuth {
 
   /**
    * Updates an existing user account with the attributes contained in the specified {@link
-   * UpdateRequest}.
+   * UserRecord.UpdateRequest}.
    *
-   * @param request A non-null {@link UpdateRequest} instance.
+   * @param request A non-null {@link UserRecord.UpdateRequest} instance.
    * @return A {@link UserRecord} instance corresponding to the updated user account.
    * @throws NullPointerException if the provided update request is null.
    * @throws FirebaseAuthException if an error occurs while updating the user account.
    */
-  public UserRecord updateUser(@NonNull UpdateRequest request) throws FirebaseAuthException {
+  public UserRecord updateUser(@NonNull UserRecord.UpdateRequest request)
+      throws FirebaseAuthException {
     return updateUserOp(request).call();
   }
 
   /**
-   * Similar to {@link #updateUser(UpdateRequest)} but performs the operation asynchronously.
+   * Similar to {@link #updateUser} but performs the operation asynchronously.
    *
-   * @param request A non-null {@link UpdateRequest} instance.
+   * @param request A non-null {@link UserRecord.UpdateRequest} instance.
    * @return An {@code ApiFuture} which will complete successfully with a {@link UserRecord}
    *     instance corresponding to the updated user account. If an error occurs while updating the
    *     user account, the future throws a {@link FirebaseAuthException}.
    */
-  public ApiFuture<UserRecord> updateUserAsync(@NonNull UpdateRequest request) {
+  public ApiFuture<UserRecord> updateUserAsync(@NonNull UserRecord.UpdateRequest request) {
     return updateUserOp(request).callAsync(firebaseApp);
   }
 
   private CallableOperation<UserRecord, FirebaseAuthException> updateUserOp(
-      final UpdateRequest request) {
+      final UserRecord.UpdateRequest request) {
     checkNotDestroyed();
     checkNotNull(request, "update request must not be null");
     final FirebaseUserManager userManager = getUserManager();
@@ -636,7 +638,8 @@ public abstract class AbstractFirebaseAuth {
     return new CallableOperation<Void, FirebaseAuthException>() {
       @Override
       protected Void execute() throws FirebaseAuthException {
-        final UpdateRequest request = new UpdateRequest(uid).setCustomClaims(claims);
+        final UserRecord.UpdateRequest request =
+            new UserRecord.UpdateRequest(uid).setCustomClaims(claims);
         userManager.updateUser(request, jsonFactory);
         return null;
       }
@@ -917,18 +920,6 @@ public abstract class AbstractFirebaseAuth {
         .callAsync(firebaseApp);
   }
 
-  FirebaseApp getFirebaseApp() {
-    return this.firebaseApp;
-  }
-
-  FirebaseTokenVerifier getCookieVerifier() {
-    return this.cookieVerifier.get();
-  }
-
-  FirebaseUserManager getUserManager() {
-    return this.userManager.get();
-  }
-
   private CallableOperation<String, FirebaseAuthException> generateEmailActionLinkOp(
       final EmailLinkType type, final String email, final ActionCodeSettings settings) {
     checkNotDestroyed();
@@ -945,6 +936,98 @@ public abstract class AbstractFirebaseAuth {
     };
   }
 
+  /**
+   * Creates a new provider OIDC Auth config with the attributes contained in the specified {@link
+   * OidcProviderConfig.CreateRequest}.
+   *
+   * @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
+   * @return An {@link OidcProviderConfig} instance corresponding to the newly created provider
+   *     config.
+   * @throws NullPointerException if the provided request is null.
+   * @throws FirebaseAuthException if an error occurs while creating the provider config.
+   */
+  public OidcProviderConfig createOidcProviderConfig(
+      @NonNull OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
+    return createOidcProviderConfigOp(request).call();
+  }
+
+  /**
+   * Similar to {@link #createOidcProviderConfig} but performs the operation asynchronously.
+   *
+   * @param request A non-null {@link OidcProviderConfig.CreateRequest} instance.
+   * @return An {@code ApiFuture} which will complete successfully with a {@link OidcProviderConfig}
+   *     instance corresponding to the newly created provider config. If an error occurs while
+   *     creating the provider config, the future throws a {@link FirebaseAuthException}.
+   * @throws NullPointerException if the provided request is null.
+   */
+  public ApiFuture<OidcProviderConfig> createOidcProviderConfigAsync(
+      @NonNull OidcProviderConfig.CreateRequest request) {
+    return createOidcProviderConfigOp(request).callAsync(firebaseApp);
+  }
+
+  private CallableOperation<OidcProviderConfig, FirebaseAuthException>
+      createOidcProviderConfigOp(final OidcProviderConfig.CreateRequest request) {
+    checkNotDestroyed();
+    checkNotNull(request, "create request must not be null");
+    final FirebaseUserManager userManager = getUserManager();
+    return new CallableOperation<OidcProviderConfig, FirebaseAuthException>() {
+      @Override
+      protected OidcProviderConfig execute() throws FirebaseAuthException {
+        return userManager.createOidcProviderConfig(request);
+      }
+    };
+  }
+
+  /**
+   * Deletes the provider config identified by the specified provider ID.
+   *
+   * @param providerId A provider ID string.
+   * @throws IllegalArgumentException If the provider ID string is null or empty.
+   * @throws FirebaseAuthException If an error occurs while deleting the provider config.
+   */
+  public void deleteProviderConfig(@NonNull String providerId) throws FirebaseAuthException {
+    deleteProviderConfigOp(providerId).call();
+  }
+
+  /**
+   * Similar to {@link #deleteProviderConfig} but performs the operation asynchronously.
+   *
+   * @param providerId A provider ID string.
+   * @return An {@code ApiFuture} which will complete successfully when the specified provider
+   *     config has been deleted. If an error occurs while deleting the provider config, the future
+   *     throws a {@link FirebaseAuthException}.
+   * @throws IllegalArgumentException If the provider ID string is null or empty.
+   */
+  public ApiFuture<Void> deleteProviderConfigAsync(String providerId) {
+    return deleteProviderConfigOp(providerId).callAsync(firebaseApp);
+  }
+
+  private CallableOperation<Void, FirebaseAuthException> deleteProviderConfigOp(
+      final String providerId) {
+    checkNotDestroyed();
+    checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
+    final FirebaseUserManager userManager = getUserManager();
+    return new CallableOperation<Void, FirebaseAuthException>() {
+      @Override
+      protected Void execute() throws FirebaseAuthException {
+        userManager.deleteProviderConfig(providerId);
+        return null;
+      }
+    };
+  }
+
+  FirebaseApp getFirebaseApp() {
+    return this.firebaseApp;
+  }
+
+  FirebaseTokenVerifier getCookieVerifier() {
+    return this.cookieVerifier.get();
+  }
+
+  FirebaseUserManager getUserManager() {
+    return this.userManager.get();
+  }
+
   protected <T> Supplier<T> threadSafeMemoize(final Supplier<T> supplier) {
     return Suppliers.memoize(
         new Supplier<T>() {
diff --git a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
index 56ef3f3..0c031a8 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseUserManager.java
@@ -65,6 +65,7 @@ import java.util.Map;
  */
 class FirebaseUserManager {
 
+  static final String CONFIGURATION_NOT_FOUND = "configuration-not-found";
   static final String TENANT_ID_MISMATCH_ERROR = "tenant-id-mismatch";
   static final String TENANT_NOT_FOUND_ERROR = "tenant-not-found";
   static final String USER_NOT_FOUND_ERROR = "user-not-found";
@@ -74,7 +75,7 @@ class FirebaseUserManager {
   // SDK error codes defined at: https://firebase.google.com/docs/auth/admin/errors
   private static final Map<String, String> ERROR_CODES = ImmutableMap.<String, String>builder()
       .put("CLAIMS_TOO_LARGE", "claims-too-large")
-      .put("CONFIGURATION_NOT_FOUND", "project-not-found")
+      .put("CONFIGURATION_NOT_FOUND", CONFIGURATION_NOT_FOUND)
       .put("INSUFFICIENT_PERMISSION", "insufficient-permission")
       .put("DUPLICATE_EMAIL", "email-already-exists")
       .put("DUPLICATE_LOCAL_ID", "uid-already-exists")
@@ -106,6 +107,7 @@ class FirebaseUserManager {
   private static final String CLIENT_VERSION_HEADER = "X-Client-Version";
 
   private final String userMgtBaseUrl;
+  private final String idpConfigMgtBaseUrl;
   private final String tenantMgtBaseUrl;
   private final JsonFactory jsonFactory;
   private final HttpRequestFactory requestFactory;
@@ -120,15 +122,18 @@ class FirebaseUserManager {
         "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.");
-    String tenantId = builder.tenantId;
-    if (builder.tenantId == null) {
-      this.userMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v1", projectId);
+    final String idToolkitUrlV1 = String.format(ID_TOOLKIT_URL, "v1", projectId);
+    final String idToolkitUrlV2 = String.format(ID_TOOLKIT_URL, "v2", projectId);
+    final String tenantId = builder.tenantId;
+    if (tenantId == null) {
+      this.userMgtBaseUrl = idToolkitUrlV1;
+      this.idpConfigMgtBaseUrl = idToolkitUrlV2;
     } else {
       checkArgument(!tenantId.isEmpty(), "tenant ID must not be empty");
-      this.userMgtBaseUrl =
-          String.format(ID_TOOLKIT_URL, "v1", projectId) + getTenantUrlSuffix(tenantId);
+      this.userMgtBaseUrl = idToolkitUrlV1 + getTenantUrlSuffix(tenantId);
+      this.idpConfigMgtBaseUrl = idToolkitUrlV2 + getTenantUrlSuffix(tenantId);
     }
-    this.tenantMgtBaseUrl = String.format(ID_TOOLKIT_URL, "v2", projectId);
+    this.tenantMgtBaseUrl = idToolkitUrlV2;
     this.jsonFactory = app.getOptions().getJsonFactory();
     this.requestFactory = builder.requestFactory == null
       ? ApiClientUtils.newAuthorizedRequestFactory(app) : builder.requestFactory;
@@ -316,6 +321,20 @@ class FirebaseUserManager {
     throw new FirebaseAuthException(INTERNAL_ERROR, "Failed to create email action link");
   }
 
+  OidcProviderConfig createOidcProviderConfig(
+      OidcProviderConfig.CreateRequest request) throws FirebaseAuthException {
+    GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs");
+    String providerId = request.getProviderId();
+    checkArgument(!Strings.isNullOrEmpty(providerId), "provider ID must not be null or empty");
+    url.set("oauthIdpConfigId", providerId);
+    return sendRequest("POST", url, request.getProperties(), OidcProviderConfig.class);
+  }
+
+  void deleteProviderConfig(String providerId) throws FirebaseAuthException {
+    GenericUrl url = new GenericUrl(idpConfigMgtBaseUrl + "/oauthIdpConfigs/" + providerId);
+    sendRequest("DELETE", url, null, GenericJson.class);
+  }
+
   private static String getTenantUrlSuffix(String tenantId) {
     checkArgument(!Strings.isNullOrEmpty(tenantId));
     return "/tenants/" + tenantId;


Test patch:
diff --git a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
index bb98389..98168da 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseAuthIT.java
@@ -959,6 +959,69 @@ public class FirebaseAuthIT {
     }
   }
 
+  @Test
+  public void testOidcProviderConfigLifecycle() throws Exception {
+    // Create config provider
+    String providerId = "oidc.provider-id";
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest()
+            .setProviderId(providerId)
+            .setDisplayName("DisplayName")
+            .setEnabled(true)
+            .setClientId("ClientId")
+            .setIssuer("https://oidc.com/issuer");
+    OidcProviderConfig config = auth.createOidcProviderConfigAsync(createRequest).get();
+    assertEquals(providerId, config.getProviderId());
+    assertEquals("DisplayName", config.getDisplayName());
+    assertEquals("ClientId", config.getClientId());
+    assertEquals("https://oidc.com/issuer", config.getIssuer());
+
+    // TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
+
+    // Delete config provider
+    auth.deleteProviderConfigAsync(providerId).get();
+    // TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
+    // double-check that the config provider was deleted.
+  }
+
+  @Test
+  public void testTenantAwareOidcProviderConfigLifecycle() throws Exception {
+    // Create tenant to use.
+    TenantManager tenantManager = auth.getTenantManager();
+    Tenant.CreateRequest tenantCreateRequest =
+        new Tenant.CreateRequest().setDisplayName("DisplayName");
+    String tenantId = tenantManager.createTenant(tenantCreateRequest).getTenantId();
+
+    try {
+      // Create config provider
+      TenantAwareFirebaseAuth tenantAwareAuth = auth.getTenantManager().getAuthForTenant(tenantId);
+      String providerId = "oidc.provider-id";
+      OidcProviderConfig.CreateRequest createRequest =
+          new OidcProviderConfig.CreateRequest()
+              .setProviderId(providerId)
+              .setDisplayName("DisplayName")
+              .setEnabled(true)
+              .setClientId("ClientId")
+              .setIssuer("https://oidc.com/issuer");
+      OidcProviderConfig config =
+          tenantAwareAuth.createOidcProviderConfigAsync(createRequest).get();
+      assertEquals(providerId, config.getProviderId());
+      assertEquals("DisplayName", config.getDisplayName());
+      assertEquals("ClientId", config.getClientId());
+      assertEquals("https://oidc.com/issuer", config.getIssuer());
+
+      // TODO(micahstairs): Test getOidcProviderConfig and updateProviderConfig operations.
+
+      // Delete config provider
+      tenantAwareAuth.deleteProviderConfigAsync(providerId).get();
+      // TODO(micahstairs): Once getOidcProviderConfig operation is implemented, add a check here to
+      // double-check that the config provider was deleted.
+    } finally {
+      // Delete tenant.
+      tenantManager.deleteTenantAsync(tenantId).get();
+    }
+  }
+
   private Map<String, String> parseLinkParameters(String link) throws Exception {
     Map<String, String> result = new HashMap<>();
     int queryBegin = link.indexOf('?');
diff --git a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
index 20eb562..13b77f8 100644
--- a/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
+++ b/src/test/java/com/google/firebase/auth/FirebaseUserManagerTest.java
@@ -65,6 +65,7 @@ public class FirebaseUserManagerTest {
   private static final JsonFactory JSON_FACTORY = Utils.getDefaultJsonFactory();
   private static final String TEST_TOKEN = "token";
   private static final GoogleCredentials credentials = new MockGoogleCredentials(TEST_TOKEN);
+
   private static final ActionCodeSettings ACTION_CODE_SETTINGS = ActionCodeSettings.builder()
           .setUrl("https://example.dynamic.link")
           .setHandleCodeInApp(true)
@@ -76,8 +77,10 @@ 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";
+
+  private static final String PROJECT_BASE_URL =
+      "https://identitytoolkit.googleapis.com/v2/projects/test-project-id";
+  private static final String TENANTS_BASE_URL = PROJECT_BASE_URL + "/tenants";
 
   @After
   public void tearDown() {
@@ -1383,6 +1386,149 @@ public class FirebaseUserManagerTest {
     }
   }
 
+  @Test
+  public void testCreateOidcProvider() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForUserManagement(
+        TestUtils.loadResource("oidc.json"));
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest()
+            .setProviderId("oidc.provider-id")
+            .setDisplayName("DISPLAY_NAME")
+            .setEnabled(true)
+            .setClientId("CLIENT_ID")
+            .setIssuer("https://oidc.com/issuer");
+
+    OidcProviderConfig config = FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+
+    checkOidcProviderConfig(config);
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "POST", PROJECT_BASE_URL + "/oauthIdpConfigs");
+    GenericJson parsed = parseRequestContent(interceptor);
+    assertEquals("DISPLAY_NAME", parsed.get("displayName"));
+    assertTrue((boolean) parsed.get("enabled"));
+    assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+    GenericUrl url = interceptor.getResponse().getRequest().getUrl();
+    assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
+  }
+
+  @Test
+  public void testCreateOidcProviderMinimal() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForUserManagement(
+        TestUtils.loadResource("oidc.json"));
+    // Only the 'enabled' field can be omitted from an OIDC provider config creation request.
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest()
+            .setProviderId("oidc.provider-id")
+            .setDisplayName("DISPLAY_NAME")
+            .setClientId("CLIENT_ID")
+            .setIssuer("https://oidc.com/issuer");
+
+    FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "POST", PROJECT_BASE_URL + "/oauthIdpConfigs");
+    GenericJson parsed = parseRequestContent(interceptor);
+    assertEquals("DISPLAY_NAME", parsed.get("displayName"));
+    assertNull(parsed.get("enabled"));
+    assertEquals("CLIENT_ID", parsed.get("clientId"));
+    assertEquals("https://oidc.com/issuer", parsed.get("issuer"));
+    GenericUrl url = interceptor.getResponse().getRequest().getUrl();
+    assertEquals("oidc.provider-id", url.getFirst("oauthIdpConfigId"));
+  }
+
+  @Test
+  public void testCreateOidcProviderError() throws Exception {
+    TestResponseInterceptor interceptor =
+        initializeAppForUserManagementWithStatusCode(404,
+            "{\"error\": {\"message\": \"INTERNAL_ERROR\"}}");
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest().setProviderId("oidc.provider-id");
+    try {
+      FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+      fail("No error thrown for invalid response");
+    } catch (FirebaseAuthException e) {
+      assertEquals(FirebaseUserManager.INTERNAL_ERROR, e.getErrorCode());
+    }
+    checkUrl(interceptor, "POST", PROJECT_BASE_URL + "/oauthIdpConfigs");
+  }
+
+  @Test
+  public void testCreateOidcProviderMissingId() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForUserManagement(
+        TestUtils.loadResource("oidc.json"));
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest()
+            .setDisplayName("DISPLAY_NAME")
+            .setEnabled(true)
+            .setClientId("CLIENT_ID")
+            .setIssuer("https://oidc.com/issuer");
+    try {
+      FirebaseAuth.getInstance().createOidcProviderConfig(createRequest);
+      fail("No error thrown for invalid response");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+  }
+
+  @Test
+  public void testTenantAwareCreateOidcProvider() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
+        "TENANT_ID",
+        TestUtils.loadResource("oidc.json"));
+    OidcProviderConfig.CreateRequest createRequest =
+        new OidcProviderConfig.CreateRequest()
+            .setProviderId("oidc.provider-id")
+            .setDisplayName("DISPLAY_NAME")
+            .setEnabled(true)
+            .setClientId("CLIENT_ID")
+            .setIssuer("https://oidc.com/issuer");
+    TenantAwareFirebaseAuth tenantAwareAuth =
+        FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
+
+    OidcProviderConfig config = tenantAwareAuth.createOidcProviderConfig(createRequest);
+
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "POST", TENANTS_BASE_URL + "/TENANT_ID/oauthIdpConfigs");
+  }
+
+  @Test
+  public void testDeleteProviderConfig() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForUserManagement("{}");
+
+    FirebaseAuth.getInstance().deleteProviderConfig("PROVIDER_ID");
+
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "DELETE", PROJECT_BASE_URL + "/oauthIdpConfigs/PROVIDER_ID");
+  }
+
+  @Test
+  public void testDeleteProviderConfigWithNotFoundError() throws Exception {
+    TestResponseInterceptor interceptor =
+        initializeAppForUserManagementWithStatusCode(404,
+            "{\"error\": {\"message\": \"CONFIGURATION_NOT_FOUND\"}}");
+    try {
+      FirebaseAuth.getInstance().deleteProviderConfig("UNKNOWN");
+      fail("No error thrown for invalid response");
+    } catch (FirebaseAuthException e) {
+      assertEquals(FirebaseUserManager.CONFIGURATION_NOT_FOUND, e.getErrorCode());
+    }
+    checkUrl(interceptor, "DELETE", PROJECT_BASE_URL + "/oauthIdpConfigs/UNKNOWN");
+  }
+
+  @Test
+  public void testTenantAwareDeleteProviderConfig() throws Exception {
+    TestResponseInterceptor interceptor = initializeAppForTenantAwareUserManagement(
+        "TENANT_ID",
+        "{}");
+    TenantAwareFirebaseAuth tenantAwareAuth =
+        FirebaseAuth.getInstance().getTenantManager().getAuthForTenant("TENANT_ID");
+
+    tenantAwareAuth.deleteProviderConfig("PROVIDER_ID");
+
+    checkRequestHeaders(interceptor);
+    checkUrl(interceptor, "DELETE", TENANTS_BASE_URL + "/TENANT_ID/oauthIdpConfigs/PROVIDER_ID");
+  }
+
   private static TestResponseInterceptor initializeAppForUserManagementWithStatusCode(
       int statusCode, String response) {
     FirebaseApp.initializeApp(new FirebaseOptions.Builder()
@@ -1397,7 +1543,24 @@ public class FirebaseUserManagerTest {
     return interceptor;
   }
 
+  private static TestResponseInterceptor initializeAppForTenantAwareUserManagement(
+      String tenantId,
+      String... responses) {
+    initializeAppWithResponses(responses);
+    TestResponseInterceptor interceptor = new TestResponseInterceptor();
+    TenantManager tenantManager =  FirebaseAuth.getInstance().getTenantManager();
+    tenantManager.getAuthForTenant(tenantId).getUserManager().setInterceptor(interceptor);
+    return interceptor;
+  }
+
   private static TestResponseInterceptor initializeAppForUserManagement(String... responses) {
+    initializeAppWithResponses(responses);
+    TestResponseInterceptor interceptor = new TestResponseInterceptor();
+    FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
+    return interceptor;
+  }
+
+  private static void initializeAppWithResponses(String... responses) {
     List<MockLowLevelHttpResponse> mocks = new ArrayList<>();
     for (String response : responses) {
       mocks.add(new MockLowLevelHttpResponse().setContent(response));
@@ -1408,9 +1571,6 @@ public class FirebaseUserManagerTest {
         .setHttpTransport(transport)
         .setProjectId("test-project-id")
         .build());
-    TestResponseInterceptor interceptor = new TestResponseInterceptor();
-    FirebaseAuth.getInstance().getUserManager().setInterceptor(interceptor);
-    return interceptor;
   }
 
   private static GenericJson parseRequestContent(TestResponseInterceptor interceptor)
@@ -1485,6 +1645,14 @@ public class FirebaseUserManagerTest {
     assertFalse(tenant.isEmailLinkSignInEnabled());
   }
 
+  private static void checkOidcProviderConfig(OidcProviderConfig config) {
+    assertEquals("oidc.provider-id", config.getProviderId());
+    assertEquals("DISPLAY_NAME", config.getDisplayName());
+    assertTrue(config.isEnabled());
+    assertEquals("CLIENT_ID", config.getClientId());
+    assertEquals("https://oidc.com/issuer", config.getIssuer());
+  }
+
   private static void checkRequestHeaders(TestResponseInterceptor interceptor) {
     HttpHeaders headers = interceptor.getResponse().getRequest().getHeaders();
     String auth = "Bearer " + TEST_TOKEN;
diff --git a/src/test/resources/oidc.json b/src/test/resources/oidc.json
new file mode 100644
index 0000000..e2f1845
--- /dev/null
+++ b/src/test/resources/oidc.json
@@ -0,0 +1,7 @@
+{
+  "name": "projects/projectId/oauthIdpConfigs/oidc.provider-id",
+  "displayName" : "DISPLAY_NAME",
+  "enabled" : true,
+  "clientId" : "CLIENT_ID",
+  "issuer" : "https://oidc.com/issuer"
+}