This is an automated email from the ASF dual-hosted git repository.

jerryshao pushed a commit to branch branch-1.3
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/branch-1.3 by this push:
     new e7a8ee7d96 [Cherry-pick to branch-1.3] [#11409] fix(idp): Validate 
incompatible simple mode at startup (#11497) (#11515)
e7a8ee7d96 is described below

commit e7a8ee7d96990a45d2dee3ac498926290c31eaf0
Author: github-actions[bot] 
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Tue Jun 9 18:58:34 2026 +0800

    [Cherry-pick to branch-1.3] [#11409] fix(idp): Validate incompatible simple 
mode at startup (#11497) (#11515)
    
    **Cherry-pick Information:**
    - Original commit: 5c43ca8727d15b395fca8ef5d57ae49916450d53
    - Target branch: `branch-1.3`
    - Status: ✅ Clean cherry-pick (no conflicts)
    
    Co-authored-by: MaSai <[email protected]>
---
 design-docs/gravitino-local-authentication.md      |  3 +
 docs/open-api/idp/idp.yaml                         |  5 +-
 docs/open-api/idp/openapi.yaml                     |  5 +-
 docs/security/how-to-authenticate.md               |  3 +
 docs/security/how-to-use-built-in-idp.md           | 13 ++-
 .../idp/web/rest/feature/IdpRESTFeature.java       | 19 +++++
 .../idp/integration/test/IdpRESTApiIT.java         | 18 +++-
 .../idp/web/rest/feature/TestIdpRESTFeature.java   | 98 ++++++++++++++++++++++
 8 files changed, 156 insertions(+), 8 deletions(-)

diff --git a/design-docs/gravitino-local-authentication.md 
b/design-docs/gravitino-local-authentication.md
index bc90a39ea9..bfb1d7ea3f 100644
--- a/design-docs/gravitino-local-authentication.md
+++ b/design-docs/gravitino-local-authentication.md
@@ -319,6 +319,9 @@ fresh Gravitino deployment.
    gravitino.authorization.serviceAdmins=admin1,admin2
    ```
 
+   Built-in IdP is incompatible with the `simple` authenticator. When the 
`idp-basic` plugin is
+   enabled, `gravitino.authenticators` must not include `simple`.
+
 2. Export the initial service admin password before starting Gravitino:
 
    ```bash
diff --git a/docs/open-api/idp/idp.yaml b/docs/open-api/idp/idp.yaml
index 0aea6d75db..6b233bbd51 100644
--- a/docs/open-api/idp/idp.yaml
+++ b/docs/open-api/idp/idp.yaml
@@ -24,7 +24,10 @@ paths:
       tags:
         - IDP
       summary: Add built-in IDP user
-      description: Creates a built-in IDP user with the given username and 
password.
+      description: >
+        Creates a built-in IDP user with the given username and password.
+        Requires the `idp-basic` plugin and built-in IdP Basic authentication.
+        `gravitino.authenticators` must not include `simple`.
       operationId: addIdpUser
       requestBody:
         required: true
diff --git a/docs/open-api/idp/openapi.yaml b/docs/open-api/idp/openapi.yaml
index 76e064083f..23000ef4f9 100644
--- a/docs/open-api/idp/openapi.yaml
+++ b/docs/open-api/idp/openapi.yaml
@@ -25,7 +25,10 @@ info:
   version: 1.3.0-SNAPSHOT
   description: |
     OpenAPI specification for built-in IDP user and group management APIs 
exposed
-    by the `idp-basic` plugin when the `basic` authenticator is enabled.
+    by the `idp-basic` plugin. Clients authenticate with Basic credentials
+    validated against built-in IdP user metadata. Enable the plugin via
+    `gravitino.server.rest.extensionPackages`; `gravitino.authenticators` must 
not
+    include `simple` when IdP is enabled.
 
 servers:
   - url: "{scheme}://{host}:{port}/{basePath}"
diff --git a/docs/security/how-to-authenticate.md 
b/docs/security/how-to-authenticate.md
index 053821fb07..5389d4a734 100644
--- a/docs/security/how-to-authenticate.md
+++ b/docs/security/how-to-authenticate.md
@@ -366,6 +366,9 @@ This example shows how to enable built-in Basic 
authentication.
 
 - Gravitino distribution package (includes the idp-basic plugin on the server 
classpath)
 
+Built-in IdP is **incompatible** with the `simple` authenticator (the 
default). When the
+`idp-basic` plugin is enabled, `gravitino.authenticators` must not include 
`simple`.
+
 **Configuration:**
 
 Append the following to `conf/gravitino.conf`:
diff --git a/docs/security/how-to-use-built-in-idp.md 
b/docs/security/how-to-use-built-in-idp.md
index ef18b44eaf..c9c00f05aa 100644
--- a/docs/security/how-to-use-built-in-idp.md
+++ b/docs/security/how-to-use-built-in-idp.md
@@ -31,7 +31,11 @@ Before you call `/api/idp/*`, ensure the following:
    gravitino.server.rest.extensionPackages = 
org.apache.gravitino.idp.web.rest.feature
    ```
 
-2. **Service admin passwords** — Built-in IDP requires every username in
+2. **Server authenticator** — Built-in IdP is **incompatible** with the 
`simple` authenticator
+   (the default). When the `idp-basic` plugin is enabled, 
`gravitino.authenticators` must not
+   include `simple`.
+
+3. **Service admin passwords** — Built-in IDP requires every username in
    `gravitino.authorization.serviceAdmins` to have a password stored in 
`idp_user_meta` before you
    can call management APIs.
 
@@ -61,9 +65,10 @@ Before you call `/api/idp/*`, ensure the following:
 
 Set service admins in `gravitino.conf` (see also 
[Prerequisites](#prerequisites)):
 
-| Configuration item                      | Description                        
                                                 | Example |
-|-----------------------------------------|-------------------------------------------------------------------------------------|---------|
-| `gravitino.authorization.serviceAdmins` | Comma-separated service admin that 
can call built-in IDP management APIs          | `admin` |
+| Configuration item                        | Description                      
                                                   | Example |
+|-------------------------------------------|-------------------------------------------------------------------------------------|---------|
+| `gravitino.server.rest.extensionPackages` | Registers built-in IdP REST APIs 
                                                   | 
`org.apache.gravitino.idp.web.rest.feature` |
+| `gravitino.authorization.serviceAdmins`   | Comma-separated service admin 
that can call built-in IDP management APIs            | `admin` |
 
 Example:
 
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
index 939c4d8406..668b5d84a3 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/web/rest/feature/IdpRESTFeature.java
@@ -26,6 +26,7 @@ import javax.ws.rs.ext.Provider;
 import org.apache.gravitino.Config;
 import org.apache.gravitino.Configs;
 import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.auth.AuthenticatorType;
 import org.apache.gravitino.idp.IdpUserGroupManager;
 import org.apache.gravitino.idp.auth.BasicAuthenticator;
 import org.apache.gravitino.idp.web.rest.IdpAuthorizationFilter;
@@ -59,6 +60,7 @@ public class IdpRESTFeature implements Feature {
   public boolean configure(FeatureContext context) {
     GravitinoEnv env = GravitinoEnv.getInstance();
     Config config = env.config();
+    validateConfiguration(config);
     registerBasicAuthenticator(config);
 
     try {
@@ -76,6 +78,23 @@ public class IdpRESTFeature implements Feature {
     return true;
   }
 
+  /**
+   * Validates that the server configuration is compatible with the built-in 
IdP plugin.
+   *
+   * @param config The server configuration.
+   */
+  static void validateConfiguration(Config config) {
+    boolean usesSimple =
+        config.get(Configs.AUTHENTICATORS).stream()
+            .anyMatch(name -> 
AuthenticatorType.SIMPLE.name().equalsIgnoreCase(name.trim()));
+    if (usesSimple) {
+      LOG.error(
+          "Built-in IdP is incompatible with Simple authentication. "
+              + "Remove 'simple' from gravitino.authenticators (default is 
simple).");
+      System.exit(1);
+    }
+  }
+
   private static void registerBasicAuthenticator(Config config) {
     List<Authenticator> authenticators = 
ServerAuthenticator.getInstance().authenticators();
     if (authenticators == null) {
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
index a732f3a5a1..6bf9cbc75e 100644
--- 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/integration/test/IdpRESTApiIT.java
@@ -30,6 +30,7 @@ import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.security.KeyPairGenerator;
 import java.util.Base64;
 import java.util.List;
 import java.util.Map;
@@ -37,6 +38,7 @@ import java.util.Set;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.gravitino.Configs;
 import org.apache.gravitino.auth.AuthConstants;
+import org.apache.gravitino.auth.AuthenticatorType;
 import org.apache.gravitino.dto.responses.ErrorConstants;
 import org.apache.gravitino.idp.dto.requests.AddGroupRequest;
 import org.apache.gravitino.idp.dto.requests.AddUserRequest;
@@ -48,6 +50,7 @@ import 
org.apache.gravitino.idp.web.rest.feature.IdpRESTFeature;
 import org.apache.gravitino.integration.test.util.BaseIT;
 import org.apache.gravitino.integration.test.util.ITUtils;
 import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.server.authentication.OAuthConfig;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
@@ -84,6 +87,11 @@ public class IdpRESTApiIT extends BaseIT {
     configs.put(Configs.CACHE_ENABLED.getKey(), String.valueOf(false));
     configs.put(Configs.STORE_DELETE_AFTER_TIME.getKey(), String.valueOf(20 * 
60 * 1000L));
     configs.put(Configs.SERVICE_ADMINS.getKey(), ADMIN);
+    configs.put(Configs.AUTHENTICATORS.getKey(), 
AuthenticatorType.OAUTH.name().toLowerCase());
+    configs.put(OAuthConfig.SERVICE_AUDIENCE.getKey(), "service1");
+    configs.put(OAuthConfig.DEFAULT_SIGN_KEY.getKey(), oauthPublicSignKey());
+    configs.put(OAuthConfig.DEFAULT_SERVER_URI.getKey(), "test");
+    configs.put(OAuthConfig.DEFAULT_TOKEN_PATH.getKey(), "test");
     configs.put(
         Configs.REST_API_EXTENSION_PACKAGES.getKey(), 
IdpRESTFeature.IDP_REST_EXTENSION_PACKAGE);
     registerCustomConfigs(configs);
@@ -117,8 +125,8 @@ public class IdpRESTApiIT extends BaseIT {
   @Test
   void testIdpAuthorization() throws Exception {
     Assertions.assertEquals(200, get("/version", ADMIN, 
ADMIN_PASSWORD).statusCode());
-    // No Authorization: simple authenticator allows anonymous access; IdP 
filter rejects.
-    assertError(403, get("/idp/users/" + USER1, null, null), 
ErrorConstants.FORBIDDEN_CODE);
+    // No Authorization: OAuth rejects the request before the IdP filter runs.
+    Assertions.assertEquals(401, get("/idp/users/" + USER1, null, 
null).statusCode());
 
     postUser(USER2, USER_PASSWORD);
     assertError(
@@ -371,4 +379,10 @@ public class IdpRESTApiIT extends BaseIT {
   private static int errorCode(HttpResponse<String> response) throws Exception 
{
     return 
JsonUtils.objectMapper().readTree(response.body()).get("code").asInt();
   }
+
+  private static String oauthPublicSignKey() throws Exception {
+    KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+    generator.initialize(2048);
+    return 
Base64.getEncoder().encodeToString(generator.generateKeyPair().getPublic().getEncoded());
+  }
 }
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
new file mode 100644
index 0000000000..7215361797
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/web/rest/feature/TestIdpRESTFeature.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.gravitino.idp.web.rest.feature;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import com.google.common.collect.Lists;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.auth.AuthenticatorType;
+import org.junit.jupiter.api.Test;
+
+class TestIdpRESTFeature {
+
+  @Test
+  void testSimpleFails() {
+    Config config = newConfig(AuthenticatorType.SIMPLE.name().toLowerCase());
+
+    SystemExitException exception =
+        assertThrows(SystemExitException.class, () -> 
validateWithExitGuard(config));
+
+    assertEquals(1, exception.status());
+  }
+
+  @Test
+  void testDefaultSimpleFails() {
+    Config config = new Config(false) {};
+
+    assertThrows(SystemExitException.class, () -> 
validateWithExitGuard(config));
+  }
+
+  @Test
+  void testOAuthOk() {
+    Config config = newConfig(AuthenticatorType.OAUTH.name().toLowerCase());
+
+    assertDoesNotThrow(() -> IdpRESTFeature.validateConfiguration(config));
+  }
+
+  private static Config newConfig(String... authenticators) {
+    Config config = new Config(false) {};
+    config.set(Configs.AUTHENTICATORS, Lists.newArrayList(authenticators));
+    return config;
+  }
+
+  @SuppressWarnings("removal")
+  private static void validateWithExitGuard(Config config) {
+    SecurityManager original = System.getSecurityManager();
+    System.setSecurityManager(
+        new SecurityManager() {
+          @Override
+          public void checkExit(int status) {
+            throw new SystemExitException(status);
+          }
+
+          @Override
+          public void checkPermission(java.security.Permission perm) {
+            // Allow test execution.
+          }
+        });
+    try {
+      IdpRESTFeature.validateConfiguration(config);
+    } finally {
+      System.setSecurityManager(original);
+    }
+  }
+
+  private static final class SystemExitException extends SecurityException {
+    private final int status;
+
+    private SystemExitException(int status) {
+      super("System.exit(" + status + ")");
+      this.status = status;
+    }
+
+    private int status() {
+      return status;
+    }
+  }
+}

Reply via email to