diff --git a/core/src/main/java/org/apache/gravitino/GravitinoEnv.java b/core/src/main/java/org/apache/gravitino/GravitinoEnv.java index b307cddbd26..2a0329ea719 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; @@ -86,7 +90,7 @@ public class GravitinoEnv { private MetalakeDispatcher metalakeDispatcher; - private AccessControlManager accessControlManager; + private AccessControlDispatcher accessControlDispatcher; private IdGenerator idGenerator; @@ -140,42 +144,49 @@ public void initialize(Config config) { EventBus eventBus = eventListenerManager.createEventBus(); // Create and initialize metalake related modules - MetalakeManager metalakeManager = new MetalakeManager(entityStore, idGenerator); + MetalakeManager metalakeManager = + installLifecycleHooks(new MetalakeManager(entityStore, idGenerator)); MetalakeNormalizeDispatcher metalakeNormalizeDispatcher = new MetalakeNormalizeDispatcher(metalakeManager); this.metalakeDispatcher = new MetalakeEventDispatcher(eventBus, metalakeNormalizeDispatcher); // Create and initialize Catalog related modules - this.catalogManager = new CatalogManager(config, entityStore, idGenerator); + this.catalogManager = + installLifecycleHooks(new CatalogManager(config, entityStore, idGenerator)); CatalogNormalizeDispatcher catalogNormalizeDispatcher = new CatalogNormalizeDispatcher(catalogManager); this.catalogDispatcher = new CatalogEventDispatcher(eventBus, catalogNormalizeDispatcher); SchemaOperationDispatcher schemaOperationDispatcher = - new SchemaOperationDispatcher(catalogManager, entityStore, idGenerator); + installLifecycleHooks( + new SchemaOperationDispatcher(catalogManager, entityStore, idGenerator)); SchemaNormalizeDispatcher schemaNormalizeDispatcher = new SchemaNormalizeDispatcher(schemaOperationDispatcher); this.schemaDispatcher = new SchemaEventDispatcher(eventBus, schemaNormalizeDispatcher); TableOperationDispatcher tableOperationDispatcher = - new TableOperationDispatcher(catalogManager, entityStore, idGenerator); + installLifecycleHooks( + new TableOperationDispatcher(catalogManager, entityStore, idGenerator)); TableNormalizeDispatcher tableNormalizeDispatcher = new TableNormalizeDispatcher(tableOperationDispatcher); this.tableDispatcher = new TableEventDispatcher(eventBus, tableNormalizeDispatcher); PartitionOperationDispatcher partitionOperationDispatcher = - new PartitionOperationDispatcher(catalogManager, entityStore, idGenerator); + installLifecycleHooks( + new PartitionOperationDispatcher(catalogManager, entityStore, idGenerator)); // todo: support PartitionEventDispatcher this.partitionDispatcher = new PartitionNormalizeDispatcher(partitionOperationDispatcher); FilesetOperationDispatcher filesetOperationDispatcher = - new FilesetOperationDispatcher(catalogManager, entityStore, idGenerator); + installLifecycleHooks( + new FilesetOperationDispatcher(catalogManager, entityStore, idGenerator)); FilesetNormalizeDispatcher filesetNormalizeDispatcher = new FilesetNormalizeDispatcher(filesetOperationDispatcher); this.filesetDispatcher = new FilesetEventDispatcher(eventBus, filesetNormalizeDispatcher); TopicOperationDispatcher topicOperationDispatcher = - new TopicOperationDispatcher(catalogManager, entityStore, idGenerator); + installLifecycleHooks( + new TopicOperationDispatcher(catalogManager, entityStore, idGenerator)); TopicNormalizeDispatcher topicNormalizeDispatcher = new TopicNormalizeDispatcher(topicOperationDispatcher); this.topicDispatcher = new TopicEventDispatcher(eventBus, topicNormalizeDispatcher); @@ -183,9 +194,10 @@ public void initialize(Config config) { // 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(new AccessControlManager(entityStore, idGenerator, config)); } else { - this.accessControlManager = null; + this.accessControlDispatcher = null; } this.auxServiceManager = new AuxiliaryServiceManager(); @@ -305,8 +317,8 @@ public LockManager getLockManager() { * * @return The AccessControlManager instance. */ - public AccessControlManager accessControlManager() { - return accessControlManager; + public AccessControlDispatcher accessControlDispatcher() { + return accessControlDispatcher; } /** @@ -358,4 +370,23 @@ public void shutdown() { LOG.info("Gravitino Environment is shut down."); } + + // 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..48606d7fc55 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,69 @@ 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. - */ 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 +123,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..1ce6d4c4079 --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHookHelper.java @@ -0,0 +1,34 @@ +/* + * 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; + +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..2d50e8635fe --- /dev/null +++ b/core/src/main/java/org/apache/gravitino/lifecycle/LifecycleHooks.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.gravitino.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; + +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/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..3420caf8331 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,12 +35,6 @@ 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.AuthorizationUtils; -import org.apache.gravitino.authorization.Privilege; -import org.apache.gravitino.authorization.Privileges; -import org.apache.gravitino.authorization.SecurableObject; -import org.apache.gravitino.authorization.SecurableObjects; import org.apache.gravitino.dto.authorization.SecurableObjectDTO; import org.apache.gravitino.dto.requests.RoleCreateRequest; import org.apache.gravitino.dto.responses.DeleteResponse; @@ -57,12 +51,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