From 482a723316b1286f7076a71f5b7e55eccfcac663 Mon Sep 17 00:00:00 2001 From: Rory Date: Tue, 23 Jul 2024 15:56:35 +0800 Subject: [PATCH] [#4236] feat(core): Supports the post-hook for the managers or dispatcher --- .../org/apache/gravitino/GravitinoEnv.java | 53 ++++- .../AccessControlDispatcher.java | 222 ++++++++++++++++++ .../authorization/AccessControlManager.java | 151 ++---------- .../authorization/AuthorizationUtils.java | 68 ++++++ .../lifecycle/LifecycleHookHelper.java | 35 +++ .../lifecycle/LifecycleHookProxy.java | 44 ++++ .../gravitino/lifecycle/LifecycleHooks.java | 52 ++++ .../TestAccessControlManager.java | 2 +- ...estAccessControlManagerForPermissions.java | 2 +- .../lifecycle/TestLifecycleHooks.java | 79 +++++++ .../server/web/rest/GroupOperations.java | 6 +- .../server/web/rest/PermissionOperations.java | 6 +- .../server/web/rest/RoleOperations.java | 6 +- .../server/web/rest/UserOperations.java | 6 +- .../server/web/rest/TestGroupOperations.java | 2 +- .../web/rest/TestPermissionOperations.java | 2 +- .../server/web/rest/TestRoleOperations.java | 2 +- .../server/web/rest/TestUserOperations.java | 2 +- 18 files changed, 573 insertions(+), 167 deletions(-) create mode 100644 core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java create mode 100644 core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookHelper.java create mode 100644 core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookProxy.java create mode 100644 core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHooks.java create mode 100644 core/src/test/java/org/apache/gravitino/lifecycle/TestLifecycleHooks.java diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java index aa267f2259a..3c459d0c75c 100644 --- a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java +++ b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java @@ -19,7 +19,9 @@ package org.apache.gravitino; import com.google.common.base.Preconditions; +import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.auxiliary.AuxiliaryServiceManager; import org.apache.gravitino.catalog.CatalogDispatcher; import org.apache.gravitino.catalog.CatalogManager; @@ -39,6 +41,8 @@ import org.apache.gravitino.catalog.TopicDispatcher; import org.apache.gravitino.catalog.TopicNormalizeDispatcher; import org.apache.gravitino.catalog.TopicOperationDispatcher; +import org.apache.gravitino.lifecycle.LifecycleHookHelper; +import org.apache.gravitino.lifecycle.LifecycleHooks; import org.apache.gravitino.listener.CatalogEventDispatcher; import org.apache.gravitino.listener.EventBus; import org.apache.gravitino.listener.EventListenerManager; @@ -87,7 +91,7 @@ public class GravitinoEnv { private MetalakeDispatcher metalakeDispatcher; - private AccessControlManager accessControlManager; + private AccessControlDispatcher accessControlDispatcher; private IdGenerator idGenerator; @@ -244,8 +248,8 @@ public LockManager getLockManager() { * * @return The AccessControlManager instance. */ - public AccessControlManager accessControlManager() { - return accessControlManager; + public AccessControlDispatcher accessControlDispatcher() { + return accessControlDispatcher; } /** @@ -317,27 +321,29 @@ private void initGravitinoServerComponents() { this.idGenerator = new RandomIdGenerator(); // Create and initialize metalake related modules - MetalakeManager metalakeManager = new MetalakeManager(entityStore, idGenerator); + MetalakeDispatcher metalakeManager = new MetalakeManager(entityStore, idGenerator); MetalakeNormalizeDispatcher metalakeNormalizeDispatcher = - new MetalakeNormalizeDispatcher(metalakeManager); + new MetalakeNormalizeDispatcher(installLifecycleHooks(metalakeManager)); this.metalakeDispatcher = new MetalakeEventDispatcher(eventBus, metalakeNormalizeDispatcher); // Create and initialize Catalog related modules this.catalogManager = new CatalogManager(config, entityStore, idGenerator); CatalogNormalizeDispatcher catalogNormalizeDispatcher = - new CatalogNormalizeDispatcher(catalogManager); + new CatalogNormalizeDispatcher(installLifecycleHooks((CatalogDispatcher) catalogManager)); this.catalogDispatcher = new CatalogEventDispatcher(eventBus, catalogNormalizeDispatcher); SchemaOperationDispatcher schemaOperationDispatcher = new SchemaOperationDispatcher(catalogManager, entityStore, idGenerator); SchemaNormalizeDispatcher schemaNormalizeDispatcher = - new SchemaNormalizeDispatcher(schemaOperationDispatcher, catalogManager); + new SchemaNormalizeDispatcher( + installLifecycleHooks((SchemaDispatcher) schemaOperationDispatcher), catalogManager); this.schemaDispatcher = new SchemaEventDispatcher(eventBus, schemaNormalizeDispatcher); TableOperationDispatcher tableOperationDispatcher = new TableOperationDispatcher(catalogManager, entityStore, idGenerator); TableNormalizeDispatcher tableNormalizeDispatcher = - new TableNormalizeDispatcher(tableOperationDispatcher, catalogManager); + new TableNormalizeDispatcher( + installLifecycleHooks((TableDispatcher) tableOperationDispatcher), catalogManager); this.tableDispatcher = new TableEventDispatcher(eventBus, tableNormalizeDispatcher); PartitionOperationDispatcher partitionOperationDispatcher = @@ -349,21 +355,25 @@ private void initGravitinoServerComponents() { FilesetOperationDispatcher filesetOperationDispatcher = new FilesetOperationDispatcher(catalogManager, entityStore, idGenerator); FilesetNormalizeDispatcher filesetNormalizeDispatcher = - new FilesetNormalizeDispatcher(filesetOperationDispatcher, catalogManager); + new FilesetNormalizeDispatcher( + installLifecycleHooks((FilesetDispatcher) filesetOperationDispatcher), catalogManager); this.filesetDispatcher = new FilesetEventDispatcher(eventBus, filesetNormalizeDispatcher); TopicOperationDispatcher topicOperationDispatcher = new TopicOperationDispatcher(catalogManager, entityStore, idGenerator); TopicNormalizeDispatcher topicNormalizeDispatcher = - new TopicNormalizeDispatcher(topicOperationDispatcher, catalogManager); + new TopicNormalizeDispatcher( + installLifecycleHooks((TopicDispatcher) topicOperationDispatcher), catalogManager); this.topicDispatcher = new TopicEventDispatcher(eventBus, topicNormalizeDispatcher); // Create and initialize access control related modules boolean enableAuthorization = config.get(Configs.ENABLE_AUTHORIZATION); if (enableAuthorization) { - this.accessControlManager = new AccessControlManager(entityStore, idGenerator, config); + this.accessControlDispatcher = + installLifecycleHooks( + (AccessControlDispatcher) new AccessControlManager(entityStore, idGenerator, config)); } else { - this.accessControlManager = null; + this.accessControlDispatcher = null; } this.auxServiceManager = new AuxiliaryServiceManager(); @@ -375,4 +385,23 @@ private void initGravitinoServerComponents() { // Tag manager this.tagManager = new TagManager(idGenerator, entityStore); } + + // Provides a universal entrance to install lifecycle hooks. This method + // focuses the logic of installing hooks. + // We should reuse the ability of *NormalizeDispatcher to avoid solving + // normalization names, this is useful for pre-hooks. + // so we can't install the hooks for the outside of *NormalizeDispatcher. + private T installLifecycleHooks(T manager) { + boolean enableAuthorization = config.get(Configs.ENABLE_AUTHORIZATION); + LifecycleHooks hooks = new LifecycleHooks(); + if (enableAuthorization) { + AuthorizationUtils.prepareAuthorizationHooks(manager, hooks); + } + + if (hooks.isEmpty()) { + return manager; + } + + return LifecycleHookHelper.installHooks(manager, hooks); + } } diff --git a/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java new file mode 100644 index 00000000000..fabc8acaaf3 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/authorization/AccessControlDispatcher.java @@ -0,0 +1,222 @@ +/* + * 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.List; +import java.util.Map; +import org.apache.gravitino.exceptions.GroupAlreadyExistsException; +import org.apache.gravitino.exceptions.NoSuchGroupException; +import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.exceptions.NoSuchRoleException; +import org.apache.gravitino.exceptions.NoSuchUserException; +import org.apache.gravitino.exceptions.RoleAlreadyExistsException; +import org.apache.gravitino.exceptions.UserAlreadyExistsException; + +/** + * This interface is related to the access control. This interface is mainly used for + * LifecycleHooks. The lifecycleHooks used the InvocationHandler. The InvocationHandler can only + * hook the interfaces. + */ +public interface AccessControlDispatcher { + /** + * Adds a new User. + * + * @param metalake The Metalake of the User. + * @param user The name of the User. + * @return The added User instance. + * @throws UserAlreadyExistsException If a User with the same name already exists. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If adding the User encounters storage issues. + */ + User addUser(String metalake, String user) + throws UserAlreadyExistsException, NoSuchMetalakeException; + + /** + * Removes a User. + * + * @param metalake The Metalake of the User. + * @param user The name 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 removeUser(String metalake, String user) throws NoSuchMetalakeException; + + /** + * Gets a User. + * + * @param metalake The Metalake of the User. + * @param user The name of the User. + * @return The getting User instance. + * @throws NoSuchUserException If the User with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If getting the User encounters storage issues. + */ + User getUser(String metalake, String user) throws NoSuchUserException, NoSuchMetalakeException; + + /** + * Adds a new Group. + * + * @param metalake The Metalake of the Group. + * @param group The name of the Group. + * @return The Added Group instance. + * @throws GroupAlreadyExistsException If a Group with the same name already exists. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If adding the Group encounters storage issues. + */ + Group addGroup(String metalake, String group) + throws GroupAlreadyExistsException, NoSuchMetalakeException; + + /** + * Removes a Group. + * + * @param metalake The Metalake of the Group. + * @param group THe name of the Group. + * @return True if the Group was successfully removed, false only when there's no such group, + * otherwise it will throw an exception. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If removing the Group encounters storage issues. + */ + boolean removeGroup(String metalake, String group) throws NoSuchMetalakeException; + + /** + * Gets a Group. + * + * @param metalake The Metalake of the Group. + * @param group The name of the Group. + * @return The getting Group instance. + * @throws NoSuchGroupException If the Group with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If getting the Group encounters storage issues. + */ + Group getGroup(String metalake, String group) + throws NoSuchGroupException, NoSuchMetalakeException; + + /** + * Grant roles to a user. + * + * @param metalake The metalake of the User. + * @param user The name of the User. + * @param roles The names of the Role. + * @return The User after granted. + * @throws NoSuchUserException If the User with the given name does not exist. + * @throws NoSuchRoleException If the Role with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If granting roles to a user encounters storage issues. + */ + User grantRolesToUser(String metalake, List roles, String user) + throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException; + + /** + * Grant roles to a group. + * + * @param metalake The metalake of the Group. + * @param group The name of the Group. + * @param roles The names of the Role. + * @return The Group after granted. + * @throws NoSuchGroupException If the Group with the given name does not exist. + * @throws NoSuchRoleException If the Role with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If granting roles to a group encounters storage issues. + */ + Group grantRolesToGroup(String metalake, List roles, String group) + throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException; + + /** + * Revoke roles from a group. + * + * @param metalake The metalake of the Group. + * @param group The name of the Group. + * @param roles The name of the Role. + * @return The Group after revoked. + * @throws NoSuchGroupException If the Group with the given name does not exist. + * @throws NoSuchRoleException If the Role with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If revoking roles from a group encounters storage issues. + */ + Group revokeRolesFromGroup(String metalake, List roles, String group) + throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException; + + /** + * Revoke roles from a user. + * + * @param metalake The metalake of the User. + * @param user The name of the User. + * @param roles The name of the Role. + * @return The User after revoked. + * @throws NoSuchUserException If the User with the given name does not exist. + * @throws NoSuchRoleException If the Role with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If revoking roles from a user encounters storage issues. + */ + User revokeRolesFromUser(String metalake, List roles, String user) + throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException; + + /** + * Judges whether the user is the service admin. + * + * @param user the name of the user + * @return True if the user is service admin, otherwise false. + */ + boolean isServiceAdmin(String user); + + /** + * Creates a new Role. + * + * @param metalake The Metalake of the Role. + * @param role The name of the Role. + * @param properties The properties of the Role. + * @param securableObjects The securable objects of the Role. + * @return The created Role instance. + * @throws RoleAlreadyExistsException If a Role with the same name already exists. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If creating the Role encounters storage issues. + */ + Role createRole( + String metalake, + String role, + Map properties, + List securableObjects) + throws RoleAlreadyExistsException, NoSuchMetalakeException; + + /** + * Gets a Role. + * + * @param metalake The Metalake of the Role. + * @param role The name of the Role. + * @return The getting Role instance. + * @throws NoSuchRoleException If the Role with the given name does not exist. + * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. + * @throws RuntimeException If getting the Role encounters storage issues. + */ + Role getRole(String metalake, String role) throws NoSuchRoleException, NoSuchMetalakeException; + + /** + * Deletes a Role. + * + * @param metalake The Metalake of the Role. + * @param role The name of the Role. + * @return True if the Role was successfully deleted, false only when there's no such role, + * otherwise it will throw an exception. + * @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; +} 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 69ca26bb71a..8c6a73346d0 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java +++ b/core/src/main/java/org/apache/gravitino/authorization/AccessControlManager.java @@ -37,7 +37,7 @@ * AccessControlManager is used for manage users, roles, grant information, this class is an * entrance class for tenant management. The operations will be protected by one lock. */ -public class AccessControlManager { +public class AccessControlManager implements AccessControlDispatcher { private final UserGroupManager userGroupManager; private final RoleManager roleManager; @@ -51,184 +51,70 @@ public AccessControlManager(EntityStore store, IdGenerator idGenerator, Config c this.serviceAdmins = config.get(Configs.SERVICE_ADMINS); } - /** - * Adds a new User. - * - * @param metalake The Metalake of the User. - * @param user The name of the User. - * @return The added User instance. - * @throws UserAlreadyExistsException If a User with the same name already exists. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If adding the User encounters storage issues. - */ + @Override public User addUser(String metalake, String user) throws UserAlreadyExistsException, NoSuchMetalakeException { return userGroupManager.addUser(metalake, user); } - /** - * Removes a User. - * - * @param metalake The Metalake of the User. - * @param user The name 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. - */ + @Override public boolean removeUser(String metalake, String user) throws NoSuchMetalakeException { return userGroupManager.removeUser(metalake, user); } - /** - * Gets a User. - * - * @param metalake The Metalake of the User. - * @param user The name of the User. - * @return The getting User instance. - * @throws NoSuchUserException If the User with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If getting the User encounters storage issues. - */ + @Override public User getUser(String metalake, String user) throws NoSuchUserException, NoSuchMetalakeException { return userGroupManager.getUser(metalake, user); } - /** - * Adds a new Group. - * - * @param metalake The Metalake of the Group. - * @param group The name of the Group. - * @return The Added Group instance. - * @throws GroupAlreadyExistsException If a Group with the same name already exists. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If adding the Group encounters storage issues. - */ + @Override public Group addGroup(String metalake, String group) throws GroupAlreadyExistsException, NoSuchMetalakeException { return userGroupManager.addGroup(metalake, group); } - /** - * Removes a Group. - * - * @param metalake The Metalake of the Group. - * @param group THe name of the Group. - * @return True if the Group was successfully removed, false only when there's no such group, - * otherwise it will throw an exception. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If removing the Group encounters storage issues. - */ + @Override public boolean removeGroup(String metalake, String group) throws NoSuchMetalakeException { return userGroupManager.removeGroup(metalake, group); } - /** - * Gets a Group. - * - * @param metalake The Metalake of the Group. - * @param group The name of the Group. - * @return The getting Group instance. - * @throws NoSuchGroupException If the Group with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If getting the Group encounters storage issues. - */ + @Override public Group getGroup(String metalake, String group) throws NoSuchGroupException, NoSuchMetalakeException { return userGroupManager.getGroup(metalake, group); } - /** - * Grant roles to a user. - * - * @param metalake The metalake of the User. - * @param user The name of the User. - * @param roles The names of the Role. - * @return The User after granted. - * @throws NoSuchUserException If the User with the given name does not exist. - * @throws NoSuchRoleException If the Role with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If granting roles to a user encounters storage issues. - */ + @Override public User grantRolesToUser(String metalake, List roles, String user) throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException { return permissionManager.grantRolesToUser(metalake, roles, user); } - /** - * Grant roles to a group. - * - * @param metalake The metalake of the Group. - * @param group The name of the Group. - * @param roles The names of the Role. - * @return The Group after granted. - * @throws NoSuchGroupException If the Group with the given name does not exist. - * @throws NoSuchRoleException If the Role with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If granting roles to a group encounters storage issues. - */ + @Override public Group grantRolesToGroup(String metalake, List roles, String group) throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException { return permissionManager.grantRolesToGroup(metalake, roles, group); } - /** - * Revoke roles from a group. - * - * @param metalake The metalake of the Group. - * @param group The name of the Group. - * @param roles The name of the Role. - * @return The Group after revoked. - * @throws NoSuchGroupException If the Group with the given name does not exist. - * @throws NoSuchRoleException If the Role with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If revoking roles from a group encounters storage issues. - */ + @Override public Group revokeRolesFromGroup(String metalake, List roles, String group) throws NoSuchGroupException, NoSuchRoleException, NoSuchMetalakeException { return permissionManager.revokeRolesFromGroup(metalake, roles, group); } - /** - * Revoke roles from a user. - * - * @param metalake The metalake of the User. - * @param user The name of the User. - * @param roles The name of the Role. - * @return The User after revoked. - * @throws NoSuchUserException If the User with the given name does not exist. - * @throws NoSuchRoleException If the Role with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If revoking roles from a user encounters storage issues. - */ + @Override public User revokeRolesFromUser(String metalake, List roles, String user) throws NoSuchUserException, NoSuchRoleException, NoSuchMetalakeException { return permissionManager.revokeRolesFromUser(metalake, roles, user); } - /** - * Judges whether the user is the service admin. - * - * @param user the name of the user - * @return True if the user is service admin, otherwise false. - */ + @Override public boolean isServiceAdmin(String user) { return serviceAdmins.contains(user); } - /** - * Creates a new Role. - * - * @param metalake The Metalake of the Role. - * @param role The name of the Role. - * @param properties The properties of the Role. - * @param securableObjects The securable objects of the Role. - * @return The created Role instance. - * @throws RoleAlreadyExistsException If a Role with the same name already exists. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If creating the Role encounters storage issues. - */ + @Override public Role createRole( String metalake, String role, @@ -238,16 +124,7 @@ public Role createRole( return roleManager.createRole(metalake, role, properties, securableObjects); } - /** - * Gets a Role. - * - * @param metalake The Metalake of the Role. - * @param role The name of the Role. - * @return The getting Role instance. - * @throws NoSuchRoleException If the Role with the given name does not exist. - * @throws NoSuchMetalakeException If the Metalake with the given name does not exist. - * @throws RuntimeException If getting the Role encounters storage issues. - */ + @Override public Role getRole(String metalake, String role) throws NoSuchRoleException, NoSuchMetalakeException { return roleManager.getRole(metalake, role); 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 5e16c5bcb5d..3e7428036c1 100644 --- a/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java +++ b/core/src/main/java/org/apache/gravitino/authorization/AuthorizationUtils.java @@ -24,7 +24,14 @@ import org.apache.gravitino.GravitinoEnv; import org.apache.gravitino.NameIdentifier; import org.apache.gravitino.Namespace; +import org.apache.gravitino.SupportsCatalogs; +import org.apache.gravitino.SupportsMetalakes; +import org.apache.gravitino.connector.SupportsSchemas; import org.apache.gravitino.exceptions.NoSuchMetalakeException; +import org.apache.gravitino.file.FilesetCatalog; +import org.apache.gravitino.lifecycle.LifecycleHooks; +import org.apache.gravitino.messaging.TopicCatalog; +import org.apache.gravitino.rel.TableCatalog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -116,4 +123,65 @@ public static void checkRoleNamespace(Namespace namespace) { "Role namespace must have 3 levels, the input namespace is %s", namespace); } + + public static void prepareAuthorizationHooks(T manager, LifecycleHooks hooks) { + if (manager instanceof SupportsMetalakes) { + hooks.addPostHook( + "createMetalake", + (args, metalake) -> { + // TODO: Add the logic of setting the owner + }); + + } else if (manager instanceof SupportsCatalogs) { + hooks.addPostHook( + "createCatalog", + (args, catalog) -> { + // TODO: Add the logic of setting the owner + }); + + } else if (manager instanceof SupportsSchemas) { + hooks.addPostHook( + "createSchema", + (args, schema) -> { + // TODO: Add the logic of setting the owner + }); + + } else if (manager instanceof TableCatalog) { + hooks.addPostHook( + "createTable", + (args, schema) -> { + // TODO: Add the logic of setting the owner + }); + + } else if (manager instanceof TopicCatalog) { + hooks.addPostHook( + "createTopic", + (args, schema) -> { + // TODO: Add the logic of setting the owner + }); + + } else if (manager instanceof FilesetCatalog) { + hooks.addPostHook( + "createFileset", + (args, schema) -> { + // TODO: Add the logic of setting the owner + }); + } else if (manager instanceof AccessControlManager) { + hooks.addPostHook( + "addUser", + (args, user) -> { + // TODO: Add the logic of setting the owner + }); + hooks.addPostHook( + "addGroup", + (args, group) -> { + // TODO: Add the logic of setting the owner + }); + hooks.addPostHook( + "createRole", + (args, role) -> { + // TODO: Add the logic of setting the owner + }); + } + } } diff --git a/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookHelper.java b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookHelper.java new file mode 100644 index 00000000000..1a8ff92cb2a --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookHelper.java @@ -0,0 +1,35 @@ +/* + * 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.lifecycle; + +import java.lang.reflect.Proxy; + +/** The class is a helper class of lifecycle hooks */ +public class LifecycleHookHelper { + + private LifecycleHookHelper() {} + + public static T installHooks(T dispatcher, LifecycleHooks hooks) { + return (T) + Proxy.newProxyInstance( + dispatcher.getClass().getClassLoader(), + dispatcher.getClass().getInterfaces(), + new LifecycleHookProxy(dispatcher, hooks)); + } +} diff --git a/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookProxy.java b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookProxy.java new file mode 100644 index 00000000000..6f4d54ca732 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookProxy.java @@ -0,0 +1,44 @@ +/* + * 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.lifecycle; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.List; +import java.util.function.BiConsumer; + +class LifecycleHookProxy implements InvocationHandler { + private final LifecycleHooks hooks; + private final T dispatcher; + + LifecycleHookProxy(T dispatcher, LifecycleHooks hooks) { + this.hooks = hooks; + this.dispatcher = dispatcher; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result = method.invoke(dispatcher, args); + List postHooks = hooks.getPostHooks(method.getName()); + for (BiConsumer hook : postHooks) { + hook.accept(args, result); + } + return result; + } +} diff --git a/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHooks.java b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHooks.java new file mode 100644 index 00000000000..907d52361db --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHooks.java @@ -0,0 +1,52 @@ +/* + * 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.lifecycle; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * LifecycleHooks provide the ability to execute specific hook actions before or after calling + * specific methods. Now we only support the post hook. + */ +public class LifecycleHooks { + + private final Map> postHookMap = Maps.newHashMap(); + + public void addPostHook(String method, BiConsumer hook) { + List postHooks = postHookMap.computeIfAbsent(method, key -> Lists.newArrayList()); + postHooks.add(hook); + } + + public boolean isEmpty() { + return postHookMap.isEmpty(); + } + + List getPostHooks(String method) { + List postHooks = postHookMap.get(method); + if (postHooks == null) { + return Collections.emptyList(); + } + return postHooks; + } +} 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 8035f303f03..06d397f3b30 100644 --- a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java +++ b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManager.java @@ -80,7 +80,7 @@ public static void setUp() throws Exception { accessControlManager = new AccessControlManager(entityStore, new RandomIdGenerator(), config); FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", entityStore, true); FieldUtils.writeField( - GravitinoEnv.getInstance(), "accessControlManager", accessControlManager, true); + GravitinoEnv.getInstance(), "accessControlDispatcher", accessControlManager, true); } @AfterAll diff --git a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManagerForPermissions.java b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManagerForPermissions.java index d8bc702b91c..0ef4b6a8c39 100644 --- a/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManagerForPermissions.java +++ b/core/src/test/java/org/apache/gravitino/authorization/TestAccessControlManagerForPermissions.java @@ -125,7 +125,7 @@ public static void setUp() throws Exception { FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", entityStore, true); FieldUtils.writeField( - GravitinoEnv.getInstance(), "accessControlManager", accessControlManager, true); + GravitinoEnv.getInstance(), "accessControlDispatcher", accessControlManager, true); } @AfterAll diff --git a/core/src/test/java/org/apache/gravitino/lifecycle/TestLifecycleHooks.java b/core/src/test/java/org/apache/gravitino/lifecycle/TestLifecycleHooks.java new file mode 100644 index 00000000000..f3df3a9bddf --- /dev/null +++ b/core/src/test/java/org/apache/gravitino/lifecycle/TestLifecycleHooks.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.gravitino.lifecycle; + +import static org.apache.gravitino.Configs.SERVICE_ADMINS; + +import com.google.common.collect.Lists; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.gravitino.Config; +import org.apache.gravitino.EntityStore; +import org.apache.gravitino.GravitinoEnv; +import org.apache.gravitino.NameIdentifier; +import org.apache.gravitino.authorization.AccessControlDispatcher; +import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.metalake.MetalakeDispatcher; +import org.apache.gravitino.metalake.MetalakeManager; +import org.apache.gravitino.storage.IdGenerator; +import org.apache.gravitino.storage.RandomIdGenerator; +import org.apache.gravitino.storage.memory.TestMemoryEntityStore; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class TestLifecycleHooks { + + @Test + public void testLifecycleHooks() throws IllegalAccessException { + Config config = new Config(false) {}; + config.set(SERVICE_ADMINS, Lists.newArrayList("admin1", "admin2")); + EntityStore entityStore = new TestMemoryEntityStore.InMemoryEntityStore(); + entityStore.initialize(config); + entityStore.setSerDe(null); + IdGenerator idGenerator = new RandomIdGenerator(); + FieldUtils.writeField(GravitinoEnv.getInstance(), "entityStore", entityStore, true); + + LifecycleHooks hooks = new LifecycleHooks(); + AtomicBoolean result = new AtomicBoolean(true); + hooks.addPostHook( + "createMetalake", + (args, metalake) -> { + result.set(false); + }); + MetalakeDispatcher metalakeDispatcher = + LifecycleHookHelper.installHooks(new MetalakeManager(entityStore, idGenerator), hooks); + Assertions.assertTrue(result.get()); + metalakeDispatcher.createMetalake(NameIdentifier.of("test"), "", Collections.emptyMap()); + Assertions.assertFalse(result.get()); + + hooks.addPostHook( + "addUser", + (args, user) -> { + result.set(false); + }); + AccessControlDispatcher accessControlManager = + LifecycleHookHelper.installHooks( + new AccessControlManager(entityStore, idGenerator, config), hooks); + result.set(true); + Assertions.assertTrue(result.get()); + accessControlManager.addUser("test", "test"); + Assertions.assertFalse(result.get()); + } +} diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java index d1ec13c7ed1..537bafb9e78 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/GroupOperations.java @@ -31,7 +31,7 @@ import javax.ws.rs.core.Response; import org.apache.gravitino.GravitinoEnv; import org.apache.gravitino.NameIdentifier; -import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.dto.requests.GroupAddRequest; import org.apache.gravitino.dto.responses.GroupResponse; @@ -51,7 +51,7 @@ public class GroupOperations { private static final Logger LOG = LoggerFactory.getLogger(GroupOperations.class); - private final AccessControlManager accessControlManager; + private final AccessControlDispatcher accessControlManager; @Context private HttpServletRequest httpRequest; @@ -59,7 +59,7 @@ public GroupOperations() { // Because accessManager may be null when Gravitino doesn't enable authorization, // and Jersey injection doesn't support null value. So GroupOperations chooses to retrieve // accessControlManager from GravitinoEnv instead of injection here. - this.accessControlManager = GravitinoEnv.getInstance().accessControlManager(); + this.accessControlManager = GravitinoEnv.getInstance().accessControlDispatcher(); } @GET diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java index c27791be5e7..7613d89ecf2 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/PermissionOperations.java @@ -30,7 +30,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.gravitino.GravitinoEnv; import org.apache.gravitino.NameIdentifier; -import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.dto.requests.RoleGrantRequest; import org.apache.gravitino.dto.requests.RoleRevokeRequest; @@ -45,7 +45,7 @@ @Path("/metalakes/{metalake}/permissions") public class PermissionOperations { - private final AccessControlManager accessControlManager; + private final AccessControlDispatcher accessControlManager; @Context private HttpServletRequest httpRequest; @@ -53,7 +53,7 @@ public PermissionOperations() { // Because accessManager may be null when Gravitino doesn't enable authorization, // and Jersey injection doesn't support null value. So PermissionOperations chooses to retrieve // accessControlManager from GravitinoEnv instead of injection here. - this.accessControlManager = GravitinoEnv.getInstance().accessControlManager(); + this.accessControlManager = GravitinoEnv.getInstance().accessControlDispatcher(); } @PUT 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 18b74c84e51..f3e8e2f86db 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 @@ -35,7 +35,7 @@ import org.apache.gravitino.GravitinoEnv; import org.apache.gravitino.MetadataObject; import org.apache.gravitino.NameIdentifier; -import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.authorization.Privilege; import org.apache.gravitino.authorization.Privileges; @@ -57,12 +57,12 @@ public class RoleOperations { private static final Logger LOG = LoggerFactory.getLogger(RoleOperations.class); - private final AccessControlManager accessControlManager; + private final AccessControlDispatcher accessControlManager; @Context private HttpServletRequest httpRequest; public RoleOperations() { - this.accessControlManager = GravitinoEnv.getInstance().accessControlManager(); + this.accessControlManager = GravitinoEnv.getInstance().accessControlDispatcher(); } @GET diff --git a/server/src/main/java/org/apache/gravitino/server/web/rest/UserOperations.java b/server/src/main/java/org/apache/gravitino/server/web/rest/UserOperations.java index 7b63082f480..1d93e0e6afa 100644 --- a/server/src/main/java/org/apache/gravitino/server/web/rest/UserOperations.java +++ b/server/src/main/java/org/apache/gravitino/server/web/rest/UserOperations.java @@ -31,7 +31,7 @@ import javax.ws.rs.core.Response; import org.apache.gravitino.GravitinoEnv; import org.apache.gravitino.NameIdentifier; -import org.apache.gravitino.authorization.AccessControlManager; +import org.apache.gravitino.authorization.AccessControlDispatcher; import org.apache.gravitino.authorization.AuthorizationUtils; import org.apache.gravitino.dto.requests.UserAddRequest; import org.apache.gravitino.dto.responses.RemoveResponse; @@ -51,7 +51,7 @@ public class UserOperations { private static final Logger LOG = LoggerFactory.getLogger(UserOperations.class); - private final AccessControlManager accessControlManager; + private final AccessControlDispatcher accessControlManager; @Context private HttpServletRequest httpRequest; @@ -59,7 +59,7 @@ public UserOperations() { // Because accessManager may be null when Gravitino doesn't enable authorization, // and Jersey injection doesn't support null value. So UserOperations chooses to retrieve // accessControlManager from GravitinoEnv instead of injection here. - this.accessControlManager = GravitinoEnv.getInstance().accessControlManager(); + this.accessControlManager = GravitinoEnv.getInstance().accessControlDispatcher(); } @GET diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java index 426b2f40a5d..c3b34bc6bff 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestGroupOperations.java @@ -81,7 +81,7 @@ public static void setup() throws IllegalAccessException { Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY); Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL); FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new LockManager(config), true); - FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlManager", manager, true); + FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlDispatcher", manager, true); } @Override diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java index 94b0c04135f..76dba3edd4b 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestPermissionOperations.java @@ -83,7 +83,7 @@ public static void setup() throws IllegalAccessException { Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY); Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL); FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new LockManager(config), true); - FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlManager", manager, true); + FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlDispatcher", manager, true); } @Override 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 ad0c5e20bff..6c34547d07d 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 @@ -99,7 +99,7 @@ public static void setup() throws IllegalAccessException { Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY); Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL); FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new LockManager(config), true); - FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlManager", manager, true); + FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlDispatcher", manager, true); FieldUtils.writeField( GravitinoEnv.getInstance(), "metalakeDispatcher", metalakeDispatcher, true); FieldUtils.writeField( diff --git a/server/src/test/java/org/apache/gravitino/server/web/rest/TestUserOperations.java b/server/src/test/java/org/apache/gravitino/server/web/rest/TestUserOperations.java index adb521cf0ea..d3209e0e22c 100644 --- a/server/src/test/java/org/apache/gravitino/server/web/rest/TestUserOperations.java +++ b/server/src/test/java/org/apache/gravitino/server/web/rest/TestUserOperations.java @@ -81,7 +81,7 @@ public static void setup() throws IllegalAccessException { Mockito.doReturn(1000L).when(config).get(TREE_LOCK_MIN_NODE_IN_MEMORY); Mockito.doReturn(36000L).when(config).get(TREE_LOCK_CLEAN_INTERVAL); FieldUtils.writeField(GravitinoEnv.getInstance(), "lockManager", new LockManager(config), true); - FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlManager", manager, true); + FieldUtils.writeField(GravitinoEnv.getInstance(), "accessControlDispatcher", manager, true); } @Override