jerqi commented on code in PR #4515: URL: https://github.com/apache/gravitino/pull/4515#discussion_r1724906342
########## authorizations/authorization-ranger/src/main/java/org/apache/gravitino/authorization/ranger/RangerHelper.java: ########## @@ -0,0 +1,533 @@ +/* + * 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.ranger; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.errorprone.annotations.FormatMethod; +import com.google.errorprone.annotations.FormatString; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang.StringUtils; +import org.apache.gravitino.MetadataObject; +import org.apache.gravitino.authorization.Owner; +import org.apache.gravitino.authorization.Privilege; +import org.apache.gravitino.authorization.SecurableObject; +import org.apache.gravitino.authorization.SecurableObjects; +import org.apache.gravitino.authorization.ranger.reference.RangerDefines; +import org.apache.gravitino.exceptions.AuthorizationPluginException; +import org.apache.ranger.RangerServiceException; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerRole; +import org.apache.ranger.plugin.util.GrantRevokeRoleRequest; +import org.apache.ranger.plugin.util.SearchFilter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class is a helper class for the Ranger authorization plugin. It provides the ability to + * manage the Ranger policies and roles. + */ +public class RangerHelper { + private static final Logger LOG = LoggerFactory.getLogger(RangerHelper.class); + public static final String MANAGED_BY_GRAVITINO = "MANAGED_BY_GRAVITINO"; + + RangerAuthorizationPlugin rangerAuthorizationPlugin; + + /** Mapping Gravitino privilege name to the underlying authorization system privileges. */ + protected Map<Privilege.Name, Set<String>> privilegesMapping = null; + /** The owner privileges, the owner can do anything on the metadata object */ + protected Set<String> ownerPrivileges = null; + + /** + * Because Ranger doesn't support the precise search, Ranger will return the policy meets the + * wildcard(*,?) conditions, If you use `db.table` condition to search policy, the Ranger will + * match `db1.table1`, `db1.table2`, `db*.table*`, So we need to manually precisely filter this + * research results. <br> + * policySearchKeys: The search Ranger policy condition key defines. <br> + * policyPreciseFilterKeys: The precise filter Ranger search results key defines <br> + */ + protected List<String> policySearchKeys = null; + + protected List<String> policyPreciseFilterKeys = null; + + public RangerHelper(RangerAuthorizationPlugin rangerAuthorizationPlugin, String catalogProvider) { + this.rangerAuthorizationPlugin = rangerAuthorizationPlugin; + switch (catalogProvider) { + case "hive": + initPrivilegesMapping(); + initOwnerPrivileges(); + initPolicySearchKeys(); + initPreciseFilterKeys(); + break; + default: + throw new IllegalArgumentException( + "Authorization plugin unsupported catalog provider: " + catalogProvider); + } + } + + /** Initial mapping Gravitino privilege name to the underlying authorization system privileges. */ + private void initPrivilegesMapping() { + privilegesMapping = + ImmutableMap.<Privilege.Name, Set<String>>builder() + .put( + Privilege.Name.CREATE_SCHEMA, + ImmutableSet.of(RangerDefines.ACCESS_TYPE_HIVE_CREATE)) + .put( + Privilege.Name.CREATE_TABLE, ImmutableSet.of(RangerDefines.ACCESS_TYPE_HIVE_CREATE)) + .put( + Privilege.Name.MODIFY_TABLE, + ImmutableSet.of( + RangerDefines.ACCESS_TYPE_HIVE_UPDATE, + RangerDefines.ACCESS_TYPE_HIVE_ALTER, + RangerDefines.ACCESS_TYPE_HIVE_WRITE)) + .put( + Privilege.Name.SELECT_TABLE, + ImmutableSet.of( + RangerDefines.ACCESS_TYPE_HIVE_READ, RangerDefines.ACCESS_TYPE_HIVE_SELECT)) + .build(); + } + + /** Initial Owner privileges */ + private void initOwnerPrivileges() { + ownerPrivileges = ImmutableSet.of(RangerDefines.ACCESS_TYPE_HIVE_ALL); + } + + /** Initial Ranger policy search key defines */ + private void initPolicySearchKeys() { + policySearchKeys = + Arrays.asList( + RangerDefines.SEARCH_FILTER_DATABASE, + RangerDefines.SEARCH_FILTER_TABLE, + RangerDefines.SEARCH_FILTER_COLUMN); + } + + /** Initial precise filter key defines */ + private void initPreciseFilterKeys() { + policyPreciseFilterKeys = + Arrays.asList( + RangerDefines.RESOURCE_DATABASE, + RangerDefines.RESOURCE_TABLE, + RangerDefines.RESOURCE_COLUMN); + } + + /** + * For easy management, each privilege will create one RangerPolicyItemAccess in the policy. + * + * @param policyItem The policy item to check + * @throws AuthorizationPluginException If the policy item contains more than one access type + */ + void checkPolicyItemAccess(RangerPolicy.RangerPolicyItem policyItem) + throws AuthorizationPluginException { + if (policyItem.getAccesses().size() != 1) { + throw new AuthorizationPluginException( + "The access type only have one in the delegate Gravitino management policy"); + } + Set<String> setAccesses = new HashSet<>(); + policyItem + .getAccesses() + .forEach( + access -> { + if (setAccesses.contains(access.getType())) { + throw new AuthorizationPluginException( + "Contain duplicate privilege(%s) in the delegate Gravitino management policy ", + access.getType()); + } + setAccesses.add(access.getType()); + }); + } + + /** + * Add policy item access items base the securable object's privileges. <br> + * We cannot clean the policy items because one Ranger policy maybe contains multiple Gravitino + * securable objects. <br> + */ + void addPolicyItem(RangerPolicy policy, String roleName, SecurableObject securableObject) { + // First check the privilege if support in the Ranger Hive + checkPrivileges(securableObject); + + // Add the policy items by the securable object's privileges + securableObject + .privileges() + .forEach( + gravitinoPrivilege -> { + // Translate the Gravitino privilege to map Ranger privilege + rangerAuthorizationPlugin + .translatePrivilege(gravitinoPrivilege.name()) + .forEach( + mappedPrivilege -> { + // Find the policy item that matches Gravitino privilege + List<RangerPolicy.RangerPolicyItem> matchPolicyItems = + policy.getPolicyItems().stream() + .filter( + policyItem -> { + return policyItem.getAccesses().stream() + .anyMatch( + access -> access.getType().equals(mappedPrivilege)); + }) + .collect(Collectors.toList()); + + if (matchPolicyItems.size() == 0) { + // If the policy item does not exist, then create a new policy item + RangerPolicy.RangerPolicyItem policyItem = + new RangerPolicy.RangerPolicyItem(); + RangerPolicy.RangerPolicyItemAccess access = + new RangerPolicy.RangerPolicyItemAccess(); + access.setType(mappedPrivilege); + policyItem.getAccesses().add(access); + policyItem.getRoles().add(roleName); + if (Privilege.Condition.ALLOW == gravitinoPrivilege.condition()) { + policy.getPolicyItems().add(policyItem); + } else { + policy.getDenyPolicyItems().add(policyItem); + } + } else { + // If the policy item exists, then add the role to the policy item + matchPolicyItems.stream() + .forEach( + policyItem -> { + // If the role is not in the policy item, then add it + if (!policyItem.getRoles().contains(roleName)) { + policyItem.getRoles().add(roleName); + } + }); + } + }); + }); + } + + /** + * Remove policy item base the securable object's privileges and role name. <br> + * We cannot directly clean the policy items because one Ranger policy maybe contains multiple + * Gravitino privilege objects. <br> + */ + void removePolicyItem(RangerPolicy policy, String roleName, SecurableObject securableObject) { + // First check the privilege if support in the Ranger Hive + checkPrivileges(securableObject); + + // Delete the policy role base the securable object's privileges + policy.getPolicyItems().stream() + .forEach( + policyItem -> { + policyItem + .getAccesses() + .forEach( + access -> { + boolean matchPrivilege = + securableObject.privileges().stream() + .filter(Objects::nonNull) + .flatMap( + privilege -> + rangerAuthorizationPlugin + .translatePrivilege(privilege.name()).stream()) + .filter(Objects::nonNull) + .anyMatch( + privilege -> { + return access.getType().equals(privilege); + }); + if (matchPrivilege + && !policyItem.getUsers().isEmpty() + && !policyItem.getGroups().isEmpty()) { + // Not ownership policy item, then remove the role + policyItem.getRoles().removeIf(roleName::equals); + } + }); + }); + + // Delete the policy items if the roles are empty and not ownership policy item + policy + .getPolicyItems() + .removeIf( + policyItem -> { + return policyItem.getRoles().isEmpty() + && policyItem.getUsers().isEmpty() + && policyItem.getGroups().isEmpty(); + }); + } + + /** + * Whether this privilege is underlying permission system supported + * + * @param securableObject The securable object to check + * @return true if the privilege is supported, otherwise false + */ + private boolean checkPrivileges(SecurableObject securableObject) { + securableObject + .privileges() + .forEach( + privilege -> { + check( + privilegesMapping.containsKey(privilege.name()), + "This privilege %s is not supported in the Ranger hive authorization", + privilege.name()); + }); + return true; + } + + /** + * Find the managed policy for the metadata object. + * + * @param metadataObject The metadata object to find the managed policy. + * @return The managed policy for the metadata object. + */ + @VisibleForTesting + public RangerPolicy findManagedPolicy(MetadataObject metadataObject) + throws AuthorizationPluginException { + List<String> nsMetadataObj = + Lists.newArrayList(SecurableObjects.DOT_SPLITTER.splitToList(metadataObject.fullName())); + nsMetadataObj.remove(0); // skip `catalog` + Map<String, String> policyFilter = new HashMap<>(); + Map<String, String> preciseFilterKeysFilter = new HashMap<>(); + policyFilter.put( + RangerDefines.SEARCH_FILTER_SERVICE_NAME, rangerAuthorizationPlugin.rangerServiceName); + policyFilter.put(SearchFilter.POLICY_LABELS_PARTIAL, MANAGED_BY_GRAVITINO); + for (int i = 0; i < nsMetadataObj.size(); i++) { + policyFilter.put(policySearchKeys.get(i), nsMetadataObj.get(i)); + preciseFilterKeysFilter.put(policyPreciseFilterKeys.get(i), nsMetadataObj.get(i)); + } + + try { + List<RangerPolicy> policies = + rangerAuthorizationPlugin.rangerClient.findPolicies(policyFilter); + + if (!policies.isEmpty()) { + /** + * Because Ranger doesn't support the precise search, Ranger will return the policy meets + * the wildcard(*,?) conditions, If you use `db.table` condition to search policy, the + * Ranger will match `db1.table1`, `db1.table2`, `db*.table*`, So we need to manually + * precisely filter this research results. + */ + policies = + policies.stream() + .filter( + policy -> + policy.getResources().entrySet().stream() + .allMatch( + entry -> + preciseFilterKeysFilter.containsKey(entry.getKey()) + && entry.getValue().getValues().size() == 1 + && entry + .getValue() + .getValues() + .contains(preciseFilterKeysFilter.get(entry.getKey())))) + .collect(Collectors.toList()); + } + + // Only return the policies that are managed by Gravitino. + if (policies.size() > 1) { + throw new AuthorizationPluginException( + "Each metadata object only have one Gravitino management enable policies."); + } + + RangerPolicy policy = policies.size() == 1 ? policies.get(0) : null; Review Comment: I and @yuqi prefer ``` if (policies.isEmpty()) { return null; } policy = policies.get(0); policy.getPolicyItems().forEach(this::checkPolicyItemAccess); policy.getDenyPolicyItems().forEach(this::checkPolicyItemAccess); policy.getRowFilterPolicyItems().forEach(this::checkPolicyItemAccess); policy.getDataMaskPolicyItems().forEach(this::checkPolicyItemAccess); ```` It's more readable. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
