Skip to content

Commit

Permalink
[apache#4460] feat(core): Add the method call of the authorizationPlu…
Browse files Browse the repository at this point in the history
…gin (apache#4461)

### What changes were proposed in this pull request?
If we want to push the privileges down the underlying system, we need to
call methods of the underlying system authorization plugin.

### Why are the changes needed?

Fix: apache#4460

### Does this PR introduce _any_ user-facing change?
No.

### How was this patch tested?
Add some test cases.
  • Loading branch information
jerqi committed Aug 22, 2024
1 parent 8293bed commit 70b7ccc
Show file tree
Hide file tree
Showing 20 changed files with 1,001 additions and 252 deletions.
18 changes: 18 additions & 0 deletions core/src/main/java/org/apache/gravitino/GravitinoEnv.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.google.common.base.Preconditions;
import org.apache.gravitino.authorization.AccessControlDispatcher;
import org.apache.gravitino.authorization.AccessControlManager;
import org.apache.gravitino.authorization.FutureGrantManager;
import org.apache.gravitino.authorization.OwnerManager;
import org.apache.gravitino.auxiliary.AuxiliaryServiceManager;
import org.apache.gravitino.catalog.CatalogDispatcher;
Expand Down Expand Up @@ -111,6 +112,7 @@ public class GravitinoEnv {
private TagManager tagManager;
private EventBus eventBus;
private OwnerManager ownerManager;
private FutureGrantManager futureGrantManager;

protected GravitinoEnv() {}

Expand Down Expand Up @@ -287,10 +289,24 @@ public TagManager tagManager() {
return tagManager;
}

/**
* Get the OwnerManager associated with the Gravitino environment.
*
* @return The OwnerManager instance.
*/
public OwnerManager ownerManager() {
return ownerManager;
}

/**
* Get the FutureGrantManager associated with the Gravitino environment.
*
* @return The FutureGrantManager instance.
*/
public FutureGrantManager futureGrantManager() {
return futureGrantManager;
}

public void start() {
auxServiceManager.serviceStart();
metricsSystem.start();
Expand Down Expand Up @@ -410,9 +426,11 @@ private void initGravitinoServerComponents() {

this.accessControlDispatcher = accessControlHookDispatcher;
this.ownerManager = new OwnerManager(entityStore);
this.futureGrantManager = new FutureGrantManager(entityStore);
} else {
this.accessControlDispatcher = null;
this.ownerManager = null;
this.futureGrantManager = null;
}

this.auxServiceManager = new AuxiliaryServiceManager();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,13 @@ public interface SupportsRelationOperations {
/** Relation is an abstraction which connects two entities. */
enum Type {
/** The owner relationship */
OWNER_REL
OWNER_REL,
/** Metadata objet and role relationship */
METADATA_OBJECT_ROLE_REL,
/** Role and user relationship */
ROLE_USER_REL,
/** Role and group relationship */
ROLE_GROUP_REL
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,24 @@
*/
package org.apache.gravitino.authorization;

import com.google.common.collect.Lists;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import org.apache.gravitino.Catalog;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.GravitinoEnv;
import org.apache.gravitino.MetadataObject;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.Namespace;
import org.apache.gravitino.catalog.CatalogManager;
import org.apache.gravitino.connector.BaseCatalog;
import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
import org.apache.gravitino.exceptions.NoSuchMetalakeException;
import org.apache.gravitino.utils.MetadataObjectUtil;
import org.apache.gravitino.utils.NameIdentifierUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -37,6 +48,15 @@ public class AuthorizationUtils {
private static final Logger LOG = LoggerFactory.getLogger(AuthorizationUtils.class);
private static final String METALAKE_DOES_NOT_EXIST_MSG = "Metalake %s does not exist";

private static final List<Privilege.Name> pluginNotSupportsPrivileges =
Lists.newArrayList(
Privilege.Name.CREATE_CATALOG,
Privilege.Name.USE_CATALOG,
Privilege.Name.MANAGE_GRANTS,
Privilege.Name.MANAGE_USERS,
Privilege.Name.MANAGE_GROUPS,
Privilege.Name.CREATE_ROLE);

private AuthorizationUtils() {}

static void checkMetalakeExists(String metalake) throws NoSuchMetalakeException {
Expand Down Expand Up @@ -116,4 +136,61 @@ public static void checkRoleNamespace(Namespace namespace) {
"Role namespace must have 3 levels, the input namespace is %s",
namespace);
}

// Every catalog has one authorization plugin, we should avoid calling
// underlying authorization repeatedly. So we use a set to record which
// catalog has been called the authorization plugin.
public static void callAuthorizationPlugin(
String metalake,
List<SecurableObject> securableObjects,
Set<String> catalogsAlreadySet,
Consumer<AuthorizationPlugin> consumer) {
CatalogManager catalogManager = GravitinoEnv.getInstance().catalogManager();
for (SecurableObject securableObject : securableObjects) {
if (needApplyAllAuthorizationPlugin(securableObject)) {
Catalog[] catalogs = catalogManager.listCatalogsInfo(Namespace.of(metalake));
for (Catalog catalog : catalogs) {
callAuthorizationPluginImpl(catalogsAlreadySet, consumer, catalog);
}

} else if (supportsSingleAuthorizationPlugin(securableObject.type())) {
NameIdentifier catalogIdent =
NameIdentifierUtil.getCatalogIdentifier(
MetadataObjectUtil.toEntityIdent(metalake, securableObject));
Catalog catalog = catalogManager.loadCatalog(catalogIdent);
callAuthorizationPluginImpl(catalogsAlreadySet, consumer, catalog);
}
}
}

private static void callAuthorizationPluginImpl(
Set<String> catalogsAlreadySet, Consumer<AuthorizationPlugin> consumer, Catalog catalog) {
if (!catalogsAlreadySet.contains(catalog.name())) {
catalogsAlreadySet.add(catalog.name());

if (catalog instanceof BaseCatalog) {
BaseCatalog baseCatalog = (BaseCatalog) catalog;
if (baseCatalog.getAuthorizationPlugin() != null) {
consumer.accept(baseCatalog.getAuthorizationPlugin());
}
}
}
}

public static boolean needApplyAllAuthorizationPlugin(SecurableObject securableObject) {
// TODO: Add supportsSecurableObjects method for every privilege to simplify this code
if (securableObject.type() == MetadataObject.Type.METALAKE) {
List<Privilege> privileges = securableObject.privileges();
for (Privilege privilege : privileges) {
if (!pluginNotSupportsPrivileges.contains(privilege.name())) {
return true;
}
}
}
return false;
}

private static boolean supportsSingleAuthorizationPlugin(MetadataObject.Type type) {
return type != MetadataObject.Type.ROLE && type != MetadataObject.Type.METALAKE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.gravitino.authorization;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.gravitino.Entity;
import org.apache.gravitino.EntityStore;
import org.apache.gravitino.NameIdentifier;
import org.apache.gravitino.SupportsRelationOperations;
import org.apache.gravitino.connector.BaseCatalog;
import org.apache.gravitino.connector.authorization.AuthorizationPlugin;
import org.apache.gravitino.meta.GroupEntity;
import org.apache.gravitino.meta.RoleEntity;
import org.apache.gravitino.meta.UserEntity;
import org.glassfish.jersey.internal.guava.Sets;

/**
* FutureGrantManager is responsible for granting privileges to future object. When you grant a
* privilege which authorization supports to a metalake, the future creating catalog should apply
* the privilege to underlying authorization plugin, too. FutureGrantManager selects the roles
* contains the metalake securable object and filter unnecessary roles. Then, it selects the users
* and groups by roles. Finally, it apply the information to the authorization plugins.
*/
public class FutureGrantManager {
EntityStore entityStore;

public FutureGrantManager(EntityStore entityStore) {
this.entityStore = entityStore;
}

public void grantNewlyCreatedCatalog(String metalake, BaseCatalog catalog) {
try {

Map<UserEntity, Set<RoleEntity>> userGrantRoles = Maps.newHashMap();
Map<GroupEntity, Set<RoleEntity>> groupGrantRoles = Maps.newHashMap();
List<RoleEntity> roles =
entityStore.relationOperations()
.listEntitiesByRelation(
SupportsRelationOperations.Type.METADATA_OBJECT_ROLE_REL,
NameIdentifier.of(metalake),
Entity.EntityType.METALAKE)
.stream()
.map(entity -> (RoleEntity) entity)
.collect(Collectors.toList());

for (RoleEntity role : roles) {

boolean supportsFutureGrant = false;
for (SecurableObject object : role.securableObjects()) {
if (AuthorizationUtils.needApplyAllAuthorizationPlugin(object)) {
supportsFutureGrant = true;
break;
}
}

if (!supportsFutureGrant) {
continue;
}

List<UserEntity> users =
entityStore.relationOperations()
.listEntitiesByRelation(
SupportsRelationOperations.Type.ROLE_USER_REL,
role.nameIdentifier(),
Entity.EntityType.ROLE)
.stream()
.map(entity -> (UserEntity) entity)
.collect(Collectors.toList());

for (UserEntity user : users) {
Set<RoleEntity> roleSet = userGrantRoles.computeIfAbsent(user, k -> Sets.newHashSet());
roleSet.add(role);
}

List<GroupEntity> groups =
entityStore.relationOperations()
.listEntitiesByRelation(
SupportsRelationOperations.Type.ROLE_GROUP_REL,
role.nameIdentifier(),
Entity.EntityType.ROLE)
.stream()
.map(entity -> (GroupEntity) entity)
.collect(Collectors.toList());

for (GroupEntity group : groups) {
Set<RoleEntity> roleSet = groupGrantRoles.computeIfAbsent(group, k -> Sets.newHashSet());
roleSet.add(role);
}
}

for (Map.Entry<UserEntity, Set<RoleEntity>> entry : userGrantRoles.entrySet()) {
AuthorizationPlugin authorizationPlugin = catalog.getAuthorizationPlugin();
if (authorizationPlugin != null) {
authorizationPlugin.onGrantedRolesToUser(
Lists.newArrayList(entry.getValue()), entry.getKey());
}
}

for (Map.Entry<GroupEntity, Set<RoleEntity>> entry : groupGrantRoles.entrySet()) {
AuthorizationPlugin authorizationPlugin = catalog.getAuthorizationPlugin();

if (authorizationPlugin != null) {
authorizationPlugin.onGrantedRolesToGroup(
Lists.newArrayList(entry.getValue()), entry.getKey());
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Loading

0 comments on commit 70b7ccc

Please sign in to comment.