diff --git a/api/src/main/java/org/apache/gravitino/authorization/Role.java b/api/src/main/java/org/apache/gravitino/authorization/Role.java index c7441a9203e..9b8453f80d2 100644 --- a/api/src/main/java/org/apache/gravitino/authorization/Role.java +++ b/api/src/main/java/org/apache/gravitino/authorization/Role.java @@ -25,8 +25,7 @@ /** * The interface of a role. The role is the entity which has kinds of privileges. One role can have - * multiple privileges of one securable object. Gravitino chooses to bind one securable object to - * one role to avoid granting too many privileges to one role. + * multiple privileges of multiple securable objects. */ @Evolving public interface Role extends Auditable { diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java index e074770e84f..a7656fd02de 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoClient.java @@ -359,6 +359,16 @@ public void setOwner(MetadataObject object, String ownerName, Owner.Type ownerTy getMetalake().setOwner(object, ownerName, ownerType); } + /** + * Lists the role names. + * + * @return The role name list. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + */ + public String[] listRoleNames() throws NoSuchMetalakeException { + return getMetalake().listRoleNames(); + } + /** * Creates a new builder for constructing a GravitinoClient. * diff --git a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java index 9a13a9dd12f..58973b4cf63 100644 --- a/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java +++ b/clients/client-java/src/main/java/org/apache/gravitino/client/GravitinoMetalake.java @@ -100,9 +100,9 @@ public class GravitinoMetalake extends MetalakeDTO implements SupportsCatalogs, private static final String API_METALAKES_GROUPS_PATH = "api/metalakes/%s/groups/%s"; private static final String API_METALAKES_ROLES_PATH = "api/metalakes/%s/roles/%s"; private static final String API_METALAKES_OWNERS_PATH = "api/metalakes/%s/owners/%s"; - private static final String BLANK_PLACE_HOLDER = ""; private static final String API_METALAKES_TAGS_PATH = "api/metalakes/%s/tags"; + private static final String BLANK_PLACEHOLDER = ""; private final RESTClient restClient; @@ -464,7 +464,7 @@ public User addUser(String user) throws UserAlreadyExistsException, NoSuchMetala UserResponse resp = restClient.post( - String.format(API_METALAKES_USERS_PATH, this.name(), BLANK_PLACE_HOLDER), + String.format(API_METALAKES_USERS_PATH, this.name(), BLANK_PLACEHOLDER), req, UserResponse.class, Collections.emptyMap(), @@ -528,7 +528,7 @@ public User[] listUsers() throws NoSuchMetalakeException { UserListResponse resp = restClient.get( - String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACE_HOLDER), + String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACEHOLDER), params, UserListResponse.class, Collections.emptyMap(), @@ -547,7 +547,7 @@ public User[] listUsers() throws NoSuchMetalakeException { public String[] listUserNames() throws NoSuchMetalakeException { NameListResponse resp = restClient.get( - String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACE_HOLDER), + String.format(API_METALAKES_USERS_PATH, name(), BLANK_PLACEHOLDER), NameListResponse.class, Collections.emptyMap(), ErrorHandlers.userErrorHandler()); @@ -571,7 +571,7 @@ public Group addGroup(String group) throws GroupAlreadyExistsException, NoSuchMe GroupResponse resp = restClient.post( - String.format(API_METALAKES_GROUPS_PATH, this.name(), BLANK_PLACE_HOLDER), + String.format(API_METALAKES_GROUPS_PATH, this.name(), BLANK_PLACEHOLDER), req, GroupResponse.class, Collections.emptyMap(), @@ -691,7 +691,7 @@ public Role createRole( RoleResponse resp = restClient.post( - String.format(API_METALAKES_ROLES_PATH, this.name(), BLANK_PLACE_HOLDER), + String.format(API_METALAKES_ROLES_PATH, this.name(), BLANK_PLACEHOLDER), req, RoleResponse.class, Collections.emptyMap(), @@ -701,6 +701,24 @@ public Role createRole( return resp.getRole(); } + /** + * Lists the role names. + * + * @return The role name list. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + */ + public String[] listRoleNames() { + NameListResponse resp = + restClient.get( + String.format(API_METALAKES_ROLES_PATH, this.name(), BLANK_PLACEHOLDER), + NameListResponse.class, + Collections.emptyMap(), + ErrorHandlers.roleErrorHandler()); + resp.validate(); + + return resp.getNames(); + } + /** * Grant roles to a user. * diff --git a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRole.java b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRole.java index 370f1941d8b..3d0771fc5f0 100644 --- a/clients/client-java/src/test/java/org/apache/gravitino/client/TestRole.java +++ b/clients/client-java/src/test/java/org/apache/gravitino/client/TestRole.java @@ -40,6 +40,7 @@ import org.apache.gravitino.dto.responses.DeleteResponse; import org.apache.gravitino.dto.responses.ErrorResponse; import org.apache.gravitino.dto.responses.MetalakeResponse; +import org.apache.gravitino.dto.responses.NameListResponse; import org.apache.gravitino.dto.responses.RoleResponse; import org.apache.gravitino.exceptions.NoSuchMetalakeException; import org.apache.gravitino.exceptions.NoSuchRoleException; @@ -211,6 +212,29 @@ public void testDeleteRoles() throws Exception { Assertions.assertThrows(RuntimeException.class, () -> gravitinoClient.deleteRole(roleName)); } + @Test + public void testListRoleNames() throws Exception { + String rolePath = withSlash(String.format(API_METALAKES_ROLES_PATH, metalakeName, "")); + + NameListResponse listResponse = new NameListResponse(new String[] {"role1", "role2"}); + buildMockResource(Method.GET, rolePath, null, listResponse, SC_OK); + + Assertions.assertArrayEquals(new String[] {"role1", "role2"}, gravitinoClient.listRoleNames()); + + ErrorResponse errRespNoMetalake = + ErrorResponse.notFound(NoSuchMetalakeException.class.getSimpleName(), "metalake not found"); + buildMockResource(Method.GET, rolePath, null, errRespNoMetalake, SC_NOT_FOUND); + Exception ex = + Assertions.assertThrows( + NoSuchMetalakeException.class, () -> gravitinoClient.listRoleNames()); + Assertions.assertEquals("metalake not found", ex.getMessage()); + + // Test RuntimeException + ErrorResponse errResp = ErrorResponse.internalError("internal error"); + buildMockResource(Method.GET, rolePath, null, errResp, SC_SERVER_ERROR); + Assertions.assertThrows(RuntimeException.class, () -> gravitinoClient.listRoleNames()); + } + private RoleDTO mockRoleDTO(String name) { SecurableObject securableObject = SecurableObjects.ofCatalog("catalog", Lists.newArrayList(Privileges.UseCatalog.allow())); diff --git a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java index 662e2c15922..965c31fdf45 100644 --- a/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java +++ b/clients/client-java/src/test/java/org/apache/gravitino/client/integration/test/authorization/AccessControlIT.java @@ -176,6 +176,16 @@ void testManageRoles() { Assertions.assertEquals(roleName, role.name()); Assertions.assertEquals(properties, role.properties()); + assertSecurableObjects(Lists.newArrayList(metalakeObject), role.securableObjects()); + + // List roles + String anotherRoleName = "another-role"; + metalake.createRole(anotherRoleName, properties, Lists.newArrayList(metalakeObject)); + String[] roleNames = metalake.listRoleNames(); + Arrays.sort(roleNames); + + Assertions.assertEquals( + Lists.newArrayList(anotherRoleName, roleName), Arrays.asList(roleNames)); // Verify the object Assertions.assertEquals(1, role.securableObjects().size()); @@ -303,4 +313,22 @@ void testManageGroupPermissions() { metalake.removeGroup(groupName); metalake.deleteRole(roleName); } + + private static void assertSecurableObjects( + List expect, List actual) { + Assertions.assertEquals(expect.size(), actual.size()); + for (int index = 0; index < expect.size(); index++) { + Assertions.assertEquals(expect.get(index).fullName(), actual.get(index).fullName()); + Assertions.assertEquals(expect.get(index).type(), actual.get(index).type()); + List expectPrivileges = expect.get(index).privileges(); + List actualPrivileges = actual.get(index).privileges(); + Assertions.assertEquals(expectPrivileges.size(), actualPrivileges.size()); + for (int priIndex = 0; priIndex < expectPrivileges.size(); priIndex++) { + Assertions.assertEquals( + expectPrivileges.get(priIndex).name(), actualPrivileges.get(priIndex).name()); + Assertions.assertEquals( + actualPrivileges.get(priIndex).condition(), actualPrivileges.get(priIndex).condition()); + } + } + } } diff --git a/common/src/main/java/org/apache/gravitino/dto/authorization/RoleDTO.java b/common/src/main/java/org/apache/gravitino/dto/authorization/RoleDTO.java index 9ce0b0da0df..d0c51753e4d 100644 --- a/common/src/main/java/org/apache/gravitino/dto/authorization/RoleDTO.java +++ b/common/src/main/java/org/apache/gravitino/dto/authorization/RoleDTO.java @@ -158,7 +158,7 @@ public S withProperties(Map properties) { /** * Sets the securable objects of the role. * - * @param securableObjects The securableObjects of the role. + * @param securableObjects The securable objects of the role. * @return The builder instance. */ public S withSecurableObjects(SecurableObjectDTO[] securableObjects) { @@ -186,9 +186,7 @@ public S withAudit(AuditDTO audit) { public RoleDTO build() { Preconditions.checkArgument(StringUtils.isNotBlank(name), "name cannot be null or empty"); Preconditions.checkArgument(audit != null, "audit cannot be null"); - Preconditions.checkArgument( - securableObjects != null && securableObjects.length != 0, - "securable objects can't null or empty"); + Preconditions.checkArgument(securableObjects != null, "securable objects can't null"); return new RoleDTO(name, properties, securableObjects, audit); } diff --git a/common/src/main/java/org/apache/gravitino/dto/responses/RoleResponse.java b/common/src/main/java/org/apache/gravitino/dto/responses/RoleResponse.java index ca9c3960eca..d200a24c82e 100644 --- a/common/src/main/java/org/apache/gravitino/dto/responses/RoleResponse.java +++ b/common/src/main/java/org/apache/gravitino/dto/responses/RoleResponse.java @@ -65,7 +65,6 @@ public void validate() throws IllegalArgumentException { StringUtils.isNotBlank(role.name()), "role 'name' must not be null and empty"); Preconditions.checkArgument(role.auditInfo() != null, "role 'auditInfo' must not be null"); Preconditions.checkArgument( - role.securableObjects() != null && !role.securableObjects().isEmpty(), - "role 'securableObjects' can't null or empty"); + role.securableObjects() != null, "role 'securable objects' can't null"); } } diff --git a/core/src/main/java/org/apache/gravitino/EntityStore.java b/core/src/main/java/org/apache/gravitino/EntityStore.java index dcb27f022a7..d26dbf13b5a 100644 --- a/core/src/main/java/org/apache/gravitino/EntityStore.java +++ b/core/src/main/java/org/apache/gravitino/EntityStore.java @@ -20,9 +20,7 @@ import java.io.Closeable; import java.io.IOException; -import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.function.Function; import org.apache.gravitino.Entity.EntityType; import org.apache.gravitino.exceptions.NoSuchEntityException; @@ -66,7 +64,7 @@ public interface EntityStore extends Closeable { */ default List list( Namespace namespace, Class type, EntityType entityType) throws IOException { - return list(namespace, type, entityType, Collections.emptySet()); + return list(namespace, type, entityType, true /* allFields */); } /** @@ -80,14 +78,15 @@ default List list( * @param namespace the namespace of the entities * @param type the detailed type of the entity * @param entityType the general type of the entity - * @param skippingFields Some fields may have a relatively high acquisition cost, EntityStore - * provides an optional setting to avoid fetching these high-cost fields to improve the - * performance. + * @param allFields Some fields may have a relatively high acquisition cost, EntityStore provides + * an optional setting to avoid fetching these high-cost fields to improve the performance. If + * true, the method will fetch all the fields, Otherwise, the method will fetch all the fields + * except for high-cost fields. * @return the list of entities * @throws IOException if the list operation fails */ default List list( - Namespace namespace, Class type, EntityType entityType, Set skippingFields) + Namespace namespace, Class type, EntityType entityType, boolean allFields) throws IOException { throw new UnsupportedOperationException("Don't support to skip fields"); } 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 fbeebd9449e..95cb304de26 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java +++ b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java @@ -236,5 +236,14 @@ Role createRole( * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. * @throws RuntimeException If deleting the Role encounters storage issues. */ - public boolean deleteRole(String metalake, String role) throws NoSuchMetalakeException; + boolean deleteRole(String metalake, String role) throws NoSuchMetalakeException; + + /** + * Lists the role names. + * + * @param metalake The Metalake of the Role. + * @return The role name list. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + */ + String[] listRoleNames(String metalake) throws 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 222b1ffb5ae..8872afade70 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java +++ b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java @@ -143,6 +143,11 @@ public boolean deleteRole(String metalake, String role) throws NoSuchMetalakeExc return roleManager.deleteRole(metalake, role); } + @Override + public String[] listRoleNames(String metalake) throws NoSuchMetalakeException { + return roleManager.listRoleNames(metalake); + } + @VisibleForTesting RoleManager getRoleManager() { return roleManager; diff --git a/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java b/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java index 457a7f5ffe3..8b195894f4a 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java +++ b/core/src/main/java/org/apache/gravitino/authorization/RoleManager.java @@ -19,6 +19,7 @@ package org.apache.gravitino.authorization; +import com.google.common.collect.Sets; import java.io.IOException; import java.time.Instant; import java.util.List; @@ -27,14 +28,15 @@ import org.apache.gravitino.EntityAlreadyExistsException; import org.apache.gravitino.EntityStore; import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; import org.apache.gravitino.exceptions.NoSuchEntityException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; import org.apache.gravitino.exceptions.NoSuchRoleException; import org.apache.gravitino.exceptions.RoleAlreadyExistsException; import org.apache.gravitino.meta.AuditInfo; import org.apache.gravitino.meta.RoleEntity; import org.apache.gravitino.storage.IdGenerator; import org.apache.gravitino.utils.PrincipalUtils; -import org.glassfish.jersey.internal.guava.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +48,7 @@ class RoleManager { private static final Logger LOG = LoggerFactory.getLogger(RoleManager.class); + private static final String METALAKE_DOES_NOT_EXIST_MSG = "Metalake %s does not exist"; private final EntityStore store; private final IdGenerator idGenerator; @@ -129,6 +132,22 @@ boolean deleteRole(String metalake, String role) { } } + String[] listRoleNames(String metalake) { + try { + AuthorizationUtils.checkMetalakeExists(metalake); + Namespace namespace = AuthorizationUtils.ofRoleNamespace(metalake); + return store.list(namespace, RoleEntity.class, Entity.EntityType.ROLE).stream() + .map(Role::name) + .toArray(String[]::new); + } catch (NoSuchEntityException e) { + LOG.warn("Metalake {} does not exist", metalake, e); + throw new NoSuchMetalakeException(METALAKE_DOES_NOT_EXIST_MSG, metalake); + } catch (IOException ioe) { + LOG.error("Listing user under metalake {} failed due to storage issues", metalake, ioe); + throw new RuntimeException(ioe); + } + } + private RoleEntity getRoleEntity(NameIdentifier identifier) { try { return store.get(identifier, Entity.EntityType.ROLE, RoleEntity.class); diff --git a/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java b/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java index 4b7b4f2d8c3..be1b687f3e4 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java +++ b/core/src/main/java/org/apache/gravitino/authorization/UserGroupManager.java @@ -23,11 +23,9 @@ import java.time.Instant; import java.util.Arrays; import java.util.Collections; -import java.util.Set; import org.apache.gravitino.Entity; import org.apache.gravitino.EntityAlreadyExistsException; import org.apache.gravitino.EntityStore; -import org.apache.gravitino.Field; import org.apache.gravitino.Namespace; import org.apache.gravitino.exceptions.GroupAlreadyExistsException; import org.apache.gravitino.exceptions.NoSuchEntityException; @@ -40,7 +38,6 @@ import org.apache.gravitino.meta.UserEntity; import org.apache.gravitino.storage.IdGenerator; import org.apache.gravitino.utils.PrincipalUtils; -import org.glassfish.jersey.internal.guava.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -117,26 +114,23 @@ User getUser(String metalake, String user) throws NoSuchUserException { } String[] listUserNames(String metalake) { - Set skippingFields = Sets.newHashSet(); - skippingFields.add(UserEntity.ROLE_NAMES); - skippingFields.add(UserEntity.ROLE_IDS); - return Arrays.stream(listUsersInternal(metalake, skippingFields)) + return Arrays.stream(listUsersInternal(metalake, false /* allFields */)) .map(User::name) .toArray(String[]::new); } User[] listUsers(String metalake) { - return listUsersInternal(metalake, Collections.emptySet()); + return listUsersInternal(metalake, true /* allFields */); } - private User[] listUsersInternal(String metalake, Set skippingFields) { + private User[] listUsersInternal(String metalake, boolean allFields) { try { AuthorizationUtils.checkMetalakeExists(metalake); Namespace namespace = AuthorizationUtils.ofUserNamespace(metalake); return store - .list(namespace, UserEntity.class, Entity.EntityType.USER, skippingFields) + .list(namespace, UserEntity.class, Entity.EntityType.USER, allFields) .toArray(new User[0]); } catch (NoSuchEntityException e) { LOG.error("Metalake {} does not exist", metalake, e); 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 730563862e7..7882e9c8a5e 100644 --- a/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java +++ b/core/src/main/java/org/apache/gravitino/hook/AccessControlHookDispatcher.java @@ -157,4 +157,9 @@ public Role getRole(String metalake, String role) public boolean deleteRole(String metalake, String role) throws NoSuchMetalakeException { return dispatcher.deleteRole(metalake, role); } + + @Override + public String[] listRoleNames(String metalake) throws NoSuchMetalakeException { + return dispatcher.listRoleNames(metalake); + } } diff --git a/core/src/main/java/org/apache/gravitino/meta/RoleEntity.java b/core/src/main/java/org/apache/gravitino/meta/RoleEntity.java index d1b3bbfe99f..a96f6e0f5a0 100644 --- a/core/src/main/java/org/apache/gravitino/meta/RoleEntity.java +++ b/core/src/main/java/org/apache/gravitino/meta/RoleEntity.java @@ -46,8 +46,8 @@ public class RoleEntity implements Role, Entity, Auditable, HasIdentifier { public static final Field AUDIT_INFO = Field.required("audit_info", AuditInfo.class, "The audit details of the role entity."); - public static final Field SECURABLE_OBJECT = - Field.required("securable_objects", List.class, "The securable objects of the role entity."); + public static final Field SECURABLE_OBJECTS = + Field.optional("securable_objects", List.class, "The securable objects of the role entity."); private Long id; private String name; @@ -91,15 +91,6 @@ public Map properties() { */ @Override public List securableObjects() { - // The securable object is a special kind of entities. Some entity types aren't the securable - // object, such as - // User, Role, etc. - // The securable object identifier must be unique. - // Gravitino assumes that the identifiers of the entities may be the same if they have different - // types. - // So one type of them can't be the securable object at least if there are the two same - // identifier - // entities . return securableObjects; } @@ -115,7 +106,7 @@ public Map fields() { fields.put(NAME, name); fields.put(AUDIT_INFO, auditInfo); fields.put(PROPERTIES, properties); - fields.put(SECURABLE_OBJECT, securableObjects); + fields.put(SECURABLE_OBJECTS, securableObjects); return Collections.unmodifiableMap(fields); } diff --git a/core/src/main/java/org/apache/gravitino/meta/UserEntity.java b/core/src/main/java/org/apache/gravitino/meta/UserEntity.java index df47215b4b5..c71d731a99e 100644 --- a/core/src/main/java/org/apache/gravitino/meta/UserEntity.java +++ b/core/src/main/java/org/apache/gravitino/meta/UserEntity.java @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import lombok.ToString; import org.apache.gravitino.Auditable; import org.apache.gravitino.Entity; @@ -31,7 +30,6 @@ import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.Namespace; import org.apache.gravitino.authorization.User; -import org.glassfish.jersey.internal.guava.Sets; /** A class representing a user metadata entity in Apache Gravitino. */ @ToString @@ -156,22 +154,6 @@ public List roleIds() { return roleIds; } - /** - * Get the set of all the fields. - * - * @return The set of all the fields. - */ - public static Set fieldSet() { - Set fields = Sets.newHashSet(); - fields.add(ID); - fields.add(NAME); - fields.add(AUDIT_INFO); - fields.add(ROLE_IDS); - fields.add(ROLE_NAMES); - - return Collections.unmodifiableSet(fields); - } - @Override public boolean equals(Object o) { if (this == o) return true; 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 549c5fec2e4..2b9a6d0e4ed 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 @@ -26,13 +26,11 @@ import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import org.apache.gravitino.Config; import org.apache.gravitino.Configs; import org.apache.gravitino.Entity; import org.apache.gravitino.EntityAlreadyExistsException; -import org.apache.gravitino.Field; import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; @@ -90,8 +88,7 @@ public void initialize(Config config) { @Override public List list( - Namespace namespace, Entity.EntityType entityType, Set skippingFields) - throws IOException { + Namespace namespace, Entity.EntityType entityType, boolean allFields) throws IOException { switch (entityType) { case METALAKE: return (List) MetalakeMetaService.getInstance().listMetalakes(); @@ -108,8 +105,9 @@ public List list( case TAG: return (List) TagMetaService.getInstance().listTagsByNamespace(namespace); case USER: - return (List) - UserMetaService.getInstance().listUsersByNamespace(namespace, skippingFields); + return (List) UserMetaService.getInstance().listUsersByNamespace(namespace, allFields); + case ROLE: + return (List) RoleMetaService.getInstance().listRolesByNamespace(namespace); default: throw new UnsupportedEntityTypeException( "Unsupported entity type: %s for list operation", 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 fe85754b4ef..cae0c7b4d36 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 @@ -21,12 +21,10 @@ import java.io.Closeable; import java.io.IOException; import java.util.List; -import java.util.Set; import java.util.function.Function; import org.apache.gravitino.Config; import org.apache.gravitino.Entity; import org.apache.gravitino.EntityAlreadyExistsException; -import org.apache.gravitino.Field; import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; @@ -50,9 +48,10 @@ public interface RelationalBackend * * @param namespace The parent namespace of these entities. * @param entityType The type of these entities. - * @param skippingFields Some fields may have a relatively high acquisition cost, EntityStore - * provide an optional setting to avoid fetching these high-cost fields to improve the - * performance. + * @param allFields Some fields may have a relatively high acquisition cost, EntityStore provide + * an optional setting to avoid fetching these high-cost fields to improve the performance. If + * true, the method will fetch all the fields, Otherwise, the method will fetch all the fields + * except for high-cost fields. * @return The list of entities associated with the given parent namespace and entityType, or null * if the entities does not exist. * @throws NoSuchEntityException If the corresponding parent entity of these list entities cannot @@ -60,7 +59,7 @@ public interface RelationalBackend * @throws IOException If the store operation fails */ List list( - Namespace namespace, Entity.EntityType entityType, Set skippingFields) + Namespace namespace, Entity.EntityType entityType, boolean allFields) throws NoSuchEntityException, IOException; /** 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 d7403729f60..c95db1a0710 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 @@ -22,9 +22,7 @@ import com.google.common.collect.ImmutableMap; import java.io.IOException; -import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.function.Function; import org.apache.gravitino.Config; import org.apache.gravitino.Configs; @@ -32,7 +30,6 @@ import org.apache.gravitino.EntityAlreadyExistsException; import org.apache.gravitino.EntitySerDe; import org.apache.gravitino.EntityStore; -import org.apache.gravitino.Field; import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; @@ -92,14 +89,14 @@ public void setSerDe(EntitySerDe entitySerDe) { @Override public List list( Namespace namespace, Class type, Entity.EntityType entityType) throws IOException { - return backend.list(namespace, entityType, Collections.emptySet()); + return backend.list(namespace, entityType, false); } @Override public List list( - Namespace namespace, Class type, Entity.EntityType entityType, Set skippingFields) + Namespace namespace, Class type, Entity.EntityType entityType, boolean allFields) throws IOException { - return backend.list(namespace, entityType, skippingFields); + return backend.list(namespace, entityType, allFields); } @Override diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaMapper.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaMapper.java index d7478c3b7f4..6b155f49856 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaMapper.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaMapper.java @@ -65,6 +65,9 @@ List listRolesByMetadataObjectIdAndType( @Param("metadataObjectId") Long metadataObjectId, @Param("metadataObjectType") String metadataObjectType); + @SelectProvider(type = RoleMetaSQLProviderFactory.class, method = "listRolePOsByMetalake") + List listRolePOsByMetalake(@Param("metalakeName") String metalakeName); + @InsertProvider(type = RoleMetaSQLProviderFactory.class, method = "insertRoleMeta") void insertRoleMeta(@Param("roleMeta") RolePO rolePO); diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaSQLProviderFactory.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaSQLProviderFactory.java index 1566318450a..65e68975df1 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaSQLProviderFactory.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/RoleMetaSQLProviderFactory.java @@ -72,6 +72,10 @@ public static String listRolesByMetadataObjectIdAndType( return getProvider().listRolesByMetadataObjectIdAndType(metadataObjectId, type); } + public static String listRolePOsByMetalake(@Param("metalakeName") String metalakeName) { + return getProvider().listRolePOsByMetalake(metalakeName); + } + public static String insertRoleMeta(@Param("roleMeta") RolePO rolePO) { return getProvider().insertRoleMeta(rolePO); } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/RoleMetaBaseSQLProvider.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/RoleMetaBaseSQLProvider.java index 95749ab65f7..b2dc1213765 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/RoleMetaBaseSQLProvider.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/RoleMetaBaseSQLProvider.java @@ -22,6 +22,7 @@ import static org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.ROLE_TABLE_NAME; import static org.apache.gravitino.storage.relational.mapper.RoleMetaMapper.USER_ROLE_RELATION_TABLE_NAME; +import org.apache.gravitino.storage.relational.mapper.MetalakeMetaMapper; import org.apache.gravitino.storage.relational.mapper.SecurableObjectMapper; import org.apache.gravitino.storage.relational.po.RolePO; import org.apache.ibatis.annotations.Param; @@ -93,6 +94,20 @@ public String listRolesByMetadataObjectIdAndType( + " AND ro.deleted_at = 0 AND se.deleted_at = 0"; } + public String listRolePOsByMetalake(@Param("metalakeName") String metalakeName) { + return "SELECT rt.role_id as roleId, rt.role_name as roleName," + + " rt.metalake_id as metalakeId, rt.properties as properties," + + " rt.audit_info as auditInfo, rt.current_version as currentVersion," + + " rt.last_version as lastVersion, rt.deleted_at as deletedAt" + + " FROM " + + ROLE_TABLE_NAME + + " rt JOIN " + + MetalakeMetaMapper.TABLE_NAME + + " mt ON rt.metalake_id = mt.metalake_id" + + " WHERE mt.metalake_name = #{metalakeName}" + + " AND rt.deleted_at = 0 AND mt.deleted_at = 0"; + } + public String insertRoleMeta(@Param("roleMeta") RolePO rolePO) { return "INSERT INTO " + ROLE_TABLE_NAME diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java index 4560b74e0d0..1e914f59ad4 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/service/RoleMetaService.java @@ -20,11 +20,13 @@ import com.google.common.collect.Lists; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.apache.gravitino.Entity; import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.Namespace; import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.authorization.SecurableObject; import org.apache.gravitino.exceptions.NoSuchEntityException; @@ -237,6 +239,22 @@ private List listSecurableObjectsByRoleId(Long roleId) { SecurableObjectMapper.class, mapper -> mapper.listSecurableObjectsByRoleId(roleId)); } + public List listRolesByNamespace(Namespace namespace) { + AuthorizationUtils.checkRoleNamespace(namespace); + String metalakeName = namespace.level(0); + + List rolePOs = + SessionUtils.getWithoutCommit( + RoleMetaMapper.class, mapper -> mapper.listRolePOsByMetalake(metalakeName)); + + return rolePOs.stream() + .map( + po -> + POConverters.fromRolePO( + po, Collections.emptyList(), AuthorizationUtils.ofRoleNamespace(metalakeName))) + .collect(Collectors.toList()); + } + public int deleteRoleMetasByLegacyTimeline(long legacyTimeline, int limit) { int[] roleDeletedCount = new int[] {0}; int[] userRoleRelDeletedCount = new int[] {0}; diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFields.java b/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFields.java deleted file mode 100644 index 42978fa5013..00000000000 --- a/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFields.java +++ /dev/null @@ -1,40 +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.storage.relational.service; - -import java.util.Set; -import org.apache.gravitino.Field; - -/** The handler supports to skip fields to acquire part desired fields. */ -interface SupportsDesiredFields { - - /** - * The fields which could be desired. - * - * @return The fields which are desired. - */ - Set desiredFields(); - - /** - * The return value of the handler. - * - * @return The return value of the handler. - */ - R execute(); -} diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFieldsHandlers.java b/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFieldsHandlers.java deleted file mode 100644 index b5b10a7b6aa..00000000000 --- a/core/src/main/java/org/apache/gravitino/storage/relational/service/SupportsDesiredFieldsHandlers.java +++ /dev/null @@ -1,50 +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.storage.relational.service; - -import com.google.common.collect.Lists; -import java.util.List; -import java.util.Set; -import org.apache.gravitino.Field; - -/** - * This class is the collection wrapper of SupportsDesiredFields handler. The class will contain all - * the handlers can proceed the data. We can choose different handlers according to the desired - * fields to acquire better performance. - * - * @param The value type which the handler will return. - */ -class SupportsDesiredFieldsHandlers { - private final List> methods = Lists.newArrayList(); - - // We should put the low-cost handler into the front of the list. - void addHandler(SupportsDesiredFields supportsSkippingFields) { - methods.add(supportsSkippingFields); - } - - T execute(Set desiredFields) { - for (SupportsDesiredFields method : methods) { - if (method.desiredFields().containsAll(desiredFields)) { - return method.execute(); - } - } - - throw new IllegalArgumentException("Don't support skip fields"); - } -} 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 f64b4ab405a..a8991da14b1 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 @@ -30,7 +30,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import org.apache.gravitino.Entity; -import org.apache.gravitino.Field; import org.apache.gravitino.HasIdentifier; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; @@ -249,18 +248,34 @@ public UserEntity updateUser( return newEntity; } - public List listUsersByNamespace(Namespace namespace, Set skippingFields) { + public List listUsersByNamespace(Namespace namespace, boolean allFields) { AuthorizationUtils.checkUserNamespace(namespace); String metalakeName = namespace.level(0); - SupportsDesiredFieldsHandlers> handlers = - new SupportsDesiredFieldsHandlers<>(); - handlers.addHandler(new ListDesiredRolesHandler(metalakeName)); - handlers.addHandler(new ListAllFieldsHandler(metalakeName)); - - Set desiredFields = Sets.newHashSet(UserEntity.fieldSet()); - desiredFields.removeAll(skippingFields); - return handlers.execute(desiredFields); + if (allFields) { + Long metalakeId = MetalakeMetaService.getInstance().getMetalakeIdByName(metalakeName); + List userPOs = + SessionUtils.getWithoutCommit( + UserMetaMapper.class, mapper -> mapper.listExtendedUserPOsByMetalakeId(metalakeId)); + return userPOs.stream() + .map( + po -> + POConverters.fromExtendedUserPO( + po, AuthorizationUtils.ofUserNamespace(metalakeName))) + .collect(Collectors.toList()); + } else { + List userPOs = + SessionUtils.getWithoutCommit( + UserMetaMapper.class, mapper -> mapper.listUserPOsByMetalake(metalakeName)); + return userPOs.stream() + .map( + po -> + POConverters.fromUserPO( + po, + Collections.emptyList(), + AuthorizationUtils.ofUserNamespace(metalakeName))) + .collect(Collectors.toList()); + } } public int deleteUserMetasByLegacyTimeline(long legacyTimeline, int limit) { @@ -282,63 +297,4 @@ public int deleteUserMetasByLegacyTimeline(long legacyTimeline, int limit) { return userDeletedCount[0] + userRoleRelDeletedCount[0]; } - - private static class ListDesiredRolesHandler implements SupportsDesiredFields> { - private final String metalakeName; - - ListDesiredRolesHandler(String metalakeName) { - this.metalakeName = metalakeName; - } - - @Override - public Set desiredFields() { - Set requiredFields = Sets.newHashSet(UserEntity.fieldSet()); - requiredFields.remove(UserEntity.ROLE_IDS); - requiredFields.remove(UserEntity.ROLE_NAMES); - - return requiredFields; - } - - @Override - public List execute() { - List userPOs = - SessionUtils.getWithoutCommit( - UserMetaMapper.class, mapper -> mapper.listUserPOsByMetalake(metalakeName)); - return userPOs.stream() - .map( - po -> - POConverters.fromUserPO( - po, - Collections.emptyList(), - AuthorizationUtils.ofUserNamespace(metalakeName))) - .collect(Collectors.toList()); - } - } - - private static class ListAllFieldsHandler implements SupportsDesiredFields> { - final String metalakeName; - - ListAllFieldsHandler(String metalakeName) { - this.metalakeName = metalakeName; - } - - @Override - public Set desiredFields() { - return UserEntity.fieldSet(); - } - - @Override - public List execute() { - Long metalakeId = MetalakeMetaService.getInstance().getMetalakeIdByName(metalakeName); - List userPOs = - SessionUtils.getWithoutCommit( - UserMetaMapper.class, mapper -> mapper.listExtendedUserPOsByMetalakeId(metalakeId)); - return userPOs.stream() - .map( - po -> - POConverters.fromExtendedUserPO( - po, AuthorizationUtils.ofUserNamespace(metalakeName))) - .collect(Collectors.toList()); - } - } } 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 1c7a26dec53..6dfaf54fecf 100644 --- a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java +++ b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java @@ -397,6 +397,32 @@ public void testDropRole() { Assertions.assertFalse(dropped1); } + @Test + public void testListRoles() { + Map props = ImmutableMap.of("k1", "v1"); + + accessControlManager.createRole( + "metalake_list", + "testList1", + props, + Lists.newArrayList( + SecurableObjects.ofCatalog( + "catalog", Lists.newArrayList(Privileges.UseCatalog.allow())))); + + accessControlManager.createRole( + "metalake_list", + "testList2", + props, + Lists.newArrayList( + SecurableObjects.ofCatalog( + "catalog", Lists.newArrayList(Privileges.UseCatalog.allow())))); + + // Test to list roles + String[] actualRoles = accessControlManager.listRoleNames("metalake_list"); + Arrays.sort(actualRoles); + Assertions.assertArrayEquals(new String[] {"testList1", "testList2"}, actualRoles); + } + private void testProperties(Map expectedProps, Map testProps) { expectedProps.forEach( (k, v) -> { diff --git a/core/src/test/java/org/apache/gravitino/meta/TestEntity.java b/core/src/test/java/org/apache/gravitino/meta/TestEntity.java index f4c32dca718..f657f01a93c 100644 --- a/core/src/test/java/org/apache/gravitino/meta/TestEntity.java +++ b/core/src/test/java/org/apache/gravitino/meta/TestEntity.java @@ -296,7 +296,7 @@ public void testRole() { Lists.newArrayList( SecurableObjects.ofCatalog( catalogName, Lists.newArrayList(Privileges.UseCatalog.allow()))), - fields.get(RoleEntity.SECURABLE_OBJECT)); + fields.get(RoleEntity.SECURABLE_OBJECTS)); RoleEntity roleWithoutFields = RoleEntity.builder() diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java index b141eb6963b..c67c9697a12 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/TestJDBCBackend.java @@ -45,7 +45,6 @@ import java.sql.Statement; import java.time.Instant; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -715,29 +714,26 @@ public void testMetaLifeCycleFromCreationToDeletion() throws IOException { // meta data list List metaLakes = - backend.list(metalake.namespace(), Entity.EntityType.METALAKE, Collections.emptySet()); + backend.list(metalake.namespace(), Entity.EntityType.METALAKE, true); assertTrue(metaLakes.contains(metalake)); List catalogs = - backend.list(catalog.namespace(), Entity.EntityType.CATALOG, Collections.emptySet()); + backend.list(catalog.namespace(), Entity.EntityType.CATALOG, true); assertTrue(catalogs.contains(catalog)); - List schemas = - backend.list(schema.namespace(), Entity.EntityType.SCHEMA, Collections.emptySet()); + List schemas = backend.list(schema.namespace(), Entity.EntityType.SCHEMA, true); assertTrue(schemas.contains(schema)); - List tables = - backend.list(table.namespace(), Entity.EntityType.TABLE, Collections.emptySet()); + List tables = backend.list(table.namespace(), Entity.EntityType.TABLE, true); assertTrue(tables.contains(table)); List filesets = - backend.list(fileset.namespace(), Entity.EntityType.FILESET, Collections.emptySet()); + backend.list(fileset.namespace(), Entity.EntityType.FILESET, true); assertFalse(filesets.contains(fileset)); assertTrue(filesets.contains(filesetV2)); assertEquals("2", filesets.get(filesets.indexOf(filesetV2)).properties().get("version")); - List topics = - backend.list(topic.namespace(), Entity.EntityType.TOPIC, Collections.emptySet()); + List topics = backend.list(topic.namespace(), Entity.EntityType.TOPIC, true); assertTrue(topics.contains(topic)); RoleEntity roleEntity = backend.get(role.nameIdentifier(), Entity.EntityType.ROLE); @@ -763,8 +759,7 @@ public void testMetaLifeCycleFromCreationToDeletion() throws IOException { TagEntity tagEntity = backend.get(tag.nameIdentifier(), Entity.EntityType.TAG); assertEquals(tag, tagEntity); - List tags = - backend.list(tag.namespace(), Entity.EntityType.TAG, Collections.emptySet()); + List tags = backend.list(tag.namespace(), Entity.EntityType.TAG, true); assertTrue(tags.contains(tag)); assertEquals(1, tags.size()); diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java index d811b8b598f..4a781f01861 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestRoleMetaService.java @@ -106,6 +106,55 @@ void getRoleByIdentifier() throws IOException { Assertions.assertEquals(role1, roleMetaService.getRoleByIdentifier(role1.nameIdentifier())); } + @Test + void testListRoles() throws IOException { + AuditInfo auditInfo = + AuditInfo.builder().withCreator("creator").withCreateTime(Instant.now()).build(); + BaseMetalake metalake = + createBaseMakeLake(RandomIdGenerator.INSTANCE.nextId(), metalakeName, auditInfo); + backend.insert(metalake, false); + + CatalogEntity catalog = + createCatalog( + RandomIdGenerator.INSTANCE.nextId(), Namespace.of("metalake"), "catalog", auditInfo); + backend.insert(catalog, false); + + RoleEntity role1 = + createRoleEntity( + RandomIdGenerator.INSTANCE.nextId(), + AuthorizationUtils.ofRoleNamespace(metalakeName), + "role1", + auditInfo, + SecurableObjects.ofCatalog( + "catalog", Lists.newArrayList(Privileges.UseCatalog.allow())), + ImmutableMap.of("k1", "v1")); + + RoleEntity role2 = + createRoleEntity( + RandomIdGenerator.INSTANCE.nextId(), + AuthorizationUtils.ofRoleNamespace(metalakeName), + "role2", + auditInfo, + SecurableObjects.ofCatalog( + "catalog", Lists.newArrayList(Privileges.UseCatalog.allow())), + ImmutableMap.of("k1", "v1")); + + backend.insert(role1, false); + backend.insert(role2, false); + + RoleMetaService roleMetaService = RoleMetaService.getInstance(); + List actualRoles = + roleMetaService.listRolesByNamespace(AuthorizationUtils.ofRoleNamespace(metalakeName)); + actualRoles.sort(Comparator.comparing(RoleEntity::name)); + List expectRoles = Lists.newArrayList(role1, role2); + Assertions.assertEquals(expectRoles.size(), actualRoles.size()); + for (int index = 0; index < expectRoles.size(); index++) { + RoleEntity expectRole = expectRoles.get(index); + RoleEntity actualRole = actualRoles.get(index); + Assertions.assertEquals(expectRole.name(), actualRole.name()); + } + } + @Test void insertRole() throws IOException { AuditInfo auditInfo = diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestUserMetaService.java b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestUserMetaService.java index 0d037317d90..0efd886ee4d 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestUserMetaService.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestUserMetaService.java @@ -27,7 +27,6 @@ import java.sql.SQLException; import java.sql.Statement; import java.time.Instant; -import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; @@ -173,7 +172,7 @@ void testListUsers() throws IOException { UserMetaService userMetaService = UserMetaService.getInstance(); List actualUsers = userMetaService.listUsersByNamespace( - AuthorizationUtils.ofUserNamespace(metalakeName), Collections.emptySet()); + AuthorizationUtils.ofUserNamespace(metalakeName), true); actualUsers.sort(Comparator.comparing(UserEntity::name)); List expectUsers = Lists.newArrayList(user1, user2); Assertions.assertEquals(expectUsers.size(), actualUsers.size()); diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/RoleOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/RoleOperations.java index 1fa8a443f81..b006471e383 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/RoleOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/RoleOperations.java @@ -44,6 +44,7 @@ import org.apache.gravitino.dto.authorization.SecurableObjectDTO; import org.apache.gravitino.dto.requests.RoleCreateRequest; import org.apache.gravitino.dto.responses.DeleteResponse; +import org.apache.gravitino.dto.responses.NameListResponse; import org.apache.gravitino.dto.responses.RoleResponse; import org.apache.gravitino.dto.util.DTOConverters; import org.apache.gravitino.exceptions.NoSuchMetadataObjectException; @@ -68,6 +69,27 @@ public RoleOperations() { this.accessControlManager = GravitinoEnv.getInstance().accessControlDispatcher(); } + @GET + @Produces("application/vnd.gravitino.v1+json") + @Timed(name = "list-role." + MetricNames.HTTP_PROCESS_DURATION, absolute = true) + @ResponseMetered(name = "list-role", absolute = true) + public Response listRoles(@PathParam("metalake") String metalake) { + try { + return Utils.doAs( + httpRequest, + () -> + TreeLockUtils.doWithTreeLock( + NameIdentifier.of(metalake), + LockType.READ, + () -> { + String[] names = accessControlManager.listRoleNames(metalake); + return Utils.ok(new NameListResponse(names)); + })); + } catch (Exception e) { + return ExceptionHandlers.handleRoleException(OperationType.LIST, "", metalake, e); + } + } + @GET @Path("{role}") @Produces("application/vnd.gravitino.v1+json") diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java index a00f91b4d03..eb365d1ac69 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestRoleOperations.java @@ -54,6 +54,7 @@ import org.apache.gravitino.dto.responses.DeleteResponse; import org.apache.gravitino.dto.responses.ErrorConstants; import org.apache.gravitino.dto.responses.ErrorResponse; +import org.apache.gravitino.dto.responses.NameListResponse; import org.apache.gravitino.dto.responses.RoleResponse; import org.apache.gravitino.dto.util.DTOConverters; import org.apache.gravitino.exceptions.NoSuchMetadataObjectException; @@ -454,4 +455,51 @@ public void testCheckSecurableObjects() { NoSuchMetadataObjectException.class, () -> RoleOperations.checkSecurableObject("metalake", DTOConverters.toDTO(fileset))); } + + @Test + public void testListRoleNames() { + when(manager.listRoleNames(any())).thenReturn(new String[] {"role"}); + + Response resp = + target("/metalakes/metalake1/roles/") + .request(MediaType.APPLICATION_JSON_TYPE) + .accept("application/vnd.gravitino.v1+json") + .get(); + Assertions.assertEquals(Response.Status.OK.getStatusCode(), resp.getStatus()); + + NameListResponse listResponse = resp.readEntity(NameListResponse.class); + Assertions.assertEquals(0, listResponse.getCode()); + + Assertions.assertEquals(1, listResponse.getNames().length); + Assertions.assertEquals("role", listResponse.getNames()[0]); + + // Test to throw NoSuchMetalakeException + doThrow(new NoSuchMetalakeException("mock error")).when(manager).listRoleNames(any()); + Response resp1 = + target("/metalakes/metalake1/roles/") + .request(MediaType.APPLICATION_JSON_TYPE) + .accept("application/vnd.gravitino.v1+json") + .get(); + + Assertions.assertEquals(Response.Status.NOT_FOUND.getStatusCode(), resp1.getStatus()); + + ErrorResponse errorResponse = resp1.readEntity(ErrorResponse.class); + Assertions.assertEquals(ErrorConstants.NOT_FOUND_CODE, errorResponse.getCode()); + Assertions.assertEquals(NoSuchMetalakeException.class.getSimpleName(), errorResponse.getType()); + + // Test to throw internal RuntimeException + doThrow(new RuntimeException("mock error")).when(manager).listRoleNames(any()); + Response resp3 = + target("/metalakes/metalake1/roles") + .request(MediaType.APPLICATION_JSON_TYPE) + .accept("application/vnd.gravitino.v1+json") + .get(); + + Assertions.assertEquals( + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), resp3.getStatus()); + + ErrorResponse errorResponse2 = resp3.readEntity(ErrorResponse.class); + Assertions.assertEquals(ErrorConstants.INTERNAL_ERROR_CODE, errorResponse2.getCode()); + Assertions.assertEquals(RuntimeException.class.getSimpleName(), errorResponse2.getType()); + } }