Repository: firebase/firebase-admin-java
Pull request #419 (2020-05-16 19:22:22)

Add class for SAML provider config.
Adds a class for the SAML provider config.


Patch:
diff --git a/src/main/java/com/google/firebase/auth/SamlProviderConfig.java b/src/main/java/com/google/firebase/auth/SamlProviderConfig.java
new file mode 100644
index 0000000..9b29f05
--- /dev/null
+++ b/src/main/java/com/google/firebase/auth/SamlProviderConfig.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+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.GenericJson;
+import com.google.api.client.util.Key;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.auth.ProviderConfig.AbstractCreateRequest;
+import com.google.firebase.auth.ProviderConfig.AbstractUpdateRequest;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Contains metadata associated with a SAML Auth provider.
+ *
+ * <p>Instances of this class are immutable and thread safe.
+ */
+public final class SamlProviderConfig extends ProviderConfig {
+
+  @Key("idpConfig")
+  private GenericJson idpConfig;
+
+  @Key("spConfig")
+  private GenericJson spConfig;
+
+  public String getIdpEntityId() {
+    return (String) idpConfig.get("idpEntityId");
+  }
+
+  public String getSsoUrl() {
+    return (String) idpConfig.get("ssoUrl");
+  }
+
+  public List<String> getX509Certificates() {
+    List<Map<String, String>> idpCertificates =
+        (List<Map<String, String>>) idpConfig.get("idpCertificates");
+    checkNotNull(idpCertificates);
+    ImmutableList.Builder<String> certificates = ImmutableList.<String>builder();
+    for (Map<String, String> idpCertificate : idpCertificates) {
+      certificates.add(idpCertificate.get("x509Certificate"));
+    }
+    return certificates.build();
+  }
+
+  public String getRpEntityId() {
+    return (String) spConfig.get("spEntityId");
+  }
+
+  public String getCallbackUrl() {
+    return (String) spConfig.get("callbackUri");
+  }
+
+  private static List<Object> ensureNestedList(Map<String, Object> outerMap, String id) {
+    List<Object> list = (List<Object>) outerMap.get(id);
+    if (list == null) {
+      list = new ArrayList<Object>();
+      outerMap.put(id, list);
+    }
+    return list;
+  }
+
+  private static Map<String, Object> ensureNestedMap(Map<String, Object> outerMap, String id) {
+    Map<String, Object> map = (Map<String, Object>) outerMap.get(id);
+    if (map == null) {
+      map = new HashMap<String, Object>();
+      outerMap.put(id, map);
+    }
+    return map;
+  }
+
+  /**
+   * A specification class for creating a new SAML Auth provider.
+   *
+   * <p>Set the initial attributes of the new provider by calling various setter methods available
+   * in this class.
+   */
+  public static final class CreateRequest extends AbstractCreateRequest<CreateRequest> {
+
+    /**
+     * Creates a new {@link CreateRequest}, which can be used to create a new SAML Auth provider.
+     *
+     * <p>The returned object should be passed to
+     * {@link AbstractFirebaseAuth#createSamlProviderConfig(CreateRequest)} to register the provider
+     * information persistently.
+     */
+    public CreateRequest() { }
+
+    /**
+     * Sets the IDP entity ID for the new provider.
+     *
+     * @param idpEntityId A non-null, non-empty IDP entity ID string.
+     * @throws IllegalArgumentException If the IDP entity ID is null or empty.
+     */
+    public CreateRequest setIdpEntityId(String idpEntityId) {
+      checkArgument(!Strings.isNullOrEmpty(idpEntityId),
+          "IDP entity ID must not be null or empty.");
+      ensureNestedMap(properties, "idpConfig").put("idpEntityId", idpEntityId);
+      return this;
+    }
+
+    /**
+     * Sets the SSO URL for the new provider.
+     *
+     * @param ssoUrl A non-null, non-empty SSO URL string.
+     * @throws IllegalArgumentException If the SSO URL is null or empty, or if the format is
+     *     invalid.
+     */
+    public CreateRequest setSsoUrl(String ssoUrl) {
+      checkArgument(!Strings.isNullOrEmpty(ssoUrl), "SSO URL must not be null or empty.");
+      assertValidUrl(ssoUrl);
+      ensureNestedMap(properties, "idpConfig").put("ssoUrl", ssoUrl);
+      return this;
+    }
+
+    /**
+     * Adds a x509 certificate to the new provider.
+     *
+     * @param x509Certificate A non-null, non-empty x509 certificate string.
+     * @throws IllegalArgumentException If the x509 certificate is null or empty.
+     */
+    public CreateRequest addX509Certificate(String x509Certificate) {
+      checkArgument(!Strings.isNullOrEmpty(x509Certificate),
+          "The x509 certificate must not be null or empty.");
+      Map<String, Object> idpConfigProperties = ensureNestedMap(properties, "idpConfig");
+      List<Object> x509Certificates = ensureNestedList(idpConfigProperties, "idpCertificates");
+      x509Certificates.add(ImmutableMap.<String, Object>of("x509Certificate", x509Certificate));
+      return this;
+    }
+
+    // TODO(micahstairs): Add 'addAllX509Certificates' method.
+
+    /**
+     * Sets the RP entity ID for the new provider.
+     *
+     * @param rpEntityId A non-null, non-empty RP entity ID string.
+     * @throws IllegalArgumentException If the RP entity ID is null or empty.
+     */
+    public CreateRequest setRpEntityId(String rpEntityId) {
+      checkArgument(!Strings.isNullOrEmpty(rpEntityId), "RP entity ID must not be null or empty.");
+      ensureNestedMap(properties, "spConfig").put("spEntityId", rpEntityId);
+      return this;
+    }
+
+    /**
+     * Sets the callback URL for the new provider.
+     *
+     * @param callbackUrl A non-null, non-empty callback URL string.
+     * @throws IllegalArgumentException If the callback URL is null or empty, or if the format is
+     *     invalid.
+     */
+    public CreateRequest setCallbackUrl(String callbackUrl) {
+      checkArgument(!Strings.isNullOrEmpty(callbackUrl), "Callback URL must not be null or empty.");
+      assertValidUrl(callbackUrl);
+      ensureNestedMap(properties, "spConfig").put("callbackUri", callbackUrl);
+      return this;
+    }
+
+    // TODO(micahstairs): Add 'setRequestSigningEnabled' method.
+
+    CreateRequest getThis() {
+      return this;
+    }
+
+    void assertValidProviderIdFormat(String providerId) {
+      checkArgument(providerId.startsWith("saml."), "Invalid SAML provider ID: " + providerId);
+    }
+  }
+}


Test patch:
diff --git a/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java b/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
index 39153d3..fa5c6d4 100644
--- a/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
+++ b/src/test/java/com/google/firebase/auth/OidcProviderConfigTest.java
@@ -41,7 +41,7 @@ public class OidcProviderConfigTest {
         + "}").replace("'", "\"");
 
   @Test
-  public void testJsonSerialization() throws IOException {
+  public void testJsonDeserialization() throws IOException {
     OidcProviderConfig config = jsonFactory.fromString(OIDC_JSON_STRING, OidcProviderConfig.class);
 
     assertEquals("oidc.provider-id", config.getProviderId());
diff --git a/src/test/java/com/google/firebase/auth/SamlProviderConfigTest.java b/src/test/java/com/google/firebase/auth/SamlProviderConfigTest.java
new file mode 100644
index 0000000..162c3b6
--- /dev/null
+++ b/src/test/java/com/google/firebase/auth/SamlProviderConfigTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2020 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.auth;
+
+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.assertTrue;
+
+import com.google.api.client.googleapis.util.Utils;
+import com.google.api.client.json.JsonFactory;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+public class SamlProviderConfigTest {
+
+  private static final JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
+
+  private static final String SAML_JSON_STRING =
+      ("{"
+        + "  'name':        'projects/projectId/inboundSamlConfigs/saml.provider-id',"
+        + "  'displayName': 'DISPLAY_NAME',"
+        + "  'enabled':      true,"
+        + "  'idpConfig': {"
+        + "    'idpEntityId': 'IDP_ENTITY_ID',"
+        + "    'ssoUrl':      'https://example.com/login',"
+        + "    'idpCertificates': ["
+        + "      { 'x509Certificate': 'certificate1' },"
+        + "      { 'x509Certificate': 'certificate2' }"
+        + "    ]"
+        + "  },"
+        + "  'spConfig': {"
+        + "    'spEntityId':  'RP_ENTITY_ID',"
+        + "    'callbackUri': 'https://projectId.firebaseapp.com/__/auth/handler'"
+        + "   }"
+        + "}").replace("'", "\"");
+
+  @Test
+  public void testJsonDeserialization() throws IOException {
+    SamlProviderConfig config = jsonFactory.fromString(SAML_JSON_STRING, SamlProviderConfig.class);
+
+    assertEquals("saml.provider-id", config.getProviderId());
+    assertEquals("DISPLAY_NAME", config.getDisplayName());
+    assertTrue(config.isEnabled());
+    assertEquals("IDP_ENTITY_ID", config.getIdpEntityId());
+    assertEquals("https://example.com/login", config.getSsoUrl());
+    assertEquals(ImmutableList.of("certificate1", "certificate2"), config.getX509Certificates());
+    assertEquals("RP_ENTITY_ID", config.getRpEntityId());
+    assertEquals("https://projectId.firebaseapp.com/__/auth/handler", config.getCallbackUrl());
+  }
+
+  @Test
+  public void testCreateRequest() throws IOException {
+    SamlProviderConfig.CreateRequest createRequest =
+        new SamlProviderConfig.CreateRequest()
+          .setProviderId("saml.provider-id")
+          .setDisplayName("DISPLAY_NAME")
+          .setEnabled(false)
+          .setIdpEntityId("IDP_ENTITY_ID")
+          .setSsoUrl("https://example.com/login")
+          .addX509Certificate("certificate1")
+          .addX509Certificate("certificate2")
+          .setRpEntityId("RP_ENTITY_ID")
+          .setCallbackUrl("https://projectId.firebaseapp.com/__/auth/handler");
+
+    assertEquals("saml.provider-id", createRequest.getProviderId());
+    Map<String,Object> properties = createRequest.getProperties();
+    assertEquals(4, properties.size());
+    assertEquals("DISPLAY_NAME", (String) properties.get("displayName"));
+    assertFalse((boolean) properties.get("enabled"));
+
+    Map<String, Object> idpConfig = (Map<String, Object>) properties.get("idpConfig");
+    assertNotNull(idpConfig);
+    assertEquals(3, idpConfig.size());
+    assertEquals("IDP_ENTITY_ID", idpConfig.get("idpEntityId"));
+    assertEquals("https://example.com/login", idpConfig.get("ssoUrl"));
+    List<Object> idpCertificates = (List<Object>) idpConfig.get("idpCertificates");
+    assertNotNull(idpCertificates);
+    assertEquals(2, idpCertificates.size());
+    assertEquals(ImmutableMap.of("x509Certificate", "certificate1"), idpCertificates.get(0));
+    assertEquals(ImmutableMap.of("x509Certificate", "certificate2"), idpCertificates.get(1));
+
+    Map<String, Object> spConfig = (Map<String, Object>) properties.get("spConfig");
+    assertNotNull(spConfig);
+    assertEquals(2, spConfig.size());
+    assertEquals("RP_ENTITY_ID", spConfig.get("spEntityId"));
+    assertEquals("https://projectId.firebaseapp.com/__/auth/handler", spConfig.get("callbackUri"));
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingProviderId() {
+    new SamlProviderConfig.CreateRequest().setProviderId(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestInvalidProviderId() {
+    new SamlProviderConfig.CreateRequest().setProviderId("oidc.provider-id");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingDisplayName() {
+    new SamlProviderConfig.CreateRequest().setDisplayName(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingIdpEntityId() {
+    new SamlProviderConfig.CreateRequest().setIdpEntityId(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingSsoUrl() {
+    new SamlProviderConfig.CreateRequest().setSsoUrl(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestInvalidSsoUrl() {
+    new SamlProviderConfig.CreateRequest().setSsoUrl("not a valid url");
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingX509Certificate() {
+    new SamlProviderConfig.CreateRequest().addX509Certificate(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingRpEntityId() {
+    new SamlProviderConfig.CreateRequest().setRpEntityId(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestMissingCallbackUrl() {
+    new SamlProviderConfig.CreateRequest().setCallbackUrl(null);
+  }
+
+  @Test(expected = IllegalArgumentException.class)
+  public void testCreateRequestInvalidCallbackUrl() {
+    new SamlProviderConfig.CreateRequest().setCallbackUrl("not a valid url");
+  }
+}
diff --git a/src/test/java/com/google/firebase/auth/TenantTest.java b/src/test/java/com/google/firebase/auth/TenantTest.java
index a0d2719..f47ccf8 100644
--- a/src/test/java/com/google/firebase/auth/TenantTest.java
+++ b/src/test/java/com/google/firebase/auth/TenantTest.java
@@ -32,7 +32,7 @@ public class TenantTest {
 
   private static final JsonFactory jsonFactory = Utils.getDefaultJsonFactory();
 
-  private static final String TENANT_JSON_STRING = 
+  private static final String TENANT_JSON_STRING =
       "{"
         + "\"name\":\"projects/project-id/resource/TENANT_ID\","
         + "\"displayName\":\"DISPLAY_NAME\","
@@ -41,7 +41,7 @@ public class TenantTest {
         + "}";
 
   @Test
-  public void testJsonSerialization() throws IOException {
+  public void testJsonDeserialization() throws IOException {
     Tenant tenant = jsonFactory.fromString(TENANT_JSON_STRING, Tenant.class);
 
     assertEquals(tenant.getTenantId(), "TENANT_ID");