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

fanng 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 0911a63cd2 [#6711] feat(core): Support user pre-event to Gravitino 
server (#6714)
0911a63cd2 is described below

commit 0911a63cd2503dba96e91aa70bcdf1c87c89c8f2
Author: Lord of Abyss <[email protected]>
AuthorDate: Mon Mar 24 21:21:13 2025 +0800

    [#6711] feat(core): Support user pre-event to Gravitino server (#6714)
    
    ### What changes were proposed in this pull request?
    
    Support user pre-event to Gravitino server.
    <img width="1663" alt="image"
    
src="https://github.com/user-attachments/assets/ecd81df0-af3a-41d9-8709-1b8d2eae677c";
    />
    
    
    ### Why are the changes needed?
    
    Fix: #6711
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    local test.
---
 .../java/org/apache/gravitino/GravitinoEnv.java    |  10 +-
 .../api/event/AccessControlEventDispatcher.java    | 361 +++++++++++++++++++++
 .../listener/api/event/AddUserPreEvent.java        |  61 ++++
 .../listener/api/event/GetUserPreEvent.java        |  61 ++++
 .../listener/api/event/ListUserNamesPreEvent.java  |  48 +++
 .../listener/api/event/ListUsersPreEvent.java      |  48 +++
 .../listener/api/event/OperationType.java          |  11 +
 .../listener/api/event/RemoveUserPreEvent.java     |  61 ++++
 .../gravitino/listener/api/event/UserPreEvent.java |  38 +++
 .../listener/api/event/TestUserEvent.java          | 187 +++++++++++
 docs/gravitino-server-config.md                    |  21 +-
 11 files changed, 893 insertions(+), 14 deletions(-)

diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java 
b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
index 385bc53392..4e9fc02b49 100644
--- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
+++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java
@@ -65,6 +65,7 @@ import org.apache.gravitino.listener.SchemaEventDispatcher;
 import org.apache.gravitino.listener.TableEventDispatcher;
 import org.apache.gravitino.listener.TagEventDispatcher;
 import org.apache.gravitino.listener.TopicEventDispatcher;
+import org.apache.gravitino.listener.api.event.AccessControlEventDispatcher;
 import org.apache.gravitino.lock.LockManager;
 import org.apache.gravitino.metalake.MetalakeDispatcher;
 import org.apache.gravitino.metalake.MetalakeManager;
@@ -492,11 +493,12 @@ public class GravitinoEnv {
     // Create and initialize access control related modules
     boolean enableAuthorization = config.get(Configs.ENABLE_AUTHORIZATION);
     if (enableAuthorization) {
+      AccessControlManager accessControlManager =
+          new AccessControlManager(entityStore, idGenerator, config);
       AccessControlHookDispatcher accessControlHookDispatcher =
-          new AccessControlHookDispatcher(
-              new AccessControlManager(entityStore, idGenerator, config));
-
-      this.accessControlDispatcher = accessControlHookDispatcher;
+          new AccessControlHookDispatcher(accessControlManager);
+      this.accessControlDispatcher =
+          new AccessControlEventDispatcher(eventBus, 
accessControlHookDispatcher);
       this.ownerManager = new OwnerManager(entityStore);
       this.futureGrantManager = new FutureGrantManager(entityStore, 
ownerManager);
     } else {
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
new file mode 100644
index 0000000000..7c658ef942
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AccessControlEventDispatcher.java
@@ -0,0 +1,361 @@
+/*
+ * 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.listener.api.event;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.gravitino.MetadataObject;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.AccessControlDispatcher;
+import org.apache.gravitino.authorization.Group;
+import org.apache.gravitino.authorization.Privilege;
+import org.apache.gravitino.authorization.Role;
+import org.apache.gravitino.authorization.SecurableObject;
+import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
+import org.apache.gravitino.exceptions.IllegalRoleException;
+import org.apache.gravitino.exceptions.NoSuchGroupException;
+import org.apache.gravitino.exceptions.NoSuchMetadataObjectException;
+import org.apache.gravitino.exceptions.NoSuchMetalakeException;
+import org.apache.gravitino.exceptions.NoSuchRoleException;
+import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.exceptions.RoleAlreadyExistsException;
+import org.apache.gravitino.exceptions.UserAlreadyExistsException;
+import org.apache.gravitino.listener.EventBus;
+import org.apache.gravitino.utils.PrincipalUtils;
+
+/**
+ * An implementation of the {@link AccessControlDispatcher} interface that 
dispatches events to the
+ * specified event bus.
+ */
+public class AccessControlEventDispatcher implements AccessControlDispatcher {
+  private final EventBus eventBus;
+  private final AccessControlDispatcher dispatcher;
+
+  /**
+   * Construct a new {@link AccessControlEventDispatcher} instance with the 
specified event bus and
+   * access control dispatcher.
+   *
+   * @param eventBus The EventBus to which events will be dispatched.
+   * @param dispatcher The underlying {@link AccessControlDispatcher} that 
will perform the actual
+   *     access control operations.
+   */
+  public AccessControlEventDispatcher(EventBus eventBus, 
AccessControlDispatcher dispatcher) {
+    this.eventBus = eventBus;
+    this.dispatcher = dispatcher;
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public User addUser(String metalake, String user)
+      throws UserAlreadyExistsException, NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new AddUserPreEvent(initiator, 
NameIdentifier.of(metalake), user));
+    try {
+      // TODO add Event
+      return dispatcher.addUser(metalake, user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean removeUser(String metalake, String user) throws 
NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new RemoveUserPreEvent(initiator, 
NameIdentifier.of(metalake), user));
+    try {
+      // TODO: add Event
+      return dispatcher.removeUser(metalake, user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public User getUser(String metalake, String user)
+      throws NoSuchUserException, NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new GetUserPreEvent(initiator, 
NameIdentifier.of(metalake), user));
+    try {
+      return dispatcher.getUser(metalake, user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public User[] listUsers(String metalake) throws NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new ListUsersPreEvent(initiator, 
NameIdentifier.of(metalake)));
+    try {
+      // TODO: add Event
+      return dispatcher.listUsers(metalake);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public String[] listUserNames(String metalake) throws 
NoSuchMetalakeException {
+    String initiator = PrincipalUtils.getCurrentUserName();
+
+    eventBus.dispatchEvent(new ListUserNamesPreEvent(initiator, 
NameIdentifier.of(metalake)));
+    try {
+      // TODO: add Event
+      return dispatcher.listUserNames(metalake);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Group addGroup(String metalake, String group)
+      throws GroupAlreadyExistsException, NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.addGroup(metalake, group);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean removeGroup(String metalake, String group) throws 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.removeGroup(metalake, group);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Group getGroup(String metalake, String group)
+      throws NoSuchGroupException, NoSuchMetalakeException {
+    try {
+      return dispatcher.getGroup(metalake, group);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Group[] listGroups(String metalake) {
+    try {
+      // TODO: add Event
+      return dispatcher.listGroups(metalake);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public String[] listGroupNames(String metalake) {
+    try {
+      // TODO: add Event
+      return dispatcher.listGroupNames(metalake);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public User grantRolesToUser(String metalake, List<String> roles, String 
user)
+      throws NoSuchUserException, IllegalRoleException, 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.grantRolesToUser(metalake, roles, user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Group grantRolesToGroup(String metalake, List<String> roles, String 
group)
+      throws NoSuchGroupException, IllegalRoleException, 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.grantRolesToGroup(metalake, roles, group);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Group revokeRolesFromGroup(String metalake, List<String> roles, 
String group)
+      throws NoSuchGroupException, IllegalRoleException, 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.revokeRolesFromGroup(metalake, roles, group);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public User revokeRolesFromUser(String metalake, List<String> roles, String 
user)
+      throws NoSuchUserException, IllegalRoleException, 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.revokeRolesFromUser(metalake, roles, user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isServiceAdmin(String user) {
+    try {
+      // TODO: add Event
+      return dispatcher.isServiceAdmin(user);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Role createRole(
+      String metalake,
+      String role,
+      Map<String, String> properties,
+      List<SecurableObject> securableObjects)
+      throws RoleAlreadyExistsException, NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.createRole(metalake, role, properties, 
securableObjects);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Role getRole(String metalake, String role)
+      throws NoSuchRoleException, NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.getRole(metalake, role);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean deleteRole(String metalake, String role) throws 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.deleteRole(metalake, role);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public String[] listRoleNames(String metalake) throws 
NoSuchMetalakeException {
+    try {
+      // TODO: add Event
+      return dispatcher.listRoleNames(metalake);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public String[] listRoleNamesByObject(String metalake, MetadataObject object)
+      throws NoSuchMetalakeException, NoSuchMetadataObjectException {
+    try {
+      // TODO: add Event
+      return dispatcher.listRoleNamesByObject(metalake, object);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Role grantPrivilegeToRole(
+      String metalake, String role, MetadataObject object, Set<Privilege> 
privileges)
+      throws NoSuchGroupException, NoSuchRoleException {
+    try {
+      // TODO: add Event
+      return dispatcher.grantPrivilegeToRole(metalake, role, object, 
privileges);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public Role revokePrivilegesFromRole(
+      String metalake, String role, MetadataObject object, Set<Privilege> 
privileges)
+      throws NoSuchMetalakeException, NoSuchRoleException {
+    try {
+      // TODO: add Event
+      return dispatcher.revokePrivilegesFromRole(metalake, role, object, 
privileges);
+    } catch (Exception e) {
+      // TODO: add failure event
+      throw e;
+    }
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/AddUserPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AddUserPreEvent.java
new file mode 100644
index 0000000000..91e1bd61a6
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/AddUserPreEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represents an event triggered before add a user to a metalake. */
+@DeveloperApi
+public class AddUserPreEvent extends UserPreEvent {
+  private final String userName;
+
+  /**
+   * Construct a new {@link AddUserPreEvent} instance.
+   *
+   * @param initiator the user who initiated the add-user request.
+   * @param identifier the identifier of the metalake which the user is being 
added to.
+   * @param userName the username which is requested to be added to the 
metalake.
+   */
+  public AddUserPreEvent(String initiator, NameIdentifier identifier, String 
userName) {
+    super(initiator, identifier);
+
+    this.userName = userName;
+  }
+
+  /**
+   * Returns the user information which is being added to the metalake.
+   *
+   * @return the username which is requested to be added to the metalake.
+   */
+  public String userName() {
+    return userName;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return the operation type.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.ADD_USER;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/GetUserPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetUserPreEvent.java
new file mode 100644
index 0000000000..1c848fedc2
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/GetUserPreEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represents an event triggered before get a user from specific metalake */
+@DeveloperApi
+public class GetUserPreEvent extends UserPreEvent {
+  private final String userName;
+
+  /**
+   * Construct a new {@link GetUserPreEvent} instance with the specified user, 
identifier and user
+   * info.
+   *
+   * @param initiator the user who initiated the add-user request.
+   * @param identifier the identifier of the metalake which the user is being 
added to.
+   * @param userName the username which is requested to be retrieved.
+   */
+  public GetUserPreEvent(String initiator, NameIdentifier identifier, String 
userName) {
+    super(initiator, identifier);
+    this.userName = userName;
+  }
+
+  /**
+   * Returns the user info for the user which is getting retrieved.
+   *
+   * @return the username which is requested to be retrieved.
+   */
+  public String userName() {
+    return userName;
+  }
+
+  /**
+   * Returns the operation type for this event.
+   *
+   * @return the operation type for this event.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.GET_USER;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/ListUserNamesPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListUserNamesPreEvent.java
new file mode 100644
index 0000000000..d5596d06d7
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListUserNamesPreEvent.java
@@ -0,0 +1,48 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represents an event triggered before list users name from specific 
metalake */
+@DeveloperApi
+public class ListUserNamesPreEvent extends UserPreEvent {
+
+  /**
+   * Construct a new {@link ListUserNamesPreEvent} instance with the specified 
user and identifier.
+   *
+   * @param initiator the user who initiated the list-user request.
+   * @param identifier the identifier of the metalake which is being listed.
+   */
+  protected ListUserNamesPreEvent(String initiator, NameIdentifier identifier) 
{
+    super(initiator, identifier);
+  }
+
+  /**
+   * Returns the operation type for this event.
+   *
+   * @return the operation type for this event.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_USER_NAMES;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/ListUsersPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListUsersPreEvent.java
new file mode 100644
index 0000000000..975d37d77b
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/ListUsersPreEvent.java
@@ -0,0 +1,48 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represents an event triggered before list user from specific metalake */
+@DeveloperApi
+public class ListUsersPreEvent extends UserPreEvent {
+
+  /**
+   * Construct a new {@link ListUsersPreEvent} instance with the specified 
user and identifier.
+   *
+   * @param initiator the user who initiated the list-user request.
+   * @param identifier the identifier of the metalake which is being listed.
+   */
+  protected ListUsersPreEvent(String initiator, NameIdentifier identifier) {
+    super(initiator, identifier);
+  }
+
+  /**
+   * Returns the operation type for this event.
+   *
+   * @return the operation type for this event.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.LIST_USERS;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java 
b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
index 9ad171f10f..8477cd3e8f 100644
--- 
a/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/OperationType.java
@@ -112,5 +112,16 @@ public enum OperationType {
   LIST_MODEL_VERSIONS,
   REGISTER_AND_LINK_MODEL_VERSION,
 
+  // User
+  ADD_USER,
+  REMOVE_USER,
+  GET_USER,
+  LIST_USERS,
+  LIST_USER_NAMES,
+
+  // TODO GROUP
+
+  // TODO ROLE
+
   UNKNOWN,
 }
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveUserPreEvent.java
 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveUserPreEvent.java
new file mode 100644
index 0000000000..3c39c5a9aa
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/RemoveUserPreEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represents an event triggered before remove a user from specific metalake. 
*/
+@DeveloperApi
+public class RemoveUserPreEvent extends UserPreEvent {
+  private final String userName;
+
+  /**
+   * Construct a new {@link RemoveUserPreEvent} instance with the specified 
user and identifier.
+   *
+   * @param initiator the user who initiated the remove user operation.
+   * @param identifier the identifier of the metalake where the user is 
removed.
+   * @param userName the username which is requested to be removed from the 
metalake.
+   */
+  protected RemoveUserPreEvent(String initiator, NameIdentifier identifier, 
String userName) {
+    super(initiator, identifier);
+
+    this.userName = userName;
+  }
+
+  /**
+   * Returns the user information of the user which is to be removed from the 
metalake.
+   *
+   * @return the username which is requested to be removed from the metalake.
+   */
+  public String userName() {
+    return userName;
+  }
+
+  /**
+   * Returns the operation type of this event.
+   *
+   * @return The operation type of this event.
+   */
+  @Override
+  public OperationType operationType() {
+    return OperationType.REMOVE_USER;
+  }
+}
diff --git 
a/core/src/main/java/org/apache/gravitino/listener/api/event/UserPreEvent.java 
b/core/src/main/java/org/apache/gravitino/listener/api/event/UserPreEvent.java
new file mode 100644
index 0000000000..0316226ab0
--- /dev/null
+++ 
b/core/src/main/java/org/apache/gravitino/listener/api/event/UserPreEvent.java
@@ -0,0 +1,38 @@
+/*
+ * 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.listener.api.event;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.annotation.DeveloperApi;
+
+/** Represent a pre-event for an user operation request. */
+@DeveloperApi
+public abstract class UserPreEvent extends PreEvent {
+
+  /**
+   * Create a new {@link UserPreEvent} instance for an access request.
+   *
+   * @param initiator the user who triggered the event.
+   * @param identifier the identifier of the model being operated on.
+   */
+  protected UserPreEvent(String initiator, NameIdentifier identifier) {
+    super(initiator, identifier);
+  }
+}
diff --git 
a/core/src/test/java/org/apache/gravitino/listener/api/event/TestUserEvent.java 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestUserEvent.java
new file mode 100644
index 0000000000..5acf13e1f9
--- /dev/null
+++ 
b/core/src/test/java/org/apache/gravitino/listener/api/event/TestUserEvent.java
@@ -0,0 +1,187 @@
+/*
+ * 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.listener.api.event;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Collections;
+import java.util.List;
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.authorization.User;
+import org.apache.gravitino.exceptions.GravitinoRuntimeException;
+import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.listener.DummyEventListener;
+import org.apache.gravitino.listener.EventBus;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+
+@TestInstance(TestInstance.Lifecycle.PER_CLASS)
+public class TestUserEvent {
+  private static final String METALAKE = "demo_metalake";
+  private AccessControlEventDispatcher dispatcher;
+  private AccessControlEventDispatcher failureDispatcher;
+  private DummyEventListener dummyEventListener;
+  private String userName;
+  private String otherUserName;
+  private String inExistUserName;
+  private User user;
+  private User otherUser;
+  private NameIdentifier identifier;
+  private NameIdentifier otherIdentifier;
+
+  @BeforeAll
+  void init() {
+    this.userName = "user_test";
+    this.otherUserName = "user_admin";
+    this.inExistUserName = "user_not_exist";
+    this.user = getMockUser(userName, ImmutableList.of("test", "engineer"));
+    this.otherUser = getMockUser(otherUserName, null);
+    this.identifier = NameIdentifier.of(METALAKE);
+    this.otherIdentifier = NameIdentifier.of(otherUserName);
+
+    this.dummyEventListener = new DummyEventListener();
+    EventBus eventBus = new 
EventBus(Collections.singletonList(dummyEventListener));
+    this.dispatcher = new AccessControlEventDispatcher(eventBus, 
mockUserDispatcher());
+    this.failureDispatcher =
+        new AccessControlEventDispatcher(eventBus, 
mockExceptionUserDispatcher());
+
+    System.out.println(dispatcher);
+    System.out.println(failureDispatcher);
+    System.out.println(otherIdentifier);
+  }
+
+  @Test
+  void testAddUserPreEvent() {
+    dispatcher.addUser(METALAKE, otherUserName);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(AddUserPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.ADD_USER, preEvent.operationType());
+
+    AddUserPreEvent addUserPreEvent = (AddUserPreEvent) preEvent;
+    Assertions.assertEquals(identifier, addUserPreEvent.identifier());
+    String userName = addUserPreEvent.userName();
+    Assertions.assertEquals(otherUserName, userName);
+  }
+
+  @Test
+  void testGetUserPreEventWithExistingUser() {
+    dispatcher.getUser(METALAKE, userName);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(GetUserPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.GET_USER, preEvent.operationType());
+
+    GetUserPreEvent getUserPreEvent = (GetUserPreEvent) preEvent;
+    Assertions.assertEquals(identifier, getUserPreEvent.identifier());
+    String requestedUserName = getUserPreEvent.userName();
+    Assertions.assertEquals(userName, requestedUserName);
+  }
+
+  @Test
+  void testGetUserPreEventWithNonExistingUser() {
+    Assertions.assertThrows(
+        NoSuchUserException.class, () -> dispatcher.getUser(METALAKE, 
inExistUserName));
+  }
+
+  @Test
+  void testListUserPreEvent() {
+    dispatcher.listUsers(METALAKE);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(ListUsersPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+
+    ListUsersPreEvent listUsersPreEvent = (ListUsersPreEvent) preEvent;
+    Assertions.assertEquals(identifier, listUsersPreEvent.identifier());
+  }
+
+  @Test
+  void testListUserNamesPreEvent() {
+    dispatcher.listUserNames(METALAKE);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(ListUserNamesPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+
+    ListUserNamesPreEvent listUserNamesPreEvent = (ListUserNamesPreEvent) 
preEvent;
+    Assertions.assertEquals(identifier, listUserNamesPreEvent.identifier());
+  }
+
+  @Test
+  void testRemoveUserPreEventWithExistingUser() {
+    dispatcher.removeUser(METALAKE, userName);
+
+    // validate pre-event
+    PreEvent preEvent = dummyEventListener.popPreEvent();
+    Assertions.assertEquals(RemoveUserPreEvent.class, preEvent.getClass());
+    Assertions.assertEquals(OperationStatus.UNPROCESSED, 
preEvent.operationStatus());
+    Assertions.assertEquals(OperationType.REMOVE_USER, 
preEvent.operationType());
+
+    RemoveUserPreEvent removeUserPreEvent = (RemoveUserPreEvent) preEvent;
+    Assertions.assertEquals(identifier, removeUserPreEvent.identifier());
+    String removedUserName = removeUserPreEvent.userName();
+    Assertions.assertEquals(userName, removedUserName);
+  }
+
+  private AccessControlEventDispatcher mockUserDispatcher() {
+    AccessControlEventDispatcher dispatcher = 
mock(AccessControlEventDispatcher.class);
+    when(dispatcher.addUser(METALAKE, userName)).thenReturn(user);
+    when(dispatcher.addUser(METALAKE, otherUserName)).thenReturn(otherUser);
+
+    when(dispatcher.removeUser(METALAKE, userName)).thenReturn(true);
+    when(dispatcher.removeUser(METALAKE, inExistUserName)).thenReturn(false);
+
+    when(dispatcher.listUsers(METALAKE)).thenReturn(new User[] {user, 
otherUser});
+    when(dispatcher.listUserNames(METALAKE)).thenReturn(new String[] 
{userName, otherUserName});
+
+    when(dispatcher.getUser(METALAKE, userName)).thenReturn(user);
+    when(dispatcher.getUser(METALAKE, inExistUserName))
+        .thenThrow(new NoSuchUserException("user not found"));
+
+    return dispatcher;
+  }
+
+  private AccessControlEventDispatcher mockExceptionUserDispatcher() {
+    return mock(
+        AccessControlEventDispatcher.class,
+        invocation -> {
+          throw new GravitinoRuntimeException("Exception for all methods");
+        });
+  }
+
+  private User getMockUser(String name, List<String> roles) {
+    User user = mock(User.class);
+    when(user.name()).thenReturn(name);
+    when(user.roles()).thenReturn(roles);
+
+    return user;
+  }
+}
diff --git a/docs/gravitino-server-config.md b/docs/gravitino-server-config.md
index 08c9beaec4..afcce0c42c 100644
--- a/docs/gravitino-server-config.md
+++ b/docs/gravitino-server-config.md
@@ -132,17 +132,18 @@ Gravitino triggers a pre-event before the operation, a 
post-event after the comp
 
 ##### Pre-event
 
-| Operation type                       | Pre-event                             
                                                                                
                                                                                
                                                                                
                                     | Since Version    |
-|--------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
-| Iceberg REST server table operation  | `IcebergCreateTablePreEvent`, 
`IcebergUpdateTablePreEvent`, `IcebergDropTablePreEvent`, 
`IcebergLoadTablePreEvent`, `IcebergListTablePreEvent`, 
`IcebergTableExistsPreEvent`, `IcebergRenameTablePreEvent`                      
                                                                                
           | 0.7.0-incubating |
-| Gravitino server table operation     | `CreateTablePreEvent`, 
`AlterTablePreEvent`, `DropTablePreEvent`, `PurgeTablePreEvent`, 
`LoadTablePreEvent`, `ListTablePreEvent`                                        
                                                                                
                                                                   | 
0.8.0-incubating |
-| Gravitino server schema operation    | `CreateSchemaPreEvent`, 
`AlterSchemaPreEvent`, `DropSchemaPreEvent`, `LoadSchemaPreEvent`, 
`ListSchemaPreEvent`                                                            
                                                                                
                                                                | 
0.8.0-incubating |
-| Gravitino server catalog operation   | `CreateCatalogPreEvent`, 
`AlterCatalogPreEvent`, `DropCatalogPreEvent`, `LoadCatalogPreEvent`, 
`ListCatalogPreEvent`                                                           
                                                                                
                                                            | 0.8.0-incubating |
-| Gravitino server metalake operation  | `CreateMetalakePreEvent`, 
`AlterMetalakePreEvent`,`DropMetalakePreEvent`,`LoadMetalakePreEvent`,`ListMetalakePreEvent`
                                                                                
                                                                                
                                     | 0.8.0-incubating |
-| Gravitino server partition operation | `AddPartitionPreEvent`, 
`DropPartitionPreEvent`, `GetPartitionPreEvent`, 
`PurgePartitionPreEvent`,`ListPartitionPreEvent`,`ListPartitionNamesPreEvent`   
                                                                                
                                                                                
  | 0.8.0-incubating |
-| Gravitino server fileset operation   | `CreateFilesetPreEvent`, 
`AlterFilesetPreEvent`, `DropFilesetPreEvent`, 
`LoadFilesetPreEvent`,`ListFilesetPreEvent`,`GetFileLocationPreEvent`           
                                                                                
                                                                                
   | 0.8.0-incubating |
-| Gravitino server model operation     | `DeleteModelPreEvent`, 
`DeleteModelVersionPreEvent`, 
`RegisterAndLinkModelPreEvent`,`GetModelPreEvent`, 
`GetModelVersionPreEvent`,`LinkModelVersionPreEvent`,`ListModelPreEvent`,`RegisterModelPreEvent`
                                                                                
                                                                | 
0.9.0-incubating |
+| Operation type                       | Pre-event                             
                                                                                
                                                                                
                                                                                
                                  | Since Version    |
+|--------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------|
+| Iceberg REST server table operation  | `IcebergCreateTablePreEvent`, 
`IcebergUpdateTablePreEvent`, `IcebergDropTablePreEvent`, 
`IcebergLoadTablePreEvent`, `IcebergListTablePreEvent`, 
`IcebergTableExistsPreEvent`, `IcebergRenameTablePreEvent`                      
                                                                                
        | 0.7.0-incubating |
+| Gravitino server table operation     | `CreateTablePreEvent`, 
`AlterTablePreEvent`, `DropTablePreEvent`, `PurgeTablePreEvent`, 
`LoadTablePreEvent`, `ListTablePreEvent`                                        
                                                                                
                                                                | 
0.8.0-incubating |
+| Gravitino server schema operation    | `CreateSchemaPreEvent`, 
`AlterSchemaPreEvent`, `DropSchemaPreEvent`, `LoadSchemaPreEvent`, 
`ListSchemaPreEvent`                                                            
                                                                                
                                                             | 0.8.0-incubating 
|
+| Gravitino server catalog operation   | `CreateCatalogPreEvent`, 
`AlterCatalogPreEvent`, `DropCatalogPreEvent`, `LoadCatalogPreEvent`, 
`ListCatalogPreEvent`                                                           
                                                                                
                                                         | 0.8.0-incubating |
+| Gravitino server metalake operation  | `CreateMetalakePreEvent`, 
`AlterMetalakePreEvent`,`DropMetalakePreEvent`,`LoadMetalakePreEvent`,`ListMetalakePreEvent`
                                                                                
                                                                                
                                  | 0.8.0-incubating |
+| Gravitino server partition operation | `AddPartitionPreEvent`, 
`DropPartitionPreEvent`, `GetPartitionPreEvent`, 
`PurgePartitionPreEvent`,`ListPartitionPreEvent`,`ListPartitionNamesPreEvent`   
                                                                                
                                                                               
| 0.8.0-incubating |
+| Gravitino server fileset operation   | `CreateFilesetPreEvent`, 
`AlterFilesetPreEvent`, `DropFilesetPreEvent`, 
`LoadFilesetPreEvent`,`ListFilesetPreEvent`,`GetFileLocationPreEvent`           
                                                                                
                                                                                
| 0.8.0-incubating |
+| Gravitino server model operation     | `DeleteModelPreEvent`, 
`DeleteModelVersionPreEvent`, 
`RegisterAndLinkModelPreEvent`,`GetModelPreEvent`, 
`GetModelVersionPreEvent`,`LinkModelVersionPreEvent`,`ListModelPreEvent`,`RegisterModelPreEvent`
                                                                                
                                                             | 0.9.0-incubating 
|
 | Gravitino server tag operation       | `ListTagsPreEvent`, 
`ListTagsInfoPreEvent`, `CreateTagPreEvent`, `GetTagPreEvent`, 
`AlterTagPreEvent`, `DeleteTagPreEvent`, `ListMetadataObjectsForTagPreEvent`, 
`ListTagsForMetadataObjectPreEvent`, `ListTagsInfoForMetadataObjectPreEvent`, 
`AssociateTagsForMetadataObjectPreEvent`, `GetTagForMetadataObjectPreEvent` | 
0.9.0-incubating |
+| Gravitino server user operation      | `AddUserPreEvent`, `GetUserPreEvent`, 
`ListUserNamesPreEvent`, `ListUserPreEvent`, `RemoveUserPreEvent` | 
0.9.0-incubating |
 
 #### Event listener plugin
 


Reply via email to