This is an automated email from the ASF dual-hosted git repository.
roryqi 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 5f418566ca [#12330] feat(core): Add user by-id APIs and alterUserById
via UserChange (#12332)
5f418566ca is described below
commit 5f418566ca02fb9cef831a56b8299580b2d1950b
Author: MaSai <[email protected]>
AuthorDate: Tue Aug 4 09:43:09 2026 +0800
[#12330] feat(core): Add user by-id APIs and alterUserById via UserChange
(#12332)
### What changes were proposed in this pull request?
Add AccessControlDispatcher APIs that locate users by Gravitino-assigned
id, and a single alter path for mutable attributes.
**API / model**
- Expose `User.id()` and align `UserDTO` / `UserInfo` (required non-null
id)
- `getUserById` / `removeUserById`
- `UserChange` + `alterUserById` (update `enabled` and/or `externalId`
in one call)
- Remove dedicated `enableUser` / `disableUser` (by externalId and by
id) and `updateUserExternalIdById`
**Persistence / store**
- Relational lookup/update/delete by metalake + user id
(`UserMetaService`, mapper/SQL)
- `SupportsIdOperations` wired through JDBC and in-memory entity stores
for `USER`
**Wiring**
- `UserGroupIdManager`, `AccessControlManager`, hook dispatcher
- TreeLock paths via `AuthorizationUtils.ofUserId` /
`USER_ID_SCHEMA_NAME`
Existing by-name get/remove and by-externalId get/remove are unchanged.
Fix: #12330
### Why are the changes needed?
SCIM push (Azure Entra / Okta) stores the SCIM `id` returned on create
and uses it for subsequent `GET` / `PATCH` / `DELETE`. Core previously
only keyed users by name or `externalId`. A single `alterUserById`
matches SCIM PATCH that may update `active` and `externalId` together.
### Does this PR introduce _any_ user-facing change?
- Yes: new `User.id()`, by-id get/remove, `UserChange` / `alterUserById`
- Breaking: removes `enableUser` / `disableUser` and related by-id
update helpers in favor of `alterUserById`
### How was this patch tested?
```bash
./gradlew :api:test \
--tests org.apache.gravitino.authorization.TestUserChange \
:core:test \
--tests org.apache.gravitino.authorization.TestAccessControlManager \
--tests org.apache.gravitino.listener.api.event.TestUserEvent \
-PskipITs
```
---------
Co-authored-by: Cursor <[email protected]>
---
.../org/apache/gravitino/authorization/User.java | 15 ++-
.../apache/gravitino/authorization/UserChange.java | 132 ++++++++++++++++++++
.../gravitino/authorization/TestUserChange.java | 43 +++++++
.../apache/gravitino/cli/commands/ListUsers.java | 5 +
.../apache/gravitino/client/TestPermission.java | 2 +
.../org/apache/gravitino/client/TestUserGroup.java | 2 +
.../gravitino/dto/authorization/UserDTO.java | 37 +++++-
.../apache/gravitino/dto/util/DTOConverters.java | 1 +
.../gravitino/dto/responses/TestResponses.java | 2 +-
.../src/main/java/org/apache/gravitino/Entity.java | 12 +-
.../java/org/apache/gravitino/EntityStore.java | 10 ++
.../org/apache/gravitino/SupportsIdOperations.java | 71 +++++++++++
.../authorization/AccessControlDispatcher.java | 40 ++++--
.../authorization/AccessControlManager.java | 22 +++-
.../authorization/AuthorizationUtils.java | 39 ++++++
.../authorization/UserGroupExternalManager.java | 44 -------
.../authorization/UserGroupIdManager.java | 138 +++++++++++++++++++++
.../hook/AccessControlHookDispatcher.java | 14 ++-
.../listener/AccessControlEventDispatcher.java | 41 ++----
.../listener/api/event/DisableUserEvent.java | 65 ----------
.../api/event/DisableUserFailureEvent.java | 57 ---------
.../listener/api/event/DisableUserPreEvent.java | 55 --------
.../listener/api/event/EnableUserEvent.java | 65 ----------
.../listener/api/event/EnableUserFailureEvent.java | 57 ---------
.../listener/api/event/EnableUserPreEvent.java | 55 --------
.../gravitino/listener/api/info/UserInfo.java | 12 ++
.../gravitino/storage/relational/JDBCBackend.java | 33 +++++
.../storage/relational/RelationalBackend.java | 29 +++++
.../storage/relational/RelationalEntityStore.java | 40 ++++++
.../storage/relational/mapper/UserMetaMapper.java | 6 +
.../mapper/UserMetaSQLProviderFactory.java | 5 +
.../provider/base/UserMetaBaseSQLProvider.java | 17 +++
.../relational/service/UserMetaService.java | 85 +++++++++++++
.../authorization/TestAccessControlManager.java | 64 ++++++++--
.../listener/api/event/TestUserEvent.java | 18 +--
.../storage/memory/TestMemoryEntityStore.java | 67 +++++++++-
36 files changed, 919 insertions(+), 481 deletions(-)
diff --git a/api/src/main/java/org/apache/gravitino/authorization/User.java
b/api/src/main/java/org/apache/gravitino/authorization/User.java
index a5ac53e51e..3e58c7d3a3 100644
--- a/api/src/main/java/org/apache/gravitino/authorization/User.java
+++ b/api/src/main/java/org/apache/gravitino/authorization/User.java
@@ -35,13 +35,24 @@ public interface User extends Auditable {
*/
String name();
+ /**
+ * The unique id assigned by Gravitino.
+ *
+ * <p>This id is server-assigned and immutable. Upstream systems may also
supply an optional
+ * {@link #externalId()}.
+ *
+ * @return The unique id of the user.
+ */
+ Long id();
+
/**
* The stable identifier assigned by an upstream identity system (for
example, SCIM, LDAP, or
* IAM), or null if not set.
*
* <p>Gravitino {@link User#name() user names} may differ from upstream ids
or be unknown at sync
- * time. External id lets integrators look up, enable/disable, and delete
users without relying on
- * the Gravitino user name.
+ * time. External id lets integrators look up and correlate users without
relying on the Gravitino
+ * user name. Mutable attributes such as {@link #enabled()} and {@code
externalId} are updated via
+ * {@code alterUserById}.
*
* @return The upstream external identifier, or null if not set.
*/
diff --git
a/api/src/main/java/org/apache/gravitino/authorization/UserChange.java
b/api/src/main/java/org/apache/gravitino/authorization/UserChange.java
new file mode 100644
index 0000000000..f147784370
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/authorization/UserChange.java
@@ -0,0 +1,132 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.authorization;
+
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * A user change is a change to a user. It can be used to update the enabled
flag or the optional
+ * external id. Multiple changes may be applied in one {@code alterUserById}
call.
+ */
+@Evolving
+public interface UserChange {
+
+ /**
+ * Creates a user change to update whether the user is enabled.
+ *
+ * @param enabled Whether the user should be enabled.
+ * @return The user change.
+ */
+ static UserChange updateEnabled(boolean enabled) {
+ return new UpdateEnabled(enabled);
+ }
+
+ /**
+ * Creates a user change to update the external identifier.
+ *
+ * @param newExternalId The new external identifier, or null to clear it.
+ * @return The user change.
+ */
+ static UserChange updateExternalId(@Nullable String newExternalId) {
+ return new UpdateExternalId(newExternalId);
+ }
+
+ /** A user change to update the enabled flag. */
+ final class UpdateEnabled implements UserChange {
+ private final boolean enabled;
+
+ private UpdateEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Returns whether the user should be enabled.
+ *
+ * @return Whether the user should be enabled.
+ */
+ public boolean enabled() {
+ return enabled;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof UpdateEnabled)) {
+ return false;
+ }
+ UpdateEnabled that = (UpdateEnabled) o;
+ return enabled == that.enabled;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateEnabled " + enabled;
+ }
+ }
+
+ /** A user change to update the external identifier. */
+ final class UpdateExternalId implements UserChange {
+ @Nullable private final String newExternalId;
+
+ private UpdateExternalId(@Nullable String newExternalId) {
+ this.newExternalId = newExternalId;
+ }
+
+ /**
+ * Returns the new external identifier, or null to clear it.
+ *
+ * @return The new external identifier, or null.
+ */
+ @Nullable
+ public String getNewExternalId() {
+ return newExternalId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof UpdateExternalId)) {
+ return false;
+ }
+ UpdateExternalId that = (UpdateExternalId) o;
+ return Objects.equals(newExternalId, that.newExternalId);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(newExternalId);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateExternalId " + newExternalId;
+ }
+ }
+}
diff --git
a/api/src/test/java/org/apache/gravitino/authorization/TestUserChange.java
b/api/src/test/java/org/apache/gravitino/authorization/TestUserChange.java
new file mode 100644
index 0000000000..cfb051b5f4
--- /dev/null
+++ b/api/src/test/java/org/apache/gravitino/authorization/TestUserChange.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.authorization;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestUserChange {
+
+ @Test
+ void testUpdateEnabled() {
+ UserChange.UpdateEnabled change = (UserChange.UpdateEnabled)
UserChange.updateEnabled(false);
+ Assertions.assertFalse(change.enabled());
+ Assertions.assertEquals(UserChange.updateEnabled(false), change);
+ Assertions.assertNotEquals(UserChange.updateEnabled(true), change);
+ }
+
+ @Test
+ void testUpdateExternalId() {
+ UserChange.UpdateExternalId change =
+ (UserChange.UpdateExternalId) UserChange.updateExternalId("ext-1");
+ Assertions.assertEquals("ext-1", change.getNewExternalId());
+ Assertions.assertEquals(UserChange.updateExternalId("ext-1"), change);
+ Assertions.assertEquals(UserChange.updateExternalId(null),
UserChange.updateExternalId(null));
+ Assertions.assertNotEquals(UserChange.updateExternalId("ext-2"), change);
+ }
+}
diff --git
a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
index 19cf9718be..19382a0e93 100644
--- a/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
+++ b/clients/cli/src/main/java/org/apache/gravitino/cli/commands/ListUsers.java
@@ -68,6 +68,11 @@ public class ListUsers extends Command {
private User getUser(String user) {
return new User() {
+ @Override
+ public Long id() {
+ return null;
+ }
+
@Override
public String name() {
return user;
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
index d1731607a8..4704886190 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestPermission.java
@@ -92,6 +92,7 @@ public class TestPermission extends TestBase {
RoleGrantRequest request = new RoleGrantRequest(roles);
UserDTO userDTO =
UserDTO.builder()
+ .withId(1L)
.withName("user")
.withRoles(Lists.newArrayList("roles"))
.withAudit(AuditDTO.builder().withCreator("test").withCreateTime(Instant.now()).build())
@@ -118,6 +119,7 @@ public class TestPermission extends TestBase {
String.format(API_PERMISSION_PATH, metalakeName,
String.format("users/%s/revoke", user));
UserDTO userDTO =
UserDTO.builder()
+ .withId(1L)
.withName("user")
.withRoles(Lists.newArrayList())
.withAudit(AuditDTO.builder().withCreator("test").withCreateTime(Instant.now()).build())
diff --git
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
index 924acb9cf0..9c0a12d133 100644
---
a/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
+++
b/clients/client-java/src/test/java/org/apache/gravitino/client/TestUserGroup.java
@@ -134,6 +134,7 @@ public class TestUserGroup extends TestBase {
UserDTO mockUser =
UserDTO.builder()
+ .withId(1L)
.withName(username)
.withExternalId(externalId)
.withEnabled(false)
@@ -427,6 +428,7 @@ public class TestUserGroup extends TestBase {
private UserDTO mockUserDTO(String name) {
return UserDTO.builder()
+ .withId(1L)
.withName(name)
.withAudit(AuditDTO.builder().withCreator("creator").withCreateTime(Instant.now()).build())
.build();
diff --git
a/common/src/main/java/org/apache/gravitino/dto/authorization/UserDTO.java
b/common/src/main/java/org/apache/gravitino/dto/authorization/UserDTO.java
index 68297c9e84..2e741a8e01 100644
--- a/common/src/main/java/org/apache/gravitino/dto/authorization/UserDTO.java
+++ b/common/src/main/java/org/apache/gravitino/dto/authorization/UserDTO.java
@@ -31,6 +31,9 @@ import org.apache.gravitino.dto.AuditDTO;
/** Represents a User Data Transfer Object (DTO). */
public class UserDTO implements User {
+ @JsonProperty("id")
+ private Long id;
+
@JsonProperty("name")
private String name;
@@ -53,6 +56,7 @@ public class UserDTO implements User {
/**
* Creates a new instance of UserDTO.
*
+ * @param id The id of the User DTO.
* @param name The name of the User DTO.
* @param externalId The external id of the User DTO.
* @param roles The roles of the User DTO.
@@ -60,7 +64,13 @@ public class UserDTO implements User {
* @param enabled Whether the User DTO is enabled.
*/
protected UserDTO(
- String name, String externalId, List<String> roles, AuditDTO audit,
boolean enabled) {
+ Long id,
+ String name,
+ String externalId,
+ List<String> roles,
+ AuditDTO audit,
+ boolean enabled) {
+ this.id = id;
this.name = name;
this.externalId = externalId;
this.enabled = enabled;
@@ -68,6 +78,14 @@ public class UserDTO implements User {
this.roles = roles;
}
+ /**
+ * @return The id of the User DTO.
+ */
+ @Override
+ public Long id() {
+ return id;
+ }
+
/**
* @return The name of the User DTO.
*/
@@ -120,6 +138,9 @@ public class UserDTO implements User {
*/
public static class Builder<S extends Builder> {
+ /** The id of the user. */
+ protected Long id;
+
/** The name of the user. */
protected String name;
@@ -135,6 +156,17 @@ public class UserDTO implements User {
/** The audit information of the user. */
protected AuditDTO audit;
+ /**
+ * Sets the id of the user.
+ *
+ * @param id The id of the user.
+ * @return The builder instance.
+ */
+ public S withId(Long id) {
+ this.id = id;
+ return (S) this;
+ }
+
/**
* Sets the name of the user.
*
@@ -200,9 +232,10 @@ public class UserDTO implements User {
* @throws IllegalArgumentException If the name or audit are not set.
*/
public UserDTO build() {
+ Preconditions.checkArgument(id != null, "id cannot be null");
Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot
be null or empty");
Preconditions.checkArgument(audit != null, "audit cannot be null");
- return new UserDTO(name, externalId, roles, audit, enabled);
+ return new UserDTO(id, name, externalId, roles, audit, enabled);
}
}
}
diff --git
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 2279048a91..75394cc69f 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -493,6 +493,7 @@ public class DTOConverters {
}
return UserDTO.builder()
+ .withId(user.id())
.withName(user.name())
.withExternalId(user.externalId())
.withEnabled(user.enabled())
diff --git
a/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
b/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
index f1c5977eda..89269ab966 100644
--- a/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
+++ b/common/src/test/java/org/apache/gravitino/dto/responses/TestResponses.java
@@ -285,7 +285,7 @@ public class TestResponses {
void testUserResponse() throws IllegalArgumentException {
AuditDTO audit =
AuditDTO.builder().withCreator("creator").withCreateTime(Instant.now()).build();
- UserDTO user =
UserDTO.builder().withName("user1").withAudit(audit).build();
+ UserDTO user =
UserDTO.builder().withId(1L).withName("user1").withAudit(audit).build();
UserResponse response = new UserResponse(user);
response.validate(); // No exception thrown
}
diff --git a/core/src/main/java/org/apache/gravitino/Entity.java
b/core/src/main/java/org/apache/gravitino/Entity.java
index 29bae55637..934f119eb0 100644
--- a/core/src/main/java/org/apache/gravitino/Entity.java
+++ b/core/src/main/java/org/apache/gravitino/Entity.java
@@ -43,7 +43,17 @@ public interface Entity extends Serializable {
/**
* A virtual schema name used only for {@link
org.apache.gravitino.lock.TreeLockUtils} lock paths
- * when operating on users by external id (for example,
get/enable/disable/delete-by-external-id).
+ * when operating on users by Gravitino-assigned id (for example,
get/alter/delete-by-id).
+ *
+ * <p>This is not a real metadata schema and does not store entities. It
forms part of a synthetic
+ * {@link org.apache.gravitino.NameIdentifier} such as {@code {metalake,
system, user-id, <id>}}
+ * so that concurrent operations on the same user id are serialized.
+ */
+ String USER_ID_SCHEMA_NAME = "user-id";
+
+ /**
+ * A virtual schema name used only for {@link
org.apache.gravitino.lock.TreeLockUtils} lock paths
+ * when operating on users by external id (for example,
get/delete-by-external-id).
*
* <p>This is not a real metadata schema and does not store entities. It
forms part of a synthetic
* {@link org.apache.gravitino.NameIdentifier} such as {@code {metalake,
system, user-external-id,
diff --git a/core/src/main/java/org/apache/gravitino/EntityStore.java
b/core/src/main/java/org/apache/gravitino/EntityStore.java
index cadf3b2615..1f844a2e27 100644
--- a/core/src/main/java/org/apache/gravitino/EntityStore.java
+++ b/core/src/main/java/org/apache/gravitino/EntityStore.java
@@ -278,4 +278,14 @@ public interface EntityStore extends Closeable {
default SupportsExternalIdOperations externalIdOperations() {
throw new UnsupportedOperationException("external id operations are not
supported");
}
+
+ /**
+ * Get the extra id operations that are supported by the entity store.
+ *
+ * @return the id operations that are supported by the entity store
+ * @throws UnsupportedOperationException if the extra operations are not
supported
+ */
+ default SupportsIdOperations idOperations() {
+ throw new UnsupportedOperationException("id operations are not supported");
+ }
}
diff --git a/core/src/main/java/org/apache/gravitino/SupportsIdOperations.java
b/core/src/main/java/org/apache/gravitino/SupportsIdOperations.java
new file mode 100644
index 0000000000..36c4634955
--- /dev/null
+++ b/core/src/main/java/org/apache/gravitino/SupportsIdOperations.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;
+
+import java.io.IOException;
+import java.util.function.Function;
+import org.apache.gravitino.exceptions.NoSuchEntityException;
+
+/**
+ * Optional extension for entity stores that support lookup and mutation by
Gravitino-assigned id
+ * within a metalake.
+ */
+public interface SupportsIdOperations {
+
+ /**
+ * Get the entity from the underlying storage by Gravitino-assigned id.
+ *
+ * @param ident the id name identifier
+ * @param entityType the general type of the entity
+ * @param type the detailed type of the entity
+ * @param <E> the class of entity
+ * @return the entity retrieved from the underlying storage
+ * @throws NoSuchEntityException if the entity does not exist
+ * @throws IOException if the retrieve operation fails
+ */
+ <E extends Entity & HasIdentifier> E getById(
+ NameIdentifier ident, Entity.EntityType entityType, Class<E> type)
+ throws NoSuchEntityException, IOException;
+
+ /**
+ * Update an entity by Gravitino-assigned id.
+ *
+ * @param ident the id name identifier
+ * @param entityType the general type of the entity
+ * @param type the detailed type of the entity
+ * @param updater the updater function to update the entity
+ * @param <E> the class of entity
+ * @return the updated entity
+ * @throws NoSuchEntityException if the entity does not exist
+ * @throws IOException if the update operation fails
+ */
+ <E extends Entity & HasIdentifier> E updateById(
+ NameIdentifier ident, Entity.EntityType entityType, Class<E> type,
Function<E, E> updater)
+ throws NoSuchEntityException, IOException;
+
+ /**
+ * Delete an entity by Gravitino-assigned id.
+ *
+ * @param ident the id name identifier
+ * @param entityType the general type of the entity
+ * @return true if the entity exists and is deleted successfully, false
otherwise
+ * @throws IOException if the delete operation fails
+ */
+ boolean deleteById(NameIdentifier ident, Entity.EntityType entityType)
throws IOException;
+}
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
index b657700bc9..20c3c0ba0d 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java
@@ -118,31 +118,47 @@ public interface AccessControlDispatcher {
throws NoSuchUserException, NoSuchMetalakeException;
/**
- * Enables a User without removing role bindings.
+ * Gets a User by Gravitino-assigned id.
*
* @param metalake The Metalake of the User.
- * @param externalId The external identifier of the User.
- * @return The updated User instance.
- * @throws IllegalArgumentException If externalId is null or blank.
- * @throws NoSuchUserException If the User with the given external id does
not exist.
+ * @param userId The Gravitino-assigned id of the User.
+ * @return The getting User instance.
+ * @throws NoSuchUserException If the User with the given id does not exist.
* @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
- * @throws RuntimeException If updating the User encounters storage issues.
+ * @throws RuntimeException If getting the User encounters storage issues.
*/
- User enableUser(String metalake, String externalId)
+ User getUserById(String metalake, long userId)
throws NoSuchUserException, NoSuchMetalakeException;
/**
- * Disables a User without removing role bindings.
+ * Removes a User by Gravitino-assigned id.
*
* @param metalake The Metalake of the User.
- * @param externalId The external identifier of the User.
+ * @param userId The Gravitino-assigned id of the User.
+ * @return True if the User was successfully removed, false only when
there's no such user,
+ * otherwise it will throw an exception.
+ * @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
+ * @throws RuntimeException If removing the User encounters storage issues.
+ */
+ boolean removeUserById(String metalake, long userId) throws
NoSuchMetalakeException;
+
+ /**
+ * Alters a User by Gravitino-assigned id.
+ *
+ * <p>Supports updating {@code enabled} and/or {@code externalId} in one
call via {@link
+ * UserChange}. Role bindings are preserved.
+ *
+ * @param metalake The Metalake of the User.
+ * @param userId The Gravitino-assigned id of the User.
+ * @param changes The changes to apply. Must not be empty.
* @return The updated User instance.
- * @throws IllegalArgumentException If externalId is null or blank.
- * @throws NoSuchUserException If the User with the given external id does
not exist.
+ * @throws IllegalArgumentException If changes is null or empty, or contains
an unsupported
+ * change.
+ * @throws NoSuchUserException If the User with the given id does not exist.
* @throws NoSuchMetalakeException If the Metalake with the given name does
not exist.
* @throws RuntimeException If updating the User encounters storage issues.
*/
- User disableUser(String metalake, String externalId)
+ User alterUserById(String metalake, long userId, UserChange... changes)
throws NoSuchUserException, NoSuchMetalakeException;
/**
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
index 2d21c4eb8a..386e7a3936 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java
@@ -48,6 +48,7 @@ public class AccessControlManager implements
AccessControlDispatcher {
private final UserGroupManager userGroupManager;
private final UserGroupExternalManager userGroupExternalManager;
+ private final UserGroupIdManager userGroupIdManager;
private final RoleManager roleManager;
private final PermissionManager permissionManager;
private final List<String> serviceAdmins;
@@ -56,6 +57,7 @@ public class AccessControlManager implements
AccessControlDispatcher {
this.roleManager = new RoleManager(store, idGenerator);
this.userGroupManager = new UserGroupManager(store, idGenerator);
this.userGroupExternalManager = new UserGroupExternalManager(store,
idGenerator);
+ this.userGroupIdManager = new UserGroupIdManager(store, idGenerator);
this.permissionManager = new PermissionManager(store, roleManager);
this.serviceAdmins = config.get(Configs.SERVICE_ADMINS);
}
@@ -114,21 +116,29 @@ public class AccessControlManager implements
AccessControlDispatcher {
}
@Override
- public User enableUser(String metalake, String externalId)
+ public User getUserById(String metalake, long userId)
throws NoSuchUserException, NoSuchMetalakeException {
return TreeLockUtils.doWithTreeLock(
- AuthorizationUtils.ofUserExternalId(metalake, externalId),
+ AuthorizationUtils.ofUserId(metalake, userId),
+ LockType.READ,
+ () -> userGroupIdManager.getUserById(metalake, userId));
+ }
+
+ @Override
+ public boolean removeUserById(String metalake, long userId) throws
NoSuchMetalakeException {
+ return TreeLockUtils.doWithTreeLock(
+ AuthorizationUtils.ofUserId(metalake, userId),
LockType.WRITE,
- () -> userGroupExternalManager.enableUser(metalake, externalId));
+ () -> userGroupIdManager.removeUserById(metalake, userId));
}
@Override
- public User disableUser(String metalake, String externalId)
+ public User alterUserById(String metalake, long userId, UserChange...
changes)
throws NoSuchUserException, NoSuchMetalakeException {
return TreeLockUtils.doWithTreeLock(
- AuthorizationUtils.ofUserExternalId(metalake, externalId),
+ AuthorizationUtils.ofUserId(metalake, userId),
LockType.WRITE,
- () -> userGroupExternalManager.disableUser(metalake, externalId));
+ () -> userGroupIdManager.alterUserById(metalake, userId, changes));
}
@Override
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
index c16d6a3beb..8c555cf29c 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java
@@ -66,6 +66,8 @@ public class AuthorizationUtils {
static final String USER_DOES_NOT_EXIST_MSG = "User %s does not exist in the
metalake %s";
static final String USER_WITH_EXTERNAL_ID_DOES_NOT_EXIST_MSG =
"User with external id %s does not exist in the metalake %s";
+ static final String USER_WITH_ID_DOES_NOT_EXIST_MSG =
+ "User with id %s does not exist in the metalake %s";
static final String GROUP_DOES_NOT_EXIST_MSG = "Group %s does not exist in
the metalake %s";
static final String GROUP_WITH_EXTERNAL_ID_DOES_NOT_EXIST_MSG =
"Group with external id %s does not exist in the metalake %s";
@@ -165,6 +167,22 @@ public class AuthorizationUtils {
externalId);
}
+ /**
+ * Creates a synthetic {@link NameIdentifier} used only as a tree-lock path
for user operations
+ * keyed by Gravitino-assigned id.
+ *
+ * @param metalake the metalake name
+ * @param userId the Gravitino-assigned user id
+ * @return a synthetic name identifier for tree locking only
+ */
+ public static NameIdentifier ofUserId(String metalake, long userId) {
+ return NameIdentifier.of(
+ metalake,
+ Entity.SYSTEM_CATALOG_RESERVED_NAME,
+ Entity.USER_ID_SCHEMA_NAME,
+ String.valueOf(userId));
+ }
+
/**
* Creates a synthetic {@link NameIdentifier} used only as a {@link
* org.apache.gravitino.lock.TreeLockUtils} lock path for group operations
keyed by external id.
@@ -202,6 +220,10 @@ public class AuthorizationUtils {
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME,
Entity.USER_EXTERNAL_ID_SCHEMA_NAME);
}
+ public static Namespace ofUserIdNamespace(String metalake) {
+ return Namespace.of(metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME,
Entity.USER_ID_SCHEMA_NAME);
+ }
+
public static Namespace ofGroupExternalIdNamespace(String metalake) {
return Namespace.of(
metalake, Entity.SYSTEM_CATALOG_RESERVED_NAME,
Entity.GROUP_EXTERNAL_ID_SCHEMA_NAME);
@@ -222,6 +244,16 @@ public class AuthorizationUtils {
checkUserExternalIdNamespace(ident.namespace());
}
+ /**
+ * Validates that the name identifier refers to a user id in a metalake.
+ *
+ * @param ident the user id name identifier to validate
+ */
+ public static void checkUserId(NameIdentifier ident) {
+ NameIdentifier.check(ident != null, "User id identifier must not be null");
+ checkUserIdNamespace(ident.namespace());
+ }
+
public static void checkGroup(NameIdentifier ident) {
NameIdentifier.check(ident != null, "Group identifier must not be null");
checkGroupNamespace(ident.namespace());
@@ -256,6 +288,13 @@ public class AuthorizationUtils {
namespace);
}
+ public static void checkUserIdNamespace(Namespace namespace) {
+ Namespace.check(
+ namespace != null && namespace.length() == 3,
+ "User id namespace must have 3 levels, the input namespace is %s",
+ namespace);
+ }
+
public static void checkGroupExternalIdNamespace(Namespace namespace) {
Namespace.check(
namespace != null && namespace.length() == 3,
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupExternalManager.java
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupExternalManager.java
index 32475f6a64..7fb7bc62dd 100644
---
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupExternalManager.java
+++
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupExternalManager.java
@@ -117,50 +117,6 @@ class UserGroupExternalManager extends UserGroupManager {
}
}
- User enableUser(String metalake, String externalId) throws
NoSuchUserException {
- return updateEnabledByExternalId(metalake, externalId, true);
- }
-
- User disableUser(String metalake, String externalId) throws
NoSuchUserException {
- return updateEnabledByExternalId(metalake, externalId, false);
- }
-
- private User updateEnabledByExternalId(String metalake, String externalId,
boolean enabled)
- throws NoSuchUserException {
- try {
- return store
- .externalIdOperations()
- .updateByExternalId(
- AuthorizationUtils.ofUserExternalId(metalake, externalId),
- Entity.EntityType.USER,
- UserEntity.class,
- user ->
- UserEntity.builder()
- .withId(user.id())
- .withName(user.name())
- .withNamespace(user.namespace())
- .withExternalId(user.externalId())
- .withEnabled(enabled)
- .withRoleNames(user.roleNames())
- .withRoleIds(user.roleIds())
- .withAuditInfo(user.auditInfo())
- .build());
- } catch (NoSuchEntityException e) {
- LOG.warn(
- "User with external id {} does not exist in the metalake {}",
externalId, metalake, e);
- throw new NoSuchUserException(
- AuthorizationUtils.USER_WITH_EXTERNAL_ID_DOES_NOT_EXIST_MSG,
externalId, metalake);
- } catch (IOException ioe) {
- LOG.error(
- "Updating enabled state for user with external id {} in the metalake
{} failed due to"
- + " storage issues",
- externalId,
- metalake,
- ioe);
- throw new RuntimeException(ioe);
- }
- }
-
Group addGroup(String metalake, String group, String externalId)
throws GroupAlreadyExistsException {
try {
diff --git
a/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
new file mode 100644
index 0000000000..22001934b7
--- /dev/null
+++
b/core/src/main/java/org/apache/gravitino/authorization/UserGroupIdManager.java
@@ -0,0 +1,138 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.gravitino.authorization;
+
+import com.google.common.base.Preconditions;
+import java.io.IOException;
+import java.time.Instant;
+import org.apache.gravitino.Entity;
+import org.apache.gravitino.EntityStore;
+import org.apache.gravitino.exceptions.NoSuchEntityException;
+import org.apache.gravitino.exceptions.NoSuchUserException;
+import org.apache.gravitino.meta.AuditInfo;
+import org.apache.gravitino.meta.UserEntity;
+import org.apache.gravitino.storage.IdGenerator;
+import org.apache.gravitino.utils.PrincipalUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Manages user and group operations keyed by Gravitino-assigned id within a
metalake. */
+class UserGroupIdManager extends UserGroupManager {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(UserGroupIdManager.class);
+
+ /**
+ * Creates a {@link UserGroupIdManager} instance.
+ *
+ * @param store the entity store
+ * @param idGenerator the id generator
+ */
+ UserGroupIdManager(EntityStore store, IdGenerator idGenerator) {
+ super(store, idGenerator);
+ }
+
+ boolean removeUserById(String metalake, long userId) {
+ try {
+ return store
+ .idOperations()
+ .deleteById(AuthorizationUtils.ofUserId(metalake, userId),
Entity.EntityType.USER);
+ } catch (IOException ioe) {
+ LOG.error(
+ "Removing user with id {} in the metalake {} failed due to storage
issues",
+ userId,
+ metalake,
+ ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ User getUserById(String metalake, long userId) throws NoSuchUserException {
+ try {
+ return store
+ .idOperations()
+ .getById(
+ AuthorizationUtils.ofUserId(metalake, userId),
+ Entity.EntityType.USER,
+ UserEntity.class);
+ } catch (NoSuchEntityException e) {
+ LOG.warn("User with id {} does not exist in the metalake {}", userId,
metalake, e);
+ throw new NoSuchUserException(
+ AuthorizationUtils.USER_WITH_ID_DOES_NOT_EXIST_MSG, userId,
metalake);
+ } catch (IOException ioe) {
+ LOG.error("Getting user with id {} failed due to storage issues",
userId, ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ User alterUserById(String metalake, long userId, UserChange... changes)
+ throws NoSuchUserException {
+ Preconditions.checkArgument(
+ changes != null && changes.length > 0, "User changes cannot be empty");
+ try {
+ return store
+ .idOperations()
+ .updateById(
+ AuthorizationUtils.ofUserId(metalake, userId),
+ Entity.EntityType.USER,
+ UserEntity.class,
+ user -> applyChanges(user, changes));
+ } catch (NoSuchEntityException e) {
+ LOG.warn("User with id {} does not exist in the metalake {}", userId,
metalake, e);
+ throw new NoSuchUserException(
+ AuthorizationUtils.USER_WITH_ID_DOES_NOT_EXIST_MSG, userId,
metalake);
+ } catch (IOException ioe) {
+ LOG.error(
+ "Altering user with id {} in the metalake {} failed due to storage
issues",
+ userId,
+ metalake,
+ ioe);
+ throw new RuntimeException(ioe);
+ }
+ }
+
+ private static UserEntity applyChanges(UserEntity user, UserChange...
changes) {
+ String externalId = user.externalId();
+ boolean enabled = user.enabled();
+ for (UserChange change : changes) {
+ if (change instanceof UserChange.UpdateEnabled) {
+ enabled = ((UserChange.UpdateEnabled) change).enabled();
+ } else if (change instanceof UserChange.UpdateExternalId) {
+ externalId = ((UserChange.UpdateExternalId) change).getNewExternalId();
+ } else {
+ throw new IllegalArgumentException("Unsupported user change: " +
change);
+ }
+ }
+ return UserEntity.builder()
+ .withId(user.id())
+ .withName(user.name())
+ .withNamespace(user.namespace())
+ .withExternalId(externalId)
+ .withEnabled(enabled)
+ .withRoleNames(user.roleNames())
+ .withRoleIds(user.roleIds())
+ .withAuditInfo(
+ AuditInfo.builder()
+ .withCreator(user.auditInfo().creator())
+ .withCreateTime(user.auditInfo().createTime())
+
.withLastModifier(PrincipalUtils.getCurrentPrincipal().getName())
+ .withLastModifiedTime(Instant.now())
+ .build())
+ .build();
+ }
+}
diff --git
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
index 3c1d8d7949..f8028b8c3a 100644
---
a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java
@@ -34,6 +34,7 @@ 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.authorization.UserChange;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
import org.apache.gravitino.exceptions.IllegalRoleException;
import org.apache.gravitino.exceptions.NoSuchGroupException;
@@ -99,15 +100,20 @@ public class AccessControlHookDispatcher implements
AccessControlDispatcher {
}
@Override
- public User enableUser(String metalake, String externalId)
+ public User getUserById(String metalake, long userId)
throws NoSuchUserException, NoSuchMetalakeException {
- return dispatcher.enableUser(metalake, externalId);
+ return dispatcher.getUserById(metalake, userId);
}
@Override
- public User disableUser(String metalake, String externalId)
+ public boolean removeUserById(String metalake, long userId) throws
NoSuchMetalakeException {
+ return dispatcher.removeUserById(metalake, userId);
+ }
+
+ @Override
+ public User alterUserById(String metalake, long userId, UserChange...
changes)
throws NoSuchUserException, NoSuchMetalakeException {
- return dispatcher.disableUser(metalake, externalId);
+ return dispatcher.alterUserById(metalake, userId, changes);
}
@Override
diff --git
a/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
b/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
index 9128e5f594..dd3f734402 100644
---
a/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
+++
b/core/src/main/java/org/apache/gravitino/listener/AccessControlEventDispatcher.java
@@ -29,6 +29,7 @@ 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.authorization.UserChange;
import org.apache.gravitino.exceptions.GroupAlreadyExistsException;
import org.apache.gravitino.exceptions.IllegalRoleException;
import org.apache.gravitino.exceptions.NoSuchGroupException;
@@ -50,12 +51,6 @@ import
org.apache.gravitino.listener.api.event.CreateRolePreEvent;
import org.apache.gravitino.listener.api.event.DeleteRoleEvent;
import org.apache.gravitino.listener.api.event.DeleteRoleFailureEvent;
import org.apache.gravitino.listener.api.event.DeleteRolePreEvent;
-import org.apache.gravitino.listener.api.event.DisableUserEvent;
-import org.apache.gravitino.listener.api.event.DisableUserFailureEvent;
-import org.apache.gravitino.listener.api.event.DisableUserPreEvent;
-import org.apache.gravitino.listener.api.event.EnableUserEvent;
-import org.apache.gravitino.listener.api.event.EnableUserFailureEvent;
-import org.apache.gravitino.listener.api.event.EnableUserPreEvent;
import org.apache.gravitino.listener.api.event.GetGroupByExternalIdEvent;
import
org.apache.gravitino.listener.api.event.GetGroupByExternalIdFailureEvent;
import org.apache.gravitino.listener.api.event.GetGroupByExternalIdPreEvent;
@@ -258,38 +253,22 @@ public class AccessControlEventDispatcher implements
AccessControlDispatcher {
/** {@inheritDoc} */
@Override
- public User enableUser(String metalake, String externalId)
+ public User getUserById(String metalake, long userId)
throws NoSuchUserException, NoSuchMetalakeException {
- String initiator = PrincipalUtils.getCurrentUserName();
-
- eventBus.dispatchEvent(new EnableUserPreEvent(initiator, metalake,
externalId));
- try {
- User userObject = dispatcher.enableUser(metalake, externalId);
- eventBus.dispatchEvent(new EnableUserEvent(initiator, metalake, new
UserInfo(userObject)));
+ return dispatcher.getUserById(metalake, userId);
+ }
- return userObject;
- } catch (Exception e) {
- eventBus.dispatchEvent(new EnableUserFailureEvent(initiator, metalake,
e, externalId));
- throw e;
- }
+ /** {@inheritDoc} */
+ @Override
+ public boolean removeUserById(String metalake, long userId) throws
NoSuchMetalakeException {
+ return dispatcher.removeUserById(metalake, userId);
}
/** {@inheritDoc} */
@Override
- public User disableUser(String metalake, String externalId)
+ public User alterUserById(String metalake, long userId, UserChange...
changes)
throws NoSuchUserException, NoSuchMetalakeException {
- String initiator = PrincipalUtils.getCurrentUserName();
-
- eventBus.dispatchEvent(new DisableUserPreEvent(initiator, metalake,
externalId));
- try {
- User userObject = dispatcher.disableUser(metalake, externalId);
- eventBus.dispatchEvent(new DisableUserEvent(initiator, metalake, new
UserInfo(userObject)));
-
- return userObject;
- } catch (Exception e) {
- eventBus.dispatchEvent(new DisableUserFailureEvent(initiator, metalake,
e, externalId));
- throw e;
- }
+ return dispatcher.alterUserById(metalake, userId, changes);
}
/** {@inheritDoc} */
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserEvent.java
deleted file mode 100644
index e52e9420b7..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserEvent.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-import org.apache.gravitino.listener.api.info.UserInfo;
-
-/** Represents an event triggered after successfully disabling a user by
external id. */
-@DeveloperApi
-public class DisableUserEvent extends UserEvent {
- private final UserInfo updatedUserInfo;
-
- /**
- * Creates a new {DisableUserEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param updatedUserInfo The updated user information.
- */
- public DisableUserEvent(String initiator, String metalake, UserInfo
updatedUserInfo) {
- super(
- initiator,
- AuthorizationUtils.ofUserExternalId(
- metalake,
- updatedUserInfo
- .externalId()
- .orElseThrow(
- () ->
- new IllegalStateException(
- "User external id is required for
DisableUserEvent"))));
- this.updatedUserInfo = updatedUserInfo;
- }
-
- /**
- * Returns the updated user information.
- *
- * @return The user information.
- */
- public UserInfo updatedUserInfo() {
- return updatedUserInfo;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.DISABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserFailureEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserFailureEvent.java
deleted file mode 100644
index ae6361ccf0..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserFailureEvent.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-
-/** Represents an event triggered when disabling a user by external id fails.
*/
-@DeveloperApi
-public class DisableUserFailureEvent extends UserFailureEvent {
- private final String externalId;
-
- /**
- * Creates a new {DisableUserFailureEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param exception The exception that caused the failure.
- * @param externalId The external identifier of the user.
- */
- public DisableUserFailureEvent(
- String initiator, String metalake, Exception exception, String
externalId) {
- super(initiator, AuthorizationUtils.ofUserExternalId(metalake,
externalId), exception);
- this.externalId = externalId;
- }
-
- /**
- * Returns the external identifier of the user.
- *
- * @return The external identifier.
- */
- public String externalId() {
- return externalId;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.DISABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserPreEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserPreEvent.java
deleted file mode 100644
index 91a4458c4d..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/DisableUserPreEvent.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-
-/** Represents an event triggered before disabling a user by external id. */
-@DeveloperApi
-public class DisableUserPreEvent extends UserPreEvent {
- private final String externalId;
-
- /**
- * Creates a new {DisableUserPreEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param externalId The external identifier of the user.
- */
- public DisableUserPreEvent(String initiator, String metalake, String
externalId) {
- super(initiator, AuthorizationUtils.ofUserExternalId(metalake,
externalId));
- this.externalId = externalId;
- }
-
- /**
- * Returns the external identifier of the user.
- *
- * @return The external identifier.
- */
- public String externalId() {
- return externalId;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.DISABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserEvent.java
deleted file mode 100644
index 4f08221a23..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserEvent.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-import org.apache.gravitino.listener.api.info.UserInfo;
-
-/** Represents an event triggered after successfully enabling a user by
external id. */
-@DeveloperApi
-public class EnableUserEvent extends UserEvent {
- private final UserInfo updatedUserInfo;
-
- /**
- * Creates a new {EnableUserEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param updatedUserInfo The updated user information.
- */
- public EnableUserEvent(String initiator, String metalake, UserInfo
updatedUserInfo) {
- super(
- initiator,
- AuthorizationUtils.ofUserExternalId(
- metalake,
- updatedUserInfo
- .externalId()
- .orElseThrow(
- () ->
- new IllegalStateException(
- "User external id is required for
EnableUserEvent"))));
- this.updatedUserInfo = updatedUserInfo;
- }
-
- /**
- * Returns the updated user information.
- *
- * @return The user information.
- */
- public UserInfo updatedUserInfo() {
- return updatedUserInfo;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.ENABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserFailureEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserFailureEvent.java
deleted file mode 100644
index 08a2cab0a7..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserFailureEvent.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-
-/** Represents an event triggered when enabling a user by external id fails. */
-@DeveloperApi
-public class EnableUserFailureEvent extends UserFailureEvent {
- private final String externalId;
-
- /**
- * Creates a new {EnableUserFailureEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param exception The exception that caused the failure.
- * @param externalId The external identifier of the user.
- */
- public EnableUserFailureEvent(
- String initiator, String metalake, Exception exception, String
externalId) {
- super(initiator, AuthorizationUtils.ofUserExternalId(metalake,
externalId), exception);
- this.externalId = externalId;
- }
-
- /**
- * Returns the external identifier of the user.
- *
- * @return The external identifier.
- */
- public String externalId() {
- return externalId;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.ENABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserPreEvent.java
b/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserPreEvent.java
deleted file mode 100644
index e89a76ee04..0000000000
---
a/core/src/main/java/org/apache/gravitino/listener/api/event/EnableUserPreEvent.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.annotation.DeveloperApi;
-import org.apache.gravitino.authorization.AuthorizationUtils;
-
-/** Represents an event triggered before enabling a user by external id. */
-@DeveloperApi
-public class EnableUserPreEvent extends UserPreEvent {
- private final String externalId;
-
- /**
- * Creates a new {@link EnableUserPreEvent}.
- *
- * @param initiator The user who initiated the request.
- * @param metalake The metalake name.
- * @param externalId The external identifier of the user.
- */
- public EnableUserPreEvent(String initiator, String metalake, String
externalId) {
- super(initiator, AuthorizationUtils.ofUserExternalId(metalake,
externalId));
- this.externalId = externalId;
- }
-
- /**
- * Returns the external identifier of the user.
- *
- * @return The external identifier.
- */
- public String externalId() {
- return externalId;
- }
-
- @Override
- public OperationType operationType() {
- return OperationType.ENABLE_USER;
- }
-}
diff --git
a/core/src/main/java/org/apache/gravitino/listener/api/info/UserInfo.java
b/core/src/main/java/org/apache/gravitino/listener/api/info/UserInfo.java
index 295342dcfc..c2a0614ad2 100644
--- a/core/src/main/java/org/apache/gravitino/listener/api/info/UserInfo.java
+++ b/core/src/main/java/org/apache/gravitino/listener/api/info/UserInfo.java
@@ -19,6 +19,7 @@
package org.apache.gravitino.listener.api.info;
+import com.google.common.base.Preconditions;
import java.util.List;
import java.util.Optional;
import org.apache.gravitino.annotation.DeveloperApi;
@@ -27,6 +28,7 @@ import org.apache.gravitino.authorization.User;
/** Provides read-only access to user information for event listeners. */
@DeveloperApi
public class UserInfo {
+ private final Long id;
private final String name;
private final Optional<String> externalId;
private final boolean enabled;
@@ -38,12 +40,22 @@ public class UserInfo {
* @param user the {@link User} instance.
*/
public UserInfo(User user) {
+ this.id = Preconditions.checkNotNull(user.id(), "user id");
this.name = user.name();
this.externalId = Optional.ofNullable(user.externalId());
this.enabled = user.enabled();
this.roles = user.roles();
}
+ /**
+ * Returns the Gravitino-assigned id of the user.
+ *
+ * @return the user id
+ */
+ public Long id() {
+ return id;
+ }
+
/**
* Returns the name of the user.
*
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
index daef6d052e..d7e7a0e1a5 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/JDBCBackend.java
@@ -42,6 +42,7 @@ import org.apache.gravitino.Namespace;
import org.apache.gravitino.RelationalEntity;
import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.UnsupportedEntityTypeException;
+import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.exceptions.NoSuchEntityException;
import org.apache.gravitino.meta.BaseMetalake;
import org.apache.gravitino.meta.CatalogEntity;
@@ -342,6 +343,38 @@ public class JDBCBackend implements RelationalBackend,
SupportsOrphanedRelationC
}
}
+ @Override
+ public <E extends Entity & HasIdentifier> E getById(
+ NameIdentifier ident, Entity.EntityType entityType)
+ throws NoSuchEntityException, IOException {
+ switch (entityType) {
+ case USER:
+ AuthorizationUtils.checkUserId(ident);
+ return (E)
+ UserMetaService.getInstance()
+ .getUserById(ident.namespace().level(0),
Long.parseLong(ident.name()));
+ default:
+ throw new UnsupportedEntityTypeException(
+ "Unsupported entity type: %s for get by id operation", entityType);
+ }
+ }
+
+ @Override
+ public <E extends Entity & HasIdentifier> E updateById(
+ NameIdentifier ident, Entity.EntityType entityType, Function<E, E>
updater)
+ throws NoSuchEntityException, IOException {
+ switch (entityType) {
+ case USER:
+ AuthorizationUtils.checkUserId(ident);
+ return (E)
+ UserMetaService.getInstance()
+ .updateUserById(ident.namespace().level(0),
Long.parseLong(ident.name()), updater);
+ default:
+ throw new UnsupportedEntityTypeException(
+ "Unsupported entity type: %s for update by id operation",
entityType);
+ }
+ }
+
@Override
public <E extends Entity & HasIdentifier> List<E> batchGet(
List<NameIdentifier> identifiers, Entity.EntityType entityType) {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
index cffdd5e278..50e48af1c2 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalBackend.java
@@ -142,6 +142,35 @@ public interface RelationalBackend extends Closeable,
SupportsRelationOperations
NameIdentifier ident, Entity.EntityType entityType, Function<E, E>
updater)
throws NoSuchEntityException, IOException;
+ /**
+ * Retrieves an entity by Gravitino-assigned id.
+ *
+ * @param <E> The type of the entity returned.
+ * @param ident The id name identifier.
+ * @param entityType The type of the entity.
+ * @return The entity associated with the id name identifier.
+ * @throws NoSuchEntityException If the entity does not exist.
+ * @throws IOException If an I/O exception occurs during retrieval.
+ */
+ <E extends Entity & HasIdentifier> E getById(NameIdentifier ident,
Entity.EntityType entityType)
+ throws NoSuchEntityException, IOException;
+
+ /**
+ * Updates an entity by Gravitino-assigned id.
+ *
+ * @param <E> the type of the entity returned
+ * @param ident the id name identifier
+ * @param entityType the type of the entity
+ * @param updater a {@link Function} that takes the current entity instance
and returns the
+ * updated instance
+ * @return the updated entity
+ * @throws NoSuchEntityException if the entity does not exist
+ * @throws IOException if the update operation fails
+ */
+ <E extends Entity & HasIdentifier> E updateById(
+ NameIdentifier ident, Entity.EntityType entityType, Function<E, E>
updater)
+ throws NoSuchEntityException, IOException;
+
/**
* Batch retrieves the entities associated with the identifiers and the
entity type.
*
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
index cd303eb0a9..b762320169 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/RelationalEntityStore.java
@@ -48,6 +48,7 @@ import org.apache.gravitino.RelationQuery;
import org.apache.gravitino.RelationUpdate;
import org.apache.gravitino.RelationalEntity;
import org.apache.gravitino.SupportsExternalIdOperations;
+import org.apache.gravitino.SupportsIdOperations;
import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.authorization.SecurableObject;
import org.apache.gravitino.cache.CacheFactory;
@@ -76,6 +77,7 @@ public class RelationalEntityStore
implements EntityStore,
SupportsRelationOperations,
SupportsExternalIdOperations,
+ SupportsIdOperations,
SupportsEntityChangeLog {
private static final Logger LOGGER =
LoggerFactory.getLogger(RelationalEntityStore.class);
public static final ImmutableMap<String, String> RELATIONAL_BACKENDS =
@@ -205,6 +207,11 @@ public class RelationalEntityStore
return this;
}
+ @Override
+ public SupportsIdOperations idOperations() {
+ return this;
+ }
+
@Override
public <E extends Entity & HasIdentifier> E getByExternalId(
NameIdentifier ident, Entity.EntityType entityType, Class<E> type)
@@ -240,6 +247,39 @@ public class RelationalEntityStore
}
}
+ @Override
+ public <E extends Entity & HasIdentifier> E getById(
+ NameIdentifier ident, Entity.EntityType entityType, Class<E> type)
+ throws NoSuchEntityException, IOException {
+ return backend.getById(ident, entityType);
+ }
+
+ @Override
+ public <E extends Entity & HasIdentifier> E updateById(
+ NameIdentifier ident, Entity.EntityType entityType, Class<E> type,
Function<E, E> updater)
+ throws NoSuchEntityException, IOException {
+ E updatedEntity = backend.updateById(ident, entityType, updater);
+ cache.invalidate(updatedEntity.nameIdentifier(), entityType);
+ return updatedEntity;
+ }
+
+ @Override
+ public boolean deleteById(NameIdentifier ident, Entity.EntityType
entityType) throws IOException {
+ NameIdentifier nameIdent = null;
+ try {
+ HasIdentifier entity = backend.getById(ident, entityType);
+ nameIdent = entity.nameIdentifier();
+ return backend.delete(nameIdent, entityType, false);
+ } catch (NoSuchEntityException e) {
+ LOGGER.warn("The entity to be deleted by id does not exist in the store:
{}", ident, e);
+ return false;
+ } finally {
+ if (nameIdent != null) {
+ cache.invalidate(nameIdent, entityType);
+ }
+ }
+ }
+
@Override
public <E extends Entity & HasIdentifier> List<E> batchGet(
List<NameIdentifier> idents, Entity.EntityType entityType, Class<E>
clazz) {
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
index 87f8a26b17..45bcb63c5d 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaMapper.java
@@ -104,6 +104,12 @@ public interface UserMetaMapper {
UserPO selectUserMetaByMetalakeNameAndExternalId(
@Param("metalakeName") String metalakeName, @Param("externalId") String
externalId);
+ @SelectProvider(
+ type = UserMetaSQLProviderFactory.class,
+ method = "selectUserMetaByMetalakeNameAndId")
+ UserPO selectUserMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("userId") Long
userId);
+
@UpdateProvider(type = UserMetaSQLProviderFactory.class, method =
"updateUserMetaByExternalId")
Integer updateUserMetaByExternalId(
@Param("newUserMeta") UserPO newUserPO, @Param("oldUserMeta") UserPO
oldUserPO);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaSQLProviderFactory.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaSQLProviderFactory.java
index cc7de6fb28..9d668dd2f3 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaSQLProviderFactory.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/UserMetaSQLProviderFactory.java
@@ -120,6 +120,11 @@ public class UserMetaSQLProviderFactory {
return
getProvider().selectUserMetaByMetalakeNameAndExternalId(metalakeName,
externalId);
}
+ public static String selectUserMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("userId") Long
userId) {
+ return getProvider().selectUserMetaByMetalakeNameAndId(metalakeName,
userId);
+ }
+
public static String updateUserMetaByExternalId(
@Param("newUserMeta") UserPO newUserPO, @Param("oldUserMeta") UserPO
oldUserPO) {
return getProvider().updateUserMetaByExternalId(newUserPO, oldUserPO);
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/UserMetaBaseSQLProvider.java
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/UserMetaBaseSQLProvider.java
index 6c0dc22334..c0647d3855 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/UserMetaBaseSQLProvider.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/UserMetaBaseSQLProvider.java
@@ -70,6 +70,23 @@ public class UserMetaBaseSQLProvider {
+ " AND ut.deleted_at = 0 AND mt.deleted_at = 0";
}
+ public String selectUserMetaByMetalakeNameAndId(
+ @Param("metalakeName") String metalakeName, @Param("userId") Long
userId) {
+ return "SELECT ut.user_id as userId, ut.user_name as userName,"
+ + " ut.metalake_id as metalakeId,"
+ + " ut.external_id as externalId, ut.enabled as enabled,"
+ + " ut.audit_info as auditInfo, ut.current_version as currentVersion,"
+ + " ut.last_version as lastVersion, ut.deleted_at as deletedAt"
+ + " FROM "
+ + USER_TABLE_NAME
+ + " ut JOIN "
+ + MetalakeMetaMapper.TABLE_NAME
+ + " mt ON ut.metalake_id = mt.metalake_id"
+ + " WHERE mt.metalake_name = #{metalakeName}"
+ + " AND ut.user_id = #{userId}"
+ + " AND ut.deleted_at = 0 AND mt.deleted_at = 0";
+ }
+
public String updateUserMetaByExternalId(
@Param("newUserMeta") UserPO newUserPO, @Param("oldUserMeta") UserPO
oldUserPO) {
return "UPDATE "
diff --git
a/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
b/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
index ba8cf5271a..0fbdab8ca2 100644
---
a/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
+++
b/core/src/main/java/org/apache/gravitino/storage/relational/service/UserMetaService.java
@@ -387,4 +387,89 @@ public class UserMetaService {
}
return newEntity;
}
+
+ private UserPO getUserPOByMetalakeNameAndId(String metalakeName, Long
userId) {
+ UserPO userPO =
+ SessionUtils.getWithoutCommit(
+ UserMetaMapper.class,
+ mapper -> mapper.selectUserMetaByMetalakeNameAndId(metalakeName,
userId));
+
+ if (userPO == null) {
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ Entity.EntityType.USER.name().toLowerCase(),
+ String.valueOf(userId));
+ }
+ return userPO;
+ }
+
+ @Monitored(metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
baseMetricName = "getUserById")
+ public UserEntity getUserById(String metalake, long userId) {
+ Namespace userNamespace = AuthorizationUtils.ofUserNamespace(metalake);
+ UserPO userPO = getUserPOByMetalakeNameAndId(metalake, userId);
+ List<RolePO> rolePOs =
RoleMetaService.getInstance().listRolesByUserId(userPO.getUserId());
+ return POConverters.fromUserPO(userPO, rolePOs, userNamespace);
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "updateUserById")
+ public <E extends Entity & HasIdentifier> UserEntity updateUserById(
+ String metalake, long userId, Function<E, E> updater) throws IOException
{
+ Namespace userNamespace = AuthorizationUtils.ofUserNamespace(metalake);
+ UserPO oldUserPO = getUserPOByMetalakeNameAndId(metalake, userId);
+ List<RolePO> rolePOs =
RoleMetaService.getInstance().listRolesByUserId(oldUserPO.getUserId());
+ UserEntity oldEntity = POConverters.fromUserPO(oldUserPO, rolePOs,
userNamespace);
+ UserEntity newEntity = (UserEntity) updater.apply((E) oldEntity);
+ Preconditions.checkArgument(
+ Objects.equals(oldEntity.id(), newEntity.id()),
+ "The updated user entity id: %s should be same with the user entity id
before: %s",
+ newEntity.id(),
+ oldEntity.id());
+
+ try {
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ UserMetaMapper.class,
+ mapper ->
+ mapper.updateUserMeta(
+ POConverters.updateUserPOWithVersion(oldUserPO,
newEntity), oldUserPO)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ UserMetaMapper.class,
+ mapper -> mapper.touchUserUpdatedAt(oldUserPO.getUserId())));
+ } catch (RuntimeException re) {
+ ExceptionUtils.checkSQLException(
+ re, Entity.EntityType.USER, newEntity.nameIdentifier().toString());
+ throw re;
+ }
+ return newEntity;
+ }
+
+ @Monitored(
+ metricsSource = GRAVITINO_RELATIONAL_STORE_METRIC_NAME,
+ baseMetricName = "deleteUserById")
+ public boolean deleteUserById(String metalake, long userId) {
+ try {
+ getUserPOByMetalakeNameAndId(metalake, userId);
+ } catch (NoSuchEntityException e) {
+ return false;
+ }
+
+ SessionUtils.doMultipleWithCommit(
+ () ->
+ SessionUtils.doWithoutCommit(
+ UserMetaMapper.class, mapper ->
mapper.softDeleteUserMetaByUserId(userId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ UserRoleRelMapper.class, mapper ->
mapper.softDeleteUserRoleRelByUserId(userId)),
+ () ->
+ SessionUtils.doWithoutCommit(
+ OwnerMetaMapper.class,
+ mapper ->
+ mapper.softDeleteOwnerRelByOwnerIdAndType(
+ userId, Entity.EntityType.USER.name())));
+ return true;
+ }
}
diff --git
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
index 8cc94e01fb..c80b4abadc 100644
---
a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
+++
b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java
@@ -471,10 +471,12 @@ public class TestAccessControlManager {
User added = accessControlManager.addUser(METALAKE, user, extId, true);
Assertions.assertEquals(extId, added.externalId());
Assertions.assertTrue(added.enabled());
+ long userId = added.id();
createCatalogRole("ext_role");
accessControlManager.grantRolesToUser(METALAKE,
Lists.newArrayList("ext_role"), user);
- User disabled = accessControlManager.disableUser(METALAKE, extId);
+ User disabled =
+ accessControlManager.alterUserById(METALAKE, userId,
UserChange.updateEnabled(false));
Assertions.assertFalse(disabled.enabled());
assertSortedRoles(disabled, "ext_role");
@@ -483,7 +485,8 @@ public class TestAccessControlManager {
assertSortedRoles(accessControlManager.getUser(METALAKE, user),
"ext_role", "ext_role2");
Assertions.assertFalse(accessControlManager.getUserByExternalId(METALAKE,
extId).enabled());
- User enabled = accessControlManager.enableUser(METALAKE, extId);
+ User enabled =
+ accessControlManager.alterUserById(METALAKE, userId,
UserChange.updateEnabled(true));
Assertions.assertTrue(enabled.enabled());
assertSortedRoles(enabled, "ext_role", "ext_role2");
@@ -506,12 +509,9 @@ public class TestAccessControlManager {
assertMissingExt(
NoSuchGroupException.class,
() -> accessControlManager.getGroupByExternalId(METALAKE,
"missing-ext-id"));
- assertMissingExt(
- NoSuchUserException.class,
- () -> accessControlManager.disableUser(METALAKE, "missing-ext-id"));
- assertMissingExt(
+ Assertions.assertThrows(
NoSuchUserException.class,
- () -> accessControlManager.enableUser(METALAKE, "missing-ext-id"));
+ () -> accessControlManager.alterUserById(METALAKE, -1L,
UserChange.updateEnabled(false)));
}
@Test
@@ -555,12 +555,58 @@ public class TestAccessControlManager {
accessControlManager.removeGroupByExternalId(METALAKE,
"missing-ext-id"));
}
+ @Test
+ public void testUserById() {
+ User added = accessControlManager.addUser(METALAKE, "id_user",
"ext-id-user", true);
+ long userId = added.id();
+ Assertions.assertNotNull(userId);
+
+ User loaded = accessControlManager.getUserById(METALAKE, userId);
+ Assertions.assertEquals(userId, loaded.id());
+ Assertions.assertEquals("id_user", loaded.name());
+ Assertions.assertEquals("ext-id-user", loaded.externalId());
+ Assertions.assertTrue(loaded.enabled());
+
+ User disabled =
+ accessControlManager.alterUserById(METALAKE, userId,
UserChange.updateEnabled(false));
+ Assertions.assertFalse(disabled.enabled());
+ Assertions.assertEquals(userId, disabled.id());
+ Assertions.assertEquals("ext-id-user", disabled.externalId());
+ Assertions.assertEquals(added.auditInfo().creator(),
disabled.auditInfo().creator());
+ Assertions.assertEquals(added.auditInfo().createTime(),
disabled.auditInfo().createTime());
+ Assertions.assertNotNull(disabled.auditInfo().lastModifier());
+ Assertions.assertNotNull(disabled.auditInfo().lastModifiedTime());
+ Assertions.assertTrue(
+
!disabled.auditInfo().lastModifiedTime().isBefore(added.auditInfo().createTime()));
+
+ User enabledAndExt =
+ accessControlManager.alterUserById(
+ METALAKE,
+ userId,
+ UserChange.updateEnabled(true),
+ UserChange.updateExternalId("ext-id-user-2"));
+ Assertions.assertTrue(enabledAndExt.enabled());
+ Assertions.assertEquals(userId, enabledAndExt.id());
+ Assertions.assertEquals("ext-id-user-2", enabledAndExt.externalId());
+ User byNewExt = accessControlManager.getUserByExternalId(METALAKE,
"ext-id-user-2");
+ Assertions.assertEquals("id_user", byNewExt.name());
+ Assertions.assertEquals(userId, byNewExt.id());
+
+ Assertions.assertTrue(accessControlManager.removeUserById(METALAKE,
userId));
+ Assertions.assertThrows(
+ NoSuchUserException.class, () ->
accessControlManager.getUserById(METALAKE, userId));
+ Assertions.assertFalse(accessControlManager.removeUserById(METALAKE,
userId));
+ Assertions.assertThrows(
+ NoSuchUserException.class,
+ () -> accessControlManager.alterUserById(METALAKE, userId,
UserChange.updateEnabled(true)));
+ }
+
@Test
public void testExtCache() {
String extId = "ext-cache-user";
- accessControlManager.addUser(METALAKE, "cache_user", extId, true);
+ User added = accessControlManager.addUser(METALAKE, "cache_user", extId,
true);
accessControlManager.getUser(METALAKE, "cache_user");
- accessControlManager.disableUser(METALAKE, extId);
+ accessControlManager.alterUserById(METALAKE, added.id(),
UserChange.updateEnabled(false));
Assertions.assertFalse(accessControlManager.getUser(METALAKE,
"cache_user").enabled());
accessControlManager.removeUser(METALAKE, "cache_user");
}
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
index e9ea0639dd..90c9161582 100644
---
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
@@ -90,6 +90,7 @@ public class TestUserEvent {
User mockUser = getMockUser("mock_user", ImmutableList.of("admin"));
UserInfo info = new UserInfo(mockUser);
+ Assertions.assertEquals(1L, info.id());
Assertions.assertEquals("mock_user", info.name());
Assertions.assertEquals(Optional.empty(), info.externalId());
Assertions.assertEquals(ImmutableList.of("admin"), info.roles());
@@ -505,19 +506,6 @@ public class TestUserEvent {
Assertions.assertEquals(OperationType.GET_USER_BY_EXTERNAL_ID,
event.operationType());
}
- @Test
- void testEnableUserEvent() {
- dispatcher.enableUser(METALAKE, USER_EXT_ID);
-
- PreEvent preEvent = dummyEventListener.popPreEvent();
- Assertions.assertEquals(EnableUserPreEvent.class, preEvent.getClass());
- Assertions.assertEquals(OperationType.ENABLE_USER,
preEvent.operationType());
-
- Event event = dummyEventListener.popPostEvent();
- Assertions.assertEquals(EnableUserEvent.class, event.getClass());
- Assertions.assertEquals(OperationType.ENABLE_USER, event.operationType());
- }
-
@Test
void testRemoveUserByExternalIdEvent() {
dispatcher.removeUserByExternalId(METALAKE, USER_EXT_ID);
@@ -558,7 +546,6 @@ public class TestUserEvent {
when(dispatcher.getUser(METALAKE, userName)).thenReturn(user);
when(dispatcher.getUserByExternalId(METALAKE,
USER_EXT_ID)).thenReturn(externalIdUser);
- when(dispatcher.enableUser(METALAKE,
USER_EXT_ID)).thenReturn(externalIdUser);
when(dispatcher.getUser(METALAKE, inExistUserName))
.thenThrow(new NoSuchUserException("user not found"));
when(dispatcher.getUser(INEXIST_METALAKE, userName))
@@ -580,6 +567,7 @@ public class TestUserEvent {
private User getMockUser(String name, List<String> roles) {
User user = mock(User.class);
+ when(user.id()).thenReturn(1L);
when(user.name()).thenReturn(name);
when(user.roles()).thenReturn(roles);
@@ -589,6 +577,7 @@ public class TestUserEvent {
private User getMockUserWithExtId(
String name, String externalId, boolean enabled, List<String> roles) {
User user = mock(User.class);
+ when(user.id()).thenReturn(1L);
when(user.name()).thenReturn(name);
when(user.externalId()).thenReturn(externalId);
when(user.enabled()).thenReturn(enabled);
@@ -598,6 +587,7 @@ public class TestUserEvent {
}
private void validateUserInfo(UserInfo userInfo, User expectedUser) {
+ Assertions.assertEquals(expectedUser.id(), userInfo.id());
Assertions.assertEquals(userInfo.name(), expectedUser.name());
Assertions.assertEquals(Optional.ofNullable(expectedUser.externalId()),
userInfo.externalId());
Assertions.assertEquals(userInfo.roles(), expectedUser.roles());
diff --git
a/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
b/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
index a3d679acae..ad7acc8959 100644
---
a/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
+++
b/core/src/test/java/org/apache/gravitino/storage/memory/TestMemoryEntityStore.java
@@ -42,6 +42,7 @@ import org.apache.gravitino.Metalake;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.SupportsExternalIdOperations;
+import org.apache.gravitino.SupportsIdOperations;
import org.apache.gravitino.TestCatalog;
import org.apache.gravitino.authorization.AuthorizationUtils;
import org.apache.gravitino.authorization.Privileges;
@@ -66,7 +67,9 @@ import org.mockito.Mockito;
public class TestMemoryEntityStore {
- public static class InMemoryEntityStore implements EntityStore,
SupportsExternalIdOperations {
+ public static class InMemoryEntityStore
+ implements EntityStore, SupportsExternalIdOperations,
SupportsIdOperations {
+
private final Map<NameIdentifier, Entity> entityMap;
private final Lock lock;
@@ -153,6 +156,11 @@ public class TestMemoryEntityStore {
return this;
}
+ @Override
+ public SupportsIdOperations idOperations() {
+ return this;
+ }
+
@Override
public <E extends Entity & HasIdentifier> E getByExternalId(
NameIdentifier ident, EntityType entityType, Class<E> type)
@@ -227,6 +235,63 @@ public class TestMemoryEntityStore {
}
}
+ @Override
+ @SuppressWarnings("unchecked")
+ public <E extends Entity & HasIdentifier> E getById(
+ NameIdentifier ident, EntityType entityType, Class<E> type)
+ throws NoSuchEntityException, IOException {
+ if (entityType != EntityType.USER) {
+ throw new UnsupportedOperationException(
+ "Get by id is not supported for entity type: " + entityType);
+ }
+
+ AuthorizationUtils.checkUserId(ident);
+ long entityId = Long.parseLong(ident.name());
+ Namespace entityNamespace =
AuthorizationUtils.ofUserNamespace(ident.namespace().level(0));
+
+ for (Map.Entry<NameIdentifier, Entity> entry : entityMap.entrySet()) {
+ Entity entity = entry.getValue();
+ if (!entity.type().equals(entityType)
+ || !entry.getKey().namespace().equals(entityNamespace)) {
+ continue;
+ }
+
+ if (entity instanceof HasIdentifier hasIdentifier
+ && java.util.Objects.equals(hasIdentifier.id(), entityId)) {
+ return (E) entity;
+ }
+ }
+
+ throw new NoSuchEntityException(
+ NoSuchEntityException.NO_SUCH_ENTITY_MESSAGE,
+ entityType.name().toLowerCase(),
+ String.valueOf(entityId));
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public <E extends Entity & HasIdentifier> E updateById(
+ NameIdentifier ident, EntityType entityType, Class<E> type,
Function<E, E> updater)
+ throws NoSuchEntityException, IOException {
+ E entity = getById(ident, entityType, type);
+ E updated = updater.apply(entity);
+ return update(entity.nameIdentifier(), type, entityType, e -> updated);
+ }
+
+ @Override
+ public boolean deleteById(NameIdentifier ident, EntityType entityType)
throws IOException {
+ try {
+ if (entityType == EntityType.USER) {
+ UserEntity user = getById(ident, entityType, UserEntity.class);
+ return delete(user.nameIdentifier(), entityType);
+ }
+ throw new UnsupportedOperationException(
+ "Delete by id is not supported for entity type: " + entityType);
+ } catch (NoSuchEntityException e) {
+ return false;
+ }
+ }
+
@Override
public <E extends Entity & HasIdentifier> List<E> batchGet(
List<NameIdentifier> idents, EntityType entityType, Class<E> e) {