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

mchades pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new f02647dc90 [#10963] feat(authn): Add built-in IdP model (#11022)
f02647dc90 is described below

commit f02647dc901d171122411aa2a111cd38cc191a31
Author: MaSai <[email protected]>
AuthorDate: Tue May 26 16:15:36 2026 +0800

    [#10963] feat(authn): Add built-in IdP model (#11022)
    
    ### What changes were proposed in this pull request?
    
    This PR adds the built-in IdP API model classes under the
    `plugins:idp-basic` module, including DTOs, requests, responses, and
    their unit tests.
    
    ### Why are the changes needed?
    
    The built-in IdP management work needs its API model classes to live
    with the `idp-basic` plugin instead of introducing them in broader
    shared modules.
    
    Fix: #10963
    
    ### Does this PR introduce _any_ user-facing change?
    
    No.
    
    ### How was this patch tested?
    
    ```bash
    ./gradlew :plugins:idp-basic:test \
      --tests org.apache.gravitino.idp.basic.dto.TestIdpUserDTO \
      --tests org.apache.gravitino.idp.basic.dto.TestIdpGroupDTO \
      --tests org.apache.gravitino.idp.basic.dto.requests.TestCreateUserRequest 
\
      --tests 
org.apache.gravitino.idp.basic.dto.requests.TestCreateGroupRequest \
      --tests 
org.apache.gravitino.idp.basic.dto.requests.TestResetPasswordRequest \
      --tests 
org.apache.gravitino.idp.basic.dto.requests.TestUpdateGroupUsersRequest \
      --tests org.apache.gravitino.idp.basic.dto.responses.TestIdpUserResponse \
      --tests org.apache.gravitino.idp.basic.dto.responses.TestIdpGroupResponse 
\
      -PskipWeb=true
    ```
    
    ---------
    
    Co-authored-by: Copilot <[email protected]>
    Co-authored-by: Cursor <[email protected]>
---
 design-docs/gravitino-local-authentication.md      |  8 +-
 plugins/idp-basic/build.gradle.kts                 |  4 +-
 .../apache/gravitino/idp/IdpUserGroupManager.java  | 17 ++--
 .../org/apache/gravitino/idp/dto/IdpGroupDTO.java  | 82 +++++++++++++++++++
 .../org/apache/gravitino/idp/dto/IdpUserDTO.java   | 83 +++++++++++++++++++
 .../idp/dto/requests/AddGroupRequest.java          | 68 ++++++++++++++++
 .../gravitino/idp/dto/requests/AddUserRequest.java | 76 +++++++++++++++++
 .../dto/requests/GroupMembershipChangeRequest.java | 84 +++++++++++++++++++
 .../idp/dto/requests/ResetPasswordRequest.java     | 69 ++++++++++++++++
 .../idp/dto/responses/IdpGroupResponse.java        | 79 ++++++++++++++++++
 .../idp/dto/responses/IdpUserResponse.java         | 79 ++++++++++++++++++
 .../idp/storage/service/IdpGroupMetaService.java   | 10 +--
 .../apache/gravitino/idp/dto/TestIdpGroupDTO.java  | 77 ++++++++++++++++++
 .../apache/gravitino/idp/dto/TestIdpUserDTO.java   | 77 ++++++++++++++++++
 .../idp/dto/requests/TestAddGroupRequest.java      | 58 +++++++++++++
 .../idp/dto/requests/TestAddUserRequest.java       | 71 ++++++++++++++++
 .../requests/TestGroupMembershipChangeRequest.java | 95 ++++++++++++++++++++++
 .../idp/dto/requests/TestResetPasswordRequest.java | 68 ++++++++++++++++
 .../idp/dto/responses/TestIdpGroupResponse.java    | 94 +++++++++++++++++++++
 .../idp/dto/responses/TestIdpUserResponse.java     | 94 +++++++++++++++++++++
 20 files changed, 1275 insertions(+), 18 deletions(-)

diff --git a/design-docs/gravitino-local-authentication.md 
b/design-docs/gravitino-local-authentication.md
index 498ff153ba..42f0dc2e29 100644
--- a/design-docs/gravitino-local-authentication.md
+++ b/design-docs/gravitino-local-authentication.md
@@ -518,9 +518,9 @@ http://localhost:8090/api/idp/users/alice
 }
 ```
 
-#### 9.1.2 Create a local user
+#### 9.1.2 Add a local user
 
-You can create a local user by providing a user name and password. The 
password must be stored as a
+You can add a local user by providing a user name and password. The password 
must be stored as a
 hash rather than plaintext.
 
 The request path for REST API is `/api/idp/users`.
@@ -631,9 +631,9 @@ http://localhost:8090/api/idp/groups/engineering
 }
 ```
 
-#### 9.1.6 Create a local group
+#### 9.1.6 Add a local group
 
-You can create a local group by providing a group name.
+You can add a local group by providing a group name.
 
 The request path for REST API is `/api/idp/groups`.
 
diff --git a/plugins/idp-basic/build.gradle.kts 
b/plugins/idp-basic/build.gradle.kts
index a977c0d581..26182a1608 100644
--- a/plugins/idp-basic/build.gradle.kts
+++ b/plugins/idp-basic/build.gradle.kts
@@ -32,6 +32,8 @@ dependencies {
   implementation(libs.bcprov.jdk18on)
   implementation(libs.commons.lang3)
   implementation(libs.guava)
+  implementation(libs.jackson.annotations)
+  implementation(libs.jackson.databind)
   implementation(libs.mybatis)
 
   compileOnly(libs.lombok)
@@ -42,9 +44,9 @@ dependencies {
   testImplementation(project(":integration-test-common", "testArtifacts"))
 
   testImplementation(libs.awaitility)
+  testImplementation(libs.commons.io)
   testImplementation(libs.junit.jupiter.api)
   testImplementation(libs.junit.jupiter.params)
-  testImplementation(libs.commons.io)
   testImplementation(libs.mysql.driver)
   testImplementation(libs.postgresql.driver)
   testImplementation(libs.testcontainers)
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/IdpUserGroupManager.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/IdpUserGroupManager.java
index 38065951a4..bb26bfa4fc 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/IdpUserGroupManager.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/IdpUserGroupManager.java
@@ -147,18 +147,19 @@ public class IdpUserGroupManager implements Closeable {
    * Changes built-in IdP group membership.
    *
    * @param groupName The group name.
-   * @param additions The usernames to add, or null if none.
-   * @param removals The usernames to remove, or null if none.
+   * @param usersToAdd The usernames to add, or null if none.
+   * @param usersToRemove The usernames to remove, or null if none.
    * @return The updated built-in IdP group.
    */
   public IdpGroup changeGroupMembership(
-      String groupName, @Nullable List<String> additions, @Nullable 
List<String> removals) {
-    List<String> additionsList = additions == null ? Collections.emptyList() : 
additions;
-    List<String> removalsList = removals == null ? Collections.emptyList() : 
removals;
+      String groupName, @Nullable List<String> usersToAdd, @Nullable 
List<String> usersToRemove) {
+    List<String> usersToAddList = usersToAdd == null ? Collections.emptyList() 
: usersToAdd;
+    List<String> usersToRemoveList =
+        usersToRemove == null ? Collections.emptyList() : usersToRemove;
     Preconditions.checkArgument(
-        !additionsList.isEmpty() || !removalsList.isEmpty(),
-        "additions and removals cannot both be empty");
-    GROUP_SERVICE.changeGroupMembership(groupName, additionsList, 
removalsList);
+        !usersToAddList.isEmpty() || !usersToRemoveList.isEmpty(),
+        "usersToAdd and usersToRemove cannot both be empty");
+    GROUP_SERVICE.changeGroupMembership(groupName, usersToAddList, 
usersToRemoveList);
     return getGroup(groupName);
   }
 
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpGroupDTO.java 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpGroupDTO.java
new file mode 100644
index 0000000000..2fc1120466
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpGroupDTO.java
@@ -0,0 +1,82 @@
+/*
+ * 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.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+
+/** Represents a built-in IdP group Data Transfer Object (DTO). */
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+@ToString
+public class IdpGroupDTO {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("users")
+  @JsonSetter(nulls = Nulls.AS_EMPTY)
+  private List<String> users = Collections.emptyList();
+
+  /**
+   * Creates a new instance of IdpGroupDTO.
+   *
+   * @param name The name of the built-in IdP group DTO.
+   * @param users The users of the built-in IdP group DTO.
+   */
+  @Builder(setterPrefix = "with")
+  protected IdpGroupDTO(String name, List<String> users) {
+    Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot be 
null or empty");
+    if (users != null) {
+      users.forEach(
+          user ->
+              Preconditions.checkArgument(
+                  StringUtils.isNotBlank(user), "users cannot contain null or 
empty user names"));
+    }
+    this.name = name;
+    this.users = users == null ? Collections.emptyList() : users;
+  }
+
+  /**
+   * @return The name of the built-in IdP group DTO.
+   */
+  public String name() {
+    return name;
+  }
+
+  /**
+   * The users of the built-in IdP group. A group can contain multiple users.
+   *
+   * @return The users of the built-in IdP group.
+   */
+  public List<String> users() {
+    return users;
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpUserDTO.java 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpUserDTO.java
new file mode 100644
index 0000000000..80ec5c4e76
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/IdpUserDTO.java
@@ -0,0 +1,83 @@
+/*
+ * 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.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.List;
+import lombok.AccessLevel;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+
+/** Represents a built-in IdP user Data Transfer Object (DTO). */
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@EqualsAndHashCode
+@ToString
+public class IdpUserDTO {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("groups")
+  @JsonSetter(nulls = Nulls.AS_EMPTY)
+  private List<String> groups = Collections.emptyList();
+
+  /**
+   * Creates a new instance of IdpUserDTO.
+   *
+   * @param name The name of the built-in IdP user DTO.
+   * @param groups The groups of the built-in IdP user DTO.
+   */
+  @Builder(setterPrefix = "with")
+  protected IdpUserDTO(String name, List<String> groups) {
+    Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot be 
null or empty");
+    if (groups != null) {
+      groups.forEach(
+          group ->
+              Preconditions.checkArgument(
+                  StringUtils.isNotBlank(group),
+                  "groups cannot contain null or empty group names"));
+    }
+    this.name = name;
+    this.groups = groups == null ? Collections.emptyList() : groups;
+  }
+
+  /**
+   * @return The name of the built-in IdP user DTO.
+   */
+  public String name() {
+    return name;
+  }
+
+  /**
+   * The groups of the built-in IdP user. A user can belong to multiple groups.
+   *
+   * @return The groups of the built-in IdP user.
+   */
+  public List<String> groups() {
+    return groups;
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddGroupRequest.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddGroupRequest.java
new file mode 100644
index 0000000000..54e0c92c5d
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddGroupRequest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.jackson.Jacksonized;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to add a built-in IdP group. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder
+@Jacksonized
+public class AddGroupRequest implements RESTRequest {
+
+  @JsonProperty("group")
+  private final String group;
+
+  /** Default constructor for AddGroupRequest. (Used for Jackson 
deserialization.) */
+  public AddGroupRequest() {
+    this(null);
+  }
+
+  /**
+   * Creates a new AddGroupRequest.
+   *
+   * @param group The group name of the built-in IdP group.
+   */
+  public AddGroupRequest(String group) {
+    super();
+    this.group = group;
+  }
+
+  /**
+   * Validates the {@link AddGroupRequest} request.
+   *
+   * @throws IllegalArgumentException If the request is invalid, this 
exception is thrown.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(group), "\"group\" field is required and cannot 
be empty");
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddUserRequest.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddUserRequest.java
new file mode 100644
index 0000000000..8625eff833
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/AddUserRequest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.jackson.Jacksonized;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to add a built-in IdP user. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder
+@Jacksonized
+public class AddUserRequest implements RESTRequest {
+
+  @JsonProperty("user")
+  private final String user;
+
+  @JsonProperty("password")
+  @ToString.Exclude
+  private final String password;
+
+  /** Default constructor for AddUserRequest. (Used for Jackson 
deserialization.) */
+  public AddUserRequest() {
+    this(null, null);
+  }
+
+  /**
+   * Creates a new AddUserRequest.
+   *
+   * @param user The user name of the built-in IdP user.
+   * @param password The password of the built-in IdP user.
+   */
+  public AddUserRequest(String user, String password) {
+    super();
+    this.user = user;
+    this.password = password;
+  }
+
+  /**
+   * Validates the {@link AddUserRequest} request.
+   *
+   * @throws IllegalArgumentException If the request is invalid, this 
exception is thrown.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(user), "\"user\" field is required and cannot 
be empty");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(password), "\"password\" field is required and 
cannot be empty");
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/GroupMembershipChangeRequest.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/GroupMembershipChangeRequest.java
new file mode 100644
index 0000000000..1cc7b07f7b
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/GroupMembershipChangeRequest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to change built-in IdP group membership. */
+@Getter
+@EqualsAndHashCode
+@ToString
+public class GroupMembershipChangeRequest implements RESTRequest {
+
+  @JsonProperty("usersToAdd")
+  private final String[] usersToAdd;
+
+  @JsonProperty("usersToRemove")
+  private final String[] usersToRemove;
+
+  /**
+   * Creates a new GroupMembershipChangeRequest.
+   *
+   * @param usersToAdd The user names to add to the built-in IdP group.
+   * @param usersToRemove The user names to remove from the built-in IdP group.
+   */
+  public GroupMembershipChangeRequest(String[] usersToAdd, String[] 
usersToRemove) {
+    this.usersToAdd = usersToAdd;
+    this.usersToRemove = usersToRemove;
+  }
+
+  /** Default constructor for GroupMembershipChangeRequest. (Used for Jackson 
deserialization.) */
+  public GroupMembershipChangeRequest() {
+    this(null, null);
+  }
+
+  /**
+   * Validates the {@link GroupMembershipChangeRequest} request.
+   *
+   * @throws IllegalArgumentException If the request is invalid, this 
exception is thrown.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        usersToAdd != null || usersToRemove != null,
+        "usersToAdd and usersToRemove cannot both be null");
+
+    if (usersToAdd != null) {
+      for (String user : usersToAdd) {
+        Preconditions.checkArgument(
+            StringUtils.isNotBlank(user), "usersToAdd must not contain null or 
empty user names");
+      }
+    }
+
+    if (usersToRemove != null) {
+      for (String user : usersToRemove) {
+        Preconditions.checkArgument(
+            StringUtils.isNotBlank(user),
+            "usersToRemove must not contain null or empty user names");
+      }
+    }
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/ResetPasswordRequest.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/ResetPasswordRequest.java
new file mode 100644
index 0000000000..a6b1908b33
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/requests/ResetPasswordRequest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import lombok.extern.jackson.Jacksonized;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to reset a built-in IdP user password. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder
+@Jacksonized
+public class ResetPasswordRequest implements RESTRequest {
+
+  @JsonProperty("password")
+  @ToString.Exclude
+  private final String password;
+
+  /** Default constructor for ResetPasswordRequest. (Used for Jackson 
deserialization.) */
+  public ResetPasswordRequest() {
+    this(null);
+  }
+
+  /**
+   * Creates a new ResetPasswordRequest.
+   *
+   * @param password The new password of the built-in IdP user.
+   */
+  public ResetPasswordRequest(String password) {
+    super();
+    this.password = password;
+  }
+
+  /**
+   * Validates the {@link ResetPasswordRequest} request.
+   *
+   * @throws IllegalArgumentException If the request is invalid, this 
exception is thrown.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(password), "\"password\" field is required and 
cannot be empty");
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpGroupResponse.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpGroupResponse.java
new file mode 100644
index 0000000000..b8a81f2408
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpGroupResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.idp.dto.IdpGroupDTO;
+
+/** Represents a response for a built-in IdP group. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class IdpGroupResponse extends BaseResponse {
+
+  @JsonProperty("group")
+  private final IdpGroupDTO group;
+
+  /**
+   * Constructor for IdpGroupResponse.
+   *
+   * @param group The built-in IdP group data transfer object.
+   */
+  public IdpGroupResponse(IdpGroupDTO group) {
+    super(0);
+    this.group = group;
+  }
+
+  /** Default constructor for IdpGroupResponse. (Used for Jackson 
deserialization.) */
+  public IdpGroupResponse() {
+    super();
+    this.group = null;
+  }
+
+  /**
+   * Validates the response data.
+   *
+   * @throws IllegalArgumentException if the name is not set.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    Preconditions.checkArgument(group != null, "group must not be null");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(group.name()), "group 'name' must not be null 
or empty");
+    validateNames(group.users(), "group 'users' must not contain null or empty 
user names");
+  }
+
+  private void validateNames(List<String> names, String errorMessage) {
+    if (names == null) {
+      return;
+    }
+
+    names.forEach(name -> 
Preconditions.checkArgument(StringUtils.isNotBlank(name), errorMessage));
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpUserResponse.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpUserResponse.java
new file mode 100644
index 0000000000..97ddd2cfc3
--- /dev/null
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/dto/responses/IdpUserResponse.java
@@ -0,0 +1,79 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import java.util.List;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.responses.BaseResponse;
+import org.apache.gravitino.idp.dto.IdpUserDTO;
+
+/** Represents a response for a built-in IdP user. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class IdpUserResponse extends BaseResponse {
+
+  @JsonProperty("user")
+  private final IdpUserDTO user;
+
+  /**
+   * Constructor for IdpUserResponse.
+   *
+   * @param user The built-in IdP user data transfer object.
+   */
+  public IdpUserResponse(IdpUserDTO user) {
+    super(0);
+    this.user = user;
+  }
+
+  /** Default constructor for IdpUserResponse. (Used for Jackson 
deserialization.) */
+  public IdpUserResponse() {
+    super();
+    this.user = null;
+  }
+
+  /**
+   * Validates the response data.
+   *
+   * @throws IllegalArgumentException if the name is not set.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+
+    Preconditions.checkArgument(user != null, "user must not be null");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(user.name()), "user 'name' must not be null or 
empty");
+    validateNames(user.groups(), "user 'groups' must not contain null or empty 
group names");
+  }
+
+  private void validateNames(List<String> names, String errorMessage) {
+    if (names == null) {
+      return;
+    }
+
+    names.forEach(name -> 
Preconditions.checkArgument(StringUtils.isNotBlank(name), errorMessage));
+  }
+}
diff --git 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
index 09313581f9..93394b8475 100644
--- 
a/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
+++ 
b/plugins/idp-basic/src/main/java/org/apache/gravitino/idp/storage/service/IdpGroupMetaService.java
@@ -122,14 +122,14 @@ public class IdpGroupMetaService {
    * Changes built-in IdP group membership in a single transaction.
    *
    * @param groupName The group name.
-   * @param additions The usernames to add.
-   * @param removals The usernames to remove.
+   * @param usersToAdd The usernames to add.
+   * @param usersToRemove The usernames to remove.
    */
   @Monitored(
       metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
       baseMetricName = "changeGroupMembership")
   public void changeGroupMembership(
-      String groupName, List<String> additions, List<String> removals) {
+      String groupName, List<String> usersToAdd, List<String> usersToRemove) {
     SessionUtils.doMultipleWithCommit(
         () -> {
           IdpGroupPO group =
@@ -149,8 +149,8 @@ public class IdpGroupMetaService {
                   mapper -> mapper.selectUsernamesByGroupName(groupName));
           Set<String> oldUsernames = Sets.newHashSet(currentUsernames);
           Set<String> newUsernames = Sets.newHashSet(oldUsernames);
-          newUsernames.addAll(additions);
-          newUsernames.removeAll(removals);
+          newUsernames.addAll(usersToAdd);
+          newUsernames.removeAll(usersToRemove);
 
           Set<String> insertUsernames = Sets.difference(newUsernames, 
oldUsernames);
           Set<String> deleteUsernames = Sets.difference(oldUsernames, 
newUsernames);
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpGroupDTO.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpGroupDTO.java
new file mode 100644
index 0000000000..d0b5bd75ac
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpGroupDTO.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dto;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Arrays;
+import java.util.Collections;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestIdpGroupDTO {
+
+  @Test
+  public void testIdpGroupDTOSerDe() throws JsonProcessingException {
+    IdpGroupDTO groupDTO =
+        IdpGroupDTO.builder()
+            .withName("test_group")
+            .withUsers(Arrays.asList("user1", "user2"))
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(groupDTO);
+    IdpGroupDTO deserialized = JsonUtils.objectMapper().readValue(json, 
IdpGroupDTO.class);
+
+    Assertions.assertEquals(groupDTO, deserialized);
+    Assertions.assertEquals("test_group", deserialized.name());
+    Assertions.assertEquals(Arrays.asList("user1", "user2"), 
deserialized.users());
+
+    // Test with default users
+    IdpGroupDTO groupDTO1 = 
IdpGroupDTO.builder().withName("test_group").build();
+
+    String json1 = JsonUtils.objectMapper().writeValueAsString(groupDTO1);
+    IdpGroupDTO deserialized1 = JsonUtils.objectMapper().readValue(json1, 
IdpGroupDTO.class);
+
+    Assertions.assertEquals(groupDTO1, deserialized1);
+    Assertions.assertEquals("test_group", deserialized1.name());
+    Assertions.assertTrue(deserialized1.users().isEmpty());
+    Assertions.assertEquals("IdpGroupDTO(name=test_group, users=[])", 
deserialized1.toString());
+
+    IdpGroupDTO deserializedWithNullUsers =
+        JsonUtils.objectMapper()
+            .readValue("{\"name\":\"test_group\",\"users\":null}", 
IdpGroupDTO.class);
+    Assertions.assertTrue(deserializedWithNullUsers.users().isEmpty());
+    Assertions.assertEquals(
+        "IdpGroupDTO(name=test_group, users=[])", 
deserializedWithNullUsers.toString());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> IdpGroupDTO.builder().withName(" 
").build());
+    IllegalArgumentException invalidUserException =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                IdpGroupDTO.builder()
+                    .withName("test_group")
+                    .withUsers(Collections.singletonList(" "))
+                    .build());
+    Assertions.assertEquals(
+        "users cannot contain null or empty user names", 
invalidUserException.getMessage());
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpUserDTO.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpUserDTO.java
new file mode 100644
index 0000000000..b0cb2545bc
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/TestIdpUserDTO.java
@@ -0,0 +1,77 @@
+/*
+ * 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.dto;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Arrays;
+import java.util.Collections;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestIdpUserDTO {
+
+  @Test
+  public void testIdpUserDTOSerDe() throws JsonProcessingException {
+    IdpUserDTO userDTO =
+        IdpUserDTO.builder()
+            .withName("test_user")
+            .withGroups(Arrays.asList("group1", "group2"))
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(userDTO);
+    IdpUserDTO deserialized = JsonUtils.objectMapper().readValue(json, 
IdpUserDTO.class);
+
+    Assertions.assertEquals(userDTO, deserialized);
+    Assertions.assertEquals("test_user", deserialized.name());
+    Assertions.assertEquals(Arrays.asList("group1", "group2"), 
deserialized.groups());
+
+    // Test with default groups
+    IdpUserDTO userDTO1 = IdpUserDTO.builder().withName("test_user").build();
+
+    String json1 = JsonUtils.objectMapper().writeValueAsString(userDTO1);
+    IdpUserDTO deserialized1 = JsonUtils.objectMapper().readValue(json1, 
IdpUserDTO.class);
+
+    Assertions.assertEquals(userDTO1, deserialized1);
+    Assertions.assertEquals("test_user", deserialized1.name());
+    Assertions.assertTrue(deserialized1.groups().isEmpty());
+    Assertions.assertEquals("IdpUserDTO(name=test_user, groups=[])", 
deserialized1.toString());
+
+    IdpUserDTO deserializedWithNullGroups =
+        JsonUtils.objectMapper()
+            .readValue("{\"name\":\"test_user\",\"groups\":null}", 
IdpUserDTO.class);
+    Assertions.assertTrue(deserializedWithNullGroups.groups().isEmpty());
+    Assertions.assertEquals(
+        "IdpUserDTO(name=test_user, groups=[])", 
deserializedWithNullGroups.toString());
+
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> IdpUserDTO.builder().withName(" 
").build());
+    IllegalArgumentException invalidGroupException =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () ->
+                IdpUserDTO.builder()
+                    .withName("test_user")
+                    .withGroups(Collections.singletonList(" "))
+                    .build());
+    Assertions.assertEquals(
+        "groups cannot contain null or empty group names", 
invalidGroupException.getMessage());
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddGroupRequest.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddGroupRequest.java
new file mode 100644
index 0000000000..cdc24d1f60
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddGroupRequest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestAddGroupRequest {
+
+  @Test
+  public void testAddGroupRequestSerDe() throws JsonProcessingException {
+    AddGroupRequest request = new AddGroupRequest("test_group");
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    AddGroupRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, AddGroupRequest.class);
+
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("test_group", deserRequest.getGroup());
+
+    // Test with null group
+    AddGroupRequest request1 = new AddGroupRequest();
+
+    String serJson1 = JsonUtils.objectMapper().writeValueAsString(request1);
+    AddGroupRequest deserRequest1 =
+        JsonUtils.objectMapper().readValue(serJson1, AddGroupRequest.class);
+
+    Assertions.assertEquals(request1, deserRequest1);
+    Assertions.assertNull(deserRequest1.getGroup());
+  }
+
+  @Test
+  public void testAddGroupRequestValidate() {
+    Assertions.assertDoesNotThrow(() -> new 
AddGroupRequest("test_group").validate());
+    Assertions.assertThrows(IllegalArgumentException.class, () -> new 
AddGroupRequest().validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> new AddGroupRequest(" 
").validate());
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddUserRequest.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddUserRequest.java
new file mode 100644
index 0000000000..cbe3618945
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestAddUserRequest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestAddUserRequest {
+
+  @Test
+  public void testAddUserRequestSerDe() throws JsonProcessingException {
+    AddUserRequest request = new AddUserRequest("test_user", "password");
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    AddUserRequest deserRequest = JsonUtils.objectMapper().readValue(serJson, 
AddUserRequest.class);
+
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("test_user", deserRequest.getUser());
+    Assertions.assertEquals("password", deserRequest.getPassword());
+
+    // Test with null user and password
+    AddUserRequest request1 = new AddUserRequest();
+
+    String serJson1 = JsonUtils.objectMapper().writeValueAsString(request1);
+    AddUserRequest deserRequest1 =
+        JsonUtils.objectMapper().readValue(serJson1, AddUserRequest.class);
+
+    Assertions.assertEquals(request1, deserRequest1);
+    Assertions.assertNull(deserRequest1.getUser());
+    Assertions.assertNull(deserRequest1.getPassword());
+  }
+
+  @Test
+  public void testAddUserRequestValidate() {
+    Assertions.assertDoesNotThrow(() -> new AddUserRequest("test_user", 
"password").validate());
+    Assertions.assertThrows(IllegalArgumentException.class, () -> new 
AddUserRequest().validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> new AddUserRequest(" ", 
"password").validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> new AddUserRequest("test_user", 
" ").validate());
+  }
+
+  @Test
+  public void testAddUserRequestToStringDoesNotExposePassword() {
+    String requestString = new AddUserRequest("test_user", 
"password").toString();
+
+    Assertions.assertTrue(requestString.contains("test_user"));
+    Assertions.assertFalse(requestString.contains("password="));
+    Assertions.assertFalse(requestString.contains("password)"));
+    Assertions.assertFalse(requestString.contains("\"password\""));
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestGroupMembershipChangeRequest.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestGroupMembershipChangeRequest.java
new file mode 100644
index 0000000000..51db3b5fb8
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestGroupMembershipChangeRequest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestGroupMembershipChangeRequest {
+
+  @Test
+  public void testGroupMembershipChangeRequestSerDe() throws 
JsonProcessingException {
+    GroupMembershipChangeRequest request =
+        new GroupMembershipChangeRequest(new String[] {"user1", "user2"}, new 
String[] {"user3"});
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    GroupMembershipChangeRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, 
GroupMembershipChangeRequest.class);
+
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertArrayEquals(new String[] {"user1", "user2"}, 
deserRequest.getUsersToAdd());
+    Assertions.assertArrayEquals(new String[] {"user3"}, 
deserRequest.getUsersToRemove());
+
+    GroupMembershipChangeRequest request1 = new GroupMembershipChangeRequest();
+
+    String serJson1 = JsonUtils.objectMapper().writeValueAsString(request1);
+    GroupMembershipChangeRequest deserRequest1 =
+        JsonUtils.objectMapper().readValue(serJson1, 
GroupMembershipChangeRequest.class);
+
+    Assertions.assertEquals(request1, deserRequest1);
+    Assertions.assertNull(deserRequest1.getUsersToAdd());
+    Assertions.assertNull(deserRequest1.getUsersToRemove());
+  }
+
+  @Test
+  public void testGroupMembershipChangeRequestValidate() {
+    Assertions.assertDoesNotThrow(
+        () ->
+            new GroupMembershipChangeRequest(new String[] {"user1"}, new 
String[] {"user2"})
+                .validate());
+    Assertions.assertDoesNotThrow(
+        () -> new GroupMembershipChangeRequest(new String[] {"user1"}, 
null).validate());
+    Assertions.assertDoesNotThrow(
+        () -> new GroupMembershipChangeRequest(null, new String[] 
{"user2"}).validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> new GroupMembershipChangeRequest(null, null).validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> new GroupMembershipChangeRequest(new String[] {" "}, 
null).validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class,
+        () -> new GroupMembershipChangeRequest(null, new String[] {"user1", 
""}).validate());
+  }
+
+  @Test
+  public void testGroupMembershipChangeRequestValidateNullUserInUsersToAdd() {
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> new GroupMembershipChangeRequest(new String[] {"user1", 
null}, null).validate());
+
+    Assertions.assertEquals(
+        "usersToAdd must not contain null or empty user names", 
exception.getMessage());
+  }
+
+  @Test
+  public void 
testGroupMembershipChangeRequestValidateNullUserInUsersToRemove() {
+    IllegalArgumentException exception =
+        Assertions.assertThrows(
+            IllegalArgumentException.class,
+            () -> new GroupMembershipChangeRequest(null, new String[] 
{null}).validate());
+
+    Assertions.assertEquals(
+        "usersToRemove must not contain null or empty user names", 
exception.getMessage());
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestResetPasswordRequest.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestResetPasswordRequest.java
new file mode 100644
index 0000000000..aa6ed997d7
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/requests/TestResetPasswordRequest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestResetPasswordRequest {
+
+  @Test
+  public void testResetPasswordRequestSerDe() throws JsonProcessingException {
+    ResetPasswordRequest request = new ResetPasswordRequest("new_password");
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(request);
+    ResetPasswordRequest deserRequest =
+        JsonUtils.objectMapper().readValue(serJson, 
ResetPasswordRequest.class);
+
+    Assertions.assertEquals(request, deserRequest);
+    Assertions.assertEquals("new_password", deserRequest.getPassword());
+
+    // Test with null password
+    ResetPasswordRequest request1 = new ResetPasswordRequest();
+
+    String serJson1 = JsonUtils.objectMapper().writeValueAsString(request1);
+    ResetPasswordRequest deserRequest1 =
+        JsonUtils.objectMapper().readValue(serJson1, 
ResetPasswordRequest.class);
+
+    Assertions.assertEquals(request1, deserRequest1);
+    Assertions.assertNull(deserRequest1.getPassword());
+  }
+
+  @Test
+  public void testResetPasswordRequestValidate() {
+    Assertions.assertDoesNotThrow(() -> new 
ResetPasswordRequest("new_password").validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> new 
ResetPasswordRequest().validate());
+    Assertions.assertThrows(
+        IllegalArgumentException.class, () -> new ResetPasswordRequest(" 
").validate());
+  }
+
+  @Test
+  public void testResetPasswordRequestToStringDoesNotExposePassword() {
+    String requestString = new ResetPasswordRequest("new_password").toString();
+
+    Assertions.assertFalse(requestString.contains("new_password"));
+    Assertions.assertFalse(requestString.contains("password="));
+    Assertions.assertFalse(requestString.contains("\"password\""));
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpGroupResponse.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpGroupResponse.java
new file mode 100644
index 0000000000..48e07b4dcd
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpGroupResponse.java
@@ -0,0 +1,94 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Arrays;
+import org.apache.gravitino.idp.dto.IdpGroupDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestIdpGroupResponse {
+
+  @Test
+  public void testIdpGroupResponseSerDe() throws JsonProcessingException {
+    IdpGroupDTO group =
+        IdpGroupDTO.builder()
+            .withName("test_group")
+            .withUsers(Arrays.asList("user1", "user2"))
+            .build();
+    IdpGroupResponse response = new IdpGroupResponse(group);
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(response);
+    IdpGroupResponse deserResponse =
+        JsonUtils.objectMapper().readValue(serJson, IdpGroupResponse.class);
+
+    Assertions.assertEquals(response, deserResponse);
+    Assertions.assertEquals("test_group", deserResponse.getGroup().name());
+    Assertions.assertEquals(Arrays.asList("user1", "user2"), 
deserResponse.getGroup().users());
+  }
+
+  @Test
+  public void testIdpGroupResponseValidate() {
+    IdpGroupDTO group =
+        IdpGroupDTO.builder()
+            .withName("test_group")
+            .withUsers(Arrays.asList("user1", "user2"))
+            .build();
+    IdpGroupResponse response = new IdpGroupResponse(group);
+    response.validate(); // No exception thrown
+  }
+
+  @Test
+  public void testIdpGroupResponseException() {
+    IdpGroupResponse response = new IdpGroupResponse();
+    Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+  }
+
+  @Test
+  public void testIdpGroupResponseBlankNameMessage() throws 
JsonProcessingException {
+    IdpGroupResponse response =
+        JsonUtils.objectMapper()
+            .readValue(
+                "{\"code\":0,\"group\":{\"name\":\" 
\",\"users\":[\"user1\"]}}",
+                IdpGroupResponse.class);
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+
+    Assertions.assertEquals("group 'name' must not be null or empty", 
exception.getMessage());
+  }
+
+  @Test
+  public void testIdpGroupResponseBlankUserMessage() throws 
JsonProcessingException {
+    IdpGroupResponse response =
+        JsonUtils.objectMapper()
+            .readValue(
+                "{\"code\":0,\"group\":{\"name\":\"test_group\",\"users\":[\" 
\"]}}",
+                IdpGroupResponse.class);
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+
+    Assertions.assertEquals(
+        "group 'users' must not contain null or empty user names", 
exception.getMessage());
+  }
+}
diff --git 
a/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpUserResponse.java
 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpUserResponse.java
new file mode 100644
index 0000000000..3fa33f5269
--- /dev/null
+++ 
b/plugins/idp-basic/src/test/java/org/apache/gravitino/idp/dto/responses/TestIdpUserResponse.java
@@ -0,0 +1,94 @@
+/*
+ * 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.dto.responses;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Arrays;
+import org.apache.gravitino.idp.dto.IdpUserDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestIdpUserResponse {
+
+  @Test
+  public void testIdpUserResponseSerDe() throws JsonProcessingException {
+    IdpUserDTO user =
+        IdpUserDTO.builder()
+            .withName("test_user")
+            .withGroups(Arrays.asList("group1", "group2"))
+            .build();
+    IdpUserResponse response = new IdpUserResponse(user);
+
+    String serJson = JsonUtils.objectMapper().writeValueAsString(response);
+    IdpUserResponse deserResponse =
+        JsonUtils.objectMapper().readValue(serJson, IdpUserResponse.class);
+
+    Assertions.assertEquals(response, deserResponse);
+    Assertions.assertEquals("test_user", deserResponse.getUser().name());
+    Assertions.assertEquals(Arrays.asList("group1", "group2"), 
deserResponse.getUser().groups());
+  }
+
+  @Test
+  public void testIdpUserResponseValidate() {
+    IdpUserDTO user =
+        IdpUserDTO.builder()
+            .withName("test_user")
+            .withGroups(Arrays.asList("group1", "group2"))
+            .build();
+    IdpUserResponse response = new IdpUserResponse(user);
+    response.validate(); // No exception thrown
+  }
+
+  @Test
+  public void testIdpUserResponseException() {
+    IdpUserResponse response = new IdpUserResponse();
+    Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+  }
+
+  @Test
+  public void testIdpUserResponseBlankNameMessage() throws 
JsonProcessingException {
+    IdpUserResponse response =
+        JsonUtils.objectMapper()
+            .readValue(
+                "{\"code\":0,\"user\":{\"name\":\" 
\",\"groups\":[\"group1\"]}}",
+                IdpUserResponse.class);
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+
+    Assertions.assertEquals("user 'name' must not be null or empty", 
exception.getMessage());
+  }
+
+  @Test
+  public void testIdpUserResponseBlankGroupMessage() throws 
JsonProcessingException {
+    IdpUserResponse response =
+        JsonUtils.objectMapper()
+            .readValue(
+                "{\"code\":0,\"user\":{\"name\":\"test_user\",\"groups\":[\" 
\"]}}",
+                IdpUserResponse.class);
+
+    IllegalArgumentException exception =
+        Assertions.assertThrows(IllegalArgumentException.class, 
response::validate);
+
+    Assertions.assertEquals(
+        "user 'groups' must not contain null or empty group names", 
exception.getMessage());
+  }
+}

Reply via email to