Copilot commented on code in PR #10971:
URL: https://github.com/apache/gravitino/pull/10971#discussion_r3213072408


##########
core/src/main/java/org/apache/gravitino/GravitinoEnv.java:
##########
@@ -685,5 +702,7 @@ private void initGravitinoServerComponents() {
     BuiltInJobTemplateEventListener builtInJobTemplateListener =
         new BuiltInJobTemplateEventListener(jobManager, entityStore, 
idGenerator);
     eventListenerManager.addEventListener("builtin-job-template", 
builtInJobTemplateListener);
+
+    this.idpManager = IdpManagerFactory.create();

Review Comment:
   `initGravitinoServerComponents()` unconditionally calls 
`IdpManagerFactory.create()`, which throws if no `IdpManager` service provider 
is on the runtime classpath. This can prevent Gravitino from starting in 
deployments/dev runs that don’t ship the `idp-basic` plugin (even when built-in 
IdP is not needed). Consider making IdP manager loading conditional (e.g., 
based on config/authenticator) or providing a default no-op implementation / 
lazy-load with a clear 503/501 behavior for `/idp/*` endpoints when the plugin 
is absent.



##########
plugins/idp-basic/src/main/java/org/apache/gravitino/idp/basic/authorization/IdpManager.java:
##########
@@ -0,0 +1,331 @@
+/*
+ * 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.basic.authorization;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.dto.IdpGroupDTO;
+import org.apache.gravitino.dto.IdpUserDTO;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
+import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.UserAlreadyExistsException;
+import org.apache.gravitino.idp.basic.password.PasswordHasher;
+import org.apache.gravitino.idp.basic.password.PasswordHasherFactory;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.storage.relational.po.IdpGroupPO;
+import org.apache.gravitino.storage.relational.po.IdpGroupUserRelPO;
+import org.apache.gravitino.storage.relational.po.IdpUserPO;
+import org.apache.gravitino.storage.relational.service.IdpGroupMetaService;
+import org.apache.gravitino.storage.relational.service.IdpUserMetaService;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/**
+ * Built-in IdP manager implementation loaded from the {@code idp-basic} 
plugin.
+ *
+ * <p>This implementation manages both built-in IdP users and groups and 
restricts mutation
+ * operations to Gravitino service admins.
+ */
+public class IdpManager implements 
org.apache.gravitino.authorization.IdpManager {
+  private static final long INITIAL_VERSION = 1L;
+
+  private final Config config;
+  private final IdGenerator idGenerator;
+  private final IdpUserMetaService userMetaService;
+  private final IdpGroupMetaService groupMetaService;
+  private final PasswordHasher passwordHasher;
+
+  public IdpManager() {
+    this(
+        GravitinoEnv.getInstance().config(),
+        GravitinoEnv.getInstance().idGenerator(),
+        IdpUserMetaService.getInstance(),
+        IdpGroupMetaService.getInstance(),
+        PasswordHasherFactory.create());
+  }
+
+  IdpManager(
+      Config config,
+      IdGenerator idGenerator,
+      IdpUserMetaService userMetaService,
+      IdpGroupMetaService groupMetaService,
+      PasswordHasher passwordHasher) {
+    this.config = config;
+    this.idGenerator = idGenerator;
+    this.userMetaService = userMetaService;
+    this.groupMetaService = groupMetaService;
+    this.passwordHasher = passwordHasher;
+  }
+
+  @Override
+  public IdpUserDTO createUser(String userName, String password) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    validatePassword(password);
+    if (userMetaService().findUser(userName).isPresent()) {
+      throw new UserAlreadyExistsException("Built-in IdP user %s already 
exists", userName);
+    }
+
+    userMetaService()
+        .createUser(
+            IdpUserPO.builder()
+                .withUserId(nextId())
+                .withUserName(userName)
+                .withPasswordHash(passwordHasher.hash(password))
+                .withCurrentVersion(INITIAL_VERSION)
+                .withLastVersion(INITIAL_VERSION)
+                .withDeletedAt(0L)
+                .build());
+    return getUser(userName);
+  }
+
+  @Override
+  public IdpUserDTO getUser(String userName) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    IdpUserPO userPO =
+        userMetaService()
+            .findUser(userName)
+            .orElseThrow(
+                () -> new NoSuchUserException("Built-in IdP user %s does not 
exist", userName));
+    return toUserDTO(userPO);
+  }
+
+  @Override
+  public boolean deleteUser(String userName) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    Optional<IdpUserPO> user = userMetaService().findUser(userName);
+    if (!user.isPresent()) {
+      return false;
+    }
+
+    return userMetaService().deleteUser(user.get(), 
System.currentTimeMillis());
+  }
+
+  @Override
+  public IdpUserDTO resetPassword(String userName, String password) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    validatePassword(password);
+    IdpUserPO userPO =
+        userMetaService()
+            .findUser(userName)
+            .orElseThrow(
+                () -> new NoSuchUserException("Built-in IdP user %s does not 
exist", userName));
+    if (passwordHasher.verify(password, userPO.getPasswordHash())) {
+      throw new IllegalArgumentException(
+          "The new password must be different from the old password");
+    }
+
+    userMetaService()
+        .updatePassword(userPO, passwordHasher.hash(password), 
userPO.getCurrentVersion() + 1);
+    return getUser(userName);
+  }
+
+  @Override
+  public IdpGroupDTO createGroup(String groupName) {
+    ensureServiceAdmin();
+    validateGroupName(groupName);
+    if (groupMetaService().findGroup(groupName).isPresent()) {
+      throw new GroupAlreadyExistsException("Built-in IdP group %s already 
exists", groupName);
+    }
+
+    groupMetaService()
+        .createGroup(
+            IdpGroupPO.builder()
+                .withGroupId(nextId())
+                .withGroupName(groupName)
+                .withCurrentVersion(INITIAL_VERSION)
+                .withLastVersion(INITIAL_VERSION)
+                .withDeletedAt(0L)
+                .build());
+    return getGroup(groupName);
+  }
+
+  @Override
+  public IdpGroupDTO getGroup(String groupName) {
+    ensureServiceAdmin();
+    validateGroupName(groupName);
+    IdpGroupPO groupPO =
+        groupMetaService()
+            .findGroup(groupName)
+            .orElseThrow(

Review Comment:
   `getGroup()` does not call `ensureServiceAdmin()`, so non-admin callers can 
read built-in IdP groups and their user membership lists. If built-in IdP is 
intended to be service-admin managed, consider enforcing the admin check for 
reads as well (or add explicit authorization + documentation if reads are 
intentionally public).



##########
plugins/idp-basic/src/main/java/org/apache/gravitino/idp/basic/authorization/IdpManager.java:
##########
@@ -0,0 +1,331 @@
+/*
+ * 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.basic.authorization;
+
+import com.google.common.base.Preconditions;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.Config;
+import org.apache.gravitino.Configs;
+import org.apache.gravitino.GravitinoEnv;
+import org.apache.gravitino.dto.IdpGroupDTO;
+import org.apache.gravitino.dto.IdpUserDTO;
+import org.apache.gravitino.exceptions.ForbiddenException;
+import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
+import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.UserAlreadyExistsException;
+import org.apache.gravitino.idp.basic.password.PasswordHasher;
+import org.apache.gravitino.idp.basic.password.PasswordHasherFactory;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.storage.relational.po.IdpGroupPO;
+import org.apache.gravitino.storage.relational.po.IdpGroupUserRelPO;
+import org.apache.gravitino.storage.relational.po.IdpUserPO;
+import org.apache.gravitino.storage.relational.service.IdpGroupMetaService;
+import org.apache.gravitino.storage.relational.service.IdpUserMetaService;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/**
+ * Built-in IdP manager implementation loaded from the {@code idp-basic} 
plugin.
+ *
+ * <p>This implementation manages both built-in IdP users and groups and 
restricts mutation
+ * operations to Gravitino service admins.
+ */
+public class IdpManager implements 
org.apache.gravitino.authorization.IdpManager {
+  private static final long INITIAL_VERSION = 1L;
+
+  private final Config config;
+  private final IdGenerator idGenerator;
+  private final IdpUserMetaService userMetaService;
+  private final IdpGroupMetaService groupMetaService;
+  private final PasswordHasher passwordHasher;
+
+  public IdpManager() {
+    this(
+        GravitinoEnv.getInstance().config(),
+        GravitinoEnv.getInstance().idGenerator(),
+        IdpUserMetaService.getInstance(),
+        IdpGroupMetaService.getInstance(),
+        PasswordHasherFactory.create());
+  }
+
+  IdpManager(
+      Config config,
+      IdGenerator idGenerator,
+      IdpUserMetaService userMetaService,
+      IdpGroupMetaService groupMetaService,
+      PasswordHasher passwordHasher) {
+    this.config = config;
+    this.idGenerator = idGenerator;
+    this.userMetaService = userMetaService;
+    this.groupMetaService = groupMetaService;
+    this.passwordHasher = passwordHasher;
+  }
+
+  @Override
+  public IdpUserDTO createUser(String userName, String password) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    validatePassword(password);
+    if (userMetaService().findUser(userName).isPresent()) {
+      throw new UserAlreadyExistsException("Built-in IdP user %s already 
exists", userName);
+    }
+
+    userMetaService()
+        .createUser(
+            IdpUserPO.builder()
+                .withUserId(nextId())
+                .withUserName(userName)
+                .withPasswordHash(passwordHasher.hash(password))
+                .withCurrentVersion(INITIAL_VERSION)
+                .withLastVersion(INITIAL_VERSION)
+                .withDeletedAt(0L)
+                .build());
+    return getUser(userName);
+  }
+
+  @Override
+  public IdpUserDTO getUser(String userName) {
+    ensureServiceAdmin();
+    validateUserName(userName);
+    IdpUserPO userPO =
+        userMetaService()
+            .findUser(userName)
+            .orElseThrow(
+                () -> new NoSuchUserException("Built-in IdP user %s does not 
exist", userName));

Review Comment:
   `getUser()` does not call `ensureServiceAdmin()`, so any authenticated (or 
even anonymous, depending on auth setup) caller can read built-in IdP users and 
their group memberships. This seems inconsistent with the service-admin 
restriction used for other IdP operations and can expose identity/membership 
data. Consider applying the same admin check to read APIs, or explicitly 
document/authorize why reads are allowed for non-admins.



##########
docs/open-api/idp.yaml:
##########
@@ -0,0 +1,514 @@
+# 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.
+
+---
+
+paths:
+
+  /idp/users:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP user
+      operationId: addIdpUser
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpUserRequest"
+            examples:
+              CreateIdpUserRequest:
+                $ref: "#/components/examples/CreateIdpUserRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP user already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                UserAlreadyExistsException:
+                  $ref: "#/components/examples/UserAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/users/{user}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/user"
+
+    get:
+      tags:
+        - authentication
+      summary: Get built-in IdP user
+      operationId: getIdpUser
+      responses:
+        "200":
+          description: Returns the built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    put:
+      tags:
+        - authentication
+      summary: Reset built-in IdP user password
+      operationId: resetIdpUserPassword
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ResetIdpUserPasswordRequest"
+            examples:
+              ResetIdpUserPasswordRequest:
+                $ref: "#/components/examples/ResetIdpUserPasswordRequest"
+      responses:
+        "200":
+          description: Returns the updated built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - authentication
+      summary: Remove built-in IdP user
+      operationId: removeIdpUser
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/RemoveResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/groups:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP group
+      operationId: addIdpGroup
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpGroupRequest"
+            examples:
+              CreateIdpGroupRequest:
+                $ref: "#/components/examples/CreateIdpGroupRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP group
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpGroupResponse"
+              examples:
+                IdpGroupResponse:
+                  $ref: "#/components/examples/IdpGroupResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP group already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                GroupAlreadyExistsException:
+                  $ref: "#/components/examples/GroupAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/groups/{group}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/group"
+
+    get:
+      tags:
+        - authentication
+      summary: Get built-in IdP group
+      operationId: getIdpGroup
+      responses:
+        "200":
+          description: Returns the built-in IdP group
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpGroupResponse"
+              examples:
+                IdpGroupResponse:
+                  $ref: "#/components/examples/IdpGroupResponse"
+        "404":
+          description: Not Found - The specified built-in IdP group does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchGroupException:
+                  $ref: "#/components/examples/NoSuchGroupException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - authentication
+      summary: Remove built-in IdP group
+      operationId: removeIdpGroup
+      parameters:
+        - name: force
+          in: query
+          required: false
+          schema:
+            type: boolean
+            default: false
+          description: Whether to force removal of the built-in IdP group
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/RemoveResponse"
+        "404":
+          description: Not Found - The specified built-in IdP group does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchGroupException:
+                  $ref: "#/components/examples/NoSuchGroupException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"

Review Comment:
   OpenAPI for `DELETE /idp/groups/{group}` documents a `404` response, but the 
implementation returns `200` with `removed=false` when the group doesn’t exist. 
It can also return `405 Method Not Allowed` when `force=false` and the group 
still has users (mapped from `UnsupportedOperationException`), and `403` for 
non-service-admin callers. Please update the spec (or adjust the API behavior) 
to match the actual responses.



##########
docs/open-api/idp.yaml:
##########
@@ -0,0 +1,514 @@
+# 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.
+
+---
+
+paths:
+
+  /idp/users:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP user
+      operationId: addIdpUser
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpUserRequest"
+            examples:
+              CreateIdpUserRequest:
+                $ref: "#/components/examples/CreateIdpUserRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP user already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                UserAlreadyExistsException:
+                  $ref: "#/components/examples/UserAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/users/{user}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/user"
+
+    get:
+      tags:
+        - authentication
+      summary: Get built-in IdP user
+      operationId: getIdpUser
+      responses:
+        "200":
+          description: Returns the built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    put:
+      tags:
+        - authentication
+      summary: Reset built-in IdP user password
+      operationId: resetIdpUserPassword
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ResetIdpUserPasswordRequest"
+            examples:
+              ResetIdpUserPasswordRequest:
+                $ref: "#/components/examples/ResetIdpUserPasswordRequest"
+      responses:
+        "200":
+          description: Returns the updated built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - authentication
+      summary: Remove built-in IdP user
+      operationId: removeIdpUser
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/RemoveResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/groups:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP group
+      operationId: addIdpGroup
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpGroupRequest"
+            examples:
+              CreateIdpGroupRequest:
+                $ref: "#/components/examples/CreateIdpGroupRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP group
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpGroupResponse"
+              examples:
+                IdpGroupResponse:
+                  $ref: "#/components/examples/IdpGroupResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP group already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                GroupAlreadyExistsException:
+                  $ref: "#/components/examples/GroupAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/groups/{group}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/group"
+
+    get:
+      tags:
+        - authentication
+      summary: Get built-in IdP group
+      operationId: getIdpGroup
+      responses:
+        "200":
+          description: Returns the built-in IdP group
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpGroupResponse"
+              examples:
+                IdpGroupResponse:
+                  $ref: "#/components/examples/IdpGroupResponse"
+        "404":
+          description: Not Found - The specified built-in IdP group does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchGroupException:
+                  $ref: "#/components/examples/NoSuchGroupException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - authentication
+      summary: Remove built-in IdP group
+      operationId: removeIdpGroup
+      parameters:
+        - name: force
+          in: query
+          required: false
+          schema:
+            type: boolean
+            default: false
+          description: Whether to force removal of the built-in IdP group
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/RemoveResponse"
+        "404":
+          description: Not Found - The specified built-in IdP group does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchGroupException:
+                  $ref: "#/components/examples/NoSuchGroupException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/groups/{group}/add:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/group"
+
+    put:
+      tags:
+        - authentication
+      summary: Add users to built-in IdP group
+      operationId: addUsersToIdpGroup
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/UpdateIdpGroupUsersRequest"
+            examples:
+              UpdateIdpGroupUsersRequest:
+                $ref: "#/components/examples/UpdateIdpGroupUsersRequest"
+      responses:
+        "200":
+          description: Returns the updated built-in IdP group
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpGroupResponse"
+              examples:
+                IdpGroupResponse:
+                  $ref: "#/components/examples/IdpGroupResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The specified built-in IdP group or user 
does not exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchGroupException:
+                  $ref: "#/components/examples/NoSuchGroupException"
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"

Review Comment:
   OpenAPI for `PUT /idp/groups/{group}/add` (and similarly `/remove`) is 
missing a `403` response, but mutations are service-admin-only and can return 
403 via `ForbiddenException`. Please document the 403 case for these endpoints.



##########
docs/open-api/idp.yaml:
##########
@@ -0,0 +1,514 @@
+# 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.
+
+---
+
+paths:
+
+  /idp/users:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP user
+      operationId: addIdpUser
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpUserRequest"
+            examples:
+              CreateIdpUserRequest:
+                $ref: "#/components/examples/CreateIdpUserRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP user already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                UserAlreadyExistsException:
+                  $ref: "#/components/examples/UserAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"

Review Comment:
   OpenAPI for `POST /idp/users` is missing a `403` response, but the 
implementation enforces service-admin-only mutations (and `ExceptionHandlers` 
maps `ForbiddenException` to 403). Please document the 403 case so generated 
clients and users understand the authorization requirement.



##########
core/src/main/java/org/apache/gravitino/authorization/IdpManagerFactory.java:
##########
@@ -0,0 +1,41 @@
+/*
+ * 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.authorization;
+
+import java.util.ServiceLoader;
+
+/** Factory for loading built-in IdP manager implementations from the runtime 
classpath. */
+public final class IdpManagerFactory {
+
+  private IdpManagerFactory() {}
+
+  /** Create the built-in IdP manager implementation. */
+  public static IdpManager create() {
+    return loadService(IdpManager.class);
+  }
+
+  private static <T> T loadService(Class<T> serviceClass) {
+    for (T service : ServiceLoader.load(serviceClass)) {
+      return service;
+    }
+
+    throw new IllegalStateException(
+        String.format("No %s implementation found", 
serviceClass.getSimpleName()));

Review Comment:
   `IdpManagerFactory.loadService(...)` returns the first `ServiceLoader` match 
without checking for multiple providers. If more than one IdP plugin is 
present, the chosen implementation becomes order-dependent and 
non-deterministic. Consider enforcing exactly one provider (throw on 0 or >1) 
similar to other ServiceLoader-based factories in the repo.
   



##########
docs/open-api/idp.yaml:
##########
@@ -0,0 +1,514 @@
+# 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.
+
+---
+
+paths:
+
+  /idp/users:
+    post:
+      tags:
+        - authentication
+      summary: Add built-in IdP user
+      operationId: addIdpUser
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/CreateIdpUserRequest"
+            examples:
+              CreateIdpUserRequest:
+                $ref: "#/components/examples/CreateIdpUserRequest"
+      responses:
+        "200":
+          description: Returns the added built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "409":
+          description: Conflict - The target built-in IdP user already exists
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                UserAlreadyExistsException:
+                  $ref: "#/components/examples/UserAlreadyExistsException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+  /idp/users/{user}:
+    parameters:
+      - $ref: "./openapi.yaml#/components/parameters/user"
+
+    get:
+      tags:
+        - authentication
+      summary: Get built-in IdP user
+      operationId: getIdpUser
+      responses:
+        "200":
+          description: Returns the built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    put:
+      tags:
+        - authentication
+      summary: Reset built-in IdP user password
+      operationId: resetIdpUserPassword
+      requestBody:
+        required: true
+        content:
+          application/json:
+            schema:
+              $ref: "#/components/schemas/ResetIdpUserPasswordRequest"
+            examples:
+              ResetIdpUserPasswordRequest:
+                $ref: "#/components/examples/ResetIdpUserPasswordRequest"
+      responses:
+        "200":
+          description: Returns the updated built-in IdP user
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "#/components/schemas/IdpUserResponse"
+              examples:
+                IdpUserResponse:
+                  $ref: "#/components/examples/IdpUserResponse"
+        "400":
+          $ref: "./openapi.yaml#/components/responses/BadRequestErrorResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":
+          $ref: "./openapi.yaml#/components/responses/ServerErrorResponse"
+
+    delete:
+      tags:
+        - authentication
+      summary: Remove built-in IdP user
+      operationId: removeIdpUser
+      responses:
+        "200":
+          $ref: "./openapi.yaml#/components/responses/RemoveResponse"
+        "404":
+          description: Not Found - The specified built-in IdP user does not 
exist
+          content:
+            application/vnd.gravitino.v1+json:
+              schema:
+                $ref: "./openapi.yaml#/components/schemas/ErrorModel"
+              examples:
+                NoSuchUserException:
+                  $ref: "#/components/examples/NoSuchUserException"
+        "5xx":

Review Comment:
   OpenAPI for `DELETE /idp/users/{user}` documents a `404` Not Found response, 
but the REST implementation returns `200` with `RemoveResponse.removed=false` 
when the user doesn’t exist (it doesn’t throw `NoSuchUserException`). Either 
adjust the API implementation to return 404 on missing users, or update the 
spec to reflect the actual 200/removed=false behavior. Also consider 
documenting possible `403` for non-service-admin callers.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to