This is an automated email from the ASF dual-hosted git repository.
rmani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/master by this push:
new 50cf9e7cc RANGER-4960: API to do Grant / Revoke permissions on the
Dataset for users / groups/ roles
50cf9e7cc is described below
commit 50cf9e7ccc9f032d1fe19e9021579785060fbda7
Author: Radhika Kundam <[email protected]>
AuthorDate: Thu Oct 31 10:40:04 2024 -0700
RANGER-4960: API to do Grant / Revoke permissions on the Dataset for users
/ groups/ roles
Signed-off-by: Ramesh Mani <[email protected]>
---
.../apache/ranger/plugin/model/RangerGrant.java | 103 ++++++
.../ranger/plugin/model/RangerPolicyHeader.java | 134 ++++++++
.../org/apache/ranger/common/RangerSearchUtil.java | 2 +-
.../main/java/org/apache/ranger/rest/GdsREST.java | 361 ++++++++++++++++++++-
.../ranger/security/context/RangerAPIList.java | 2 +
.../java/org/apache/ranger/rest/TestGdsREST.java | 253 +++++++++++++++
6 files changed, 849 insertions(+), 6 deletions(-)
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerGrant.java
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerGrant.java
new file mode 100644
index 000000000..a8c63b759
--- /dev/null
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerGrant.java
@@ -0,0 +1,103 @@
+/*
+ * 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.ranger.plugin.model;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import java.util.List;
+import java.util.Objects;
+
+@JsonAutoDetect(getterVisibility= JsonAutoDetect.Visibility.NONE,
setterVisibility= JsonAutoDetect.Visibility.NONE, fieldVisibility=
JsonAutoDetect.Visibility.ANY)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class RangerGrant implements java.io.Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private RangerPrincipal principal;
+ private List<String> accessTypes;
+ private List<String> conditions;
+
+ public RangerGrant() {
+ this(null, null, null);
+ }
+
+ public RangerGrant(RangerPrincipal principal, List<String> accessTypes,
List<String> conditions) {
+ this.principal = principal;
+ this.accessTypes = accessTypes;
+ this.conditions = conditions;
+ }
+
+ public RangerPrincipal getPrincipal() {
+ return principal;
+ }
+
+ public void setPrincipal(RangerPrincipal principal) {
+ this.principal = principal;
+ }
+
+ public List<String> getAccessTypes() {
+ return accessTypes;
+ }
+
+ public void setAccessTypes(List<String> accessTypes) {
+ this.accessTypes = accessTypes;
+ }
+
+ public List<String> getConditions() {
+ return conditions;
+ }
+
+ public void setConditions(List<String> conditions) {
+ this.conditions = conditions;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(principal, accessTypes, conditions);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj == null) {
+ return false;
+ } else if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ RangerGrant other = (RangerGrant) obj;
+
+ return Objects.equals(principal, other.principal) &&
+ Objects.equals(accessTypes, other.accessTypes) &&
+ Objects.equals(conditions, other.conditions);
+ }
+
+ @Override
+ public String toString() {
+ return "RangerGrant{" +
+ "principal='" + principal.toString() +
+ ", accessTypes=" + accessTypes +
+ ", conditions=" + conditions +
+ '}';
+ }
+}
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerPolicyHeader.java
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerPolicyHeader.java
new file mode 100644
index 000000000..b8fdf102d
--- /dev/null
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/model/RangerPolicyHeader.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ranger.plugin.model;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+@JsonAutoDetect(fieldVisibility=Visibility.ANY)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+@JsonIgnoreProperties(ignoreUnknown=true)
+public class RangerPolicyHeader extends RangerBaseModelObject implements
java.io.Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private String service;
+ private String name;
+ private Integer policyType;
+ private String zoneName;
+ private Map<String, RangerPolicyResource> resources;
+
+ public RangerPolicyHeader(RangerPolicy rangerPolicy) {
+ super();
+
+ setId(rangerPolicy.getId());
+ setGuid(rangerPolicy.getGuid());
+ setName(rangerPolicy.getName());
+ setResources(rangerPolicy.getResources());
+ setIsEnabled(rangerPolicy.getIsEnabled());
+ setService(rangerPolicy.getService());
+ setVersion(rangerPolicy.getVersion());
+ setPolicyType(rangerPolicy.getPolicyType());
+ setZoneName(rangerPolicy.getZoneName());
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public void setService(String service) {
+ this.service = service;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getPolicyType() {
+ return policyType;
+ }
+
+ public void setPolicyType(Integer policyType) {
+ this.policyType = policyType;
+ }
+
+ public String getZoneName() {
+ return zoneName;
+ }
+
+ public void setZoneName(String zoneName) {
+ this.zoneName = zoneName;
+ }
+
+ public Map<String, RangerPolicyResource> getResources() {
+ return resources;
+ }
+
+ public void setResources(Map<String, RangerPolicyResource> resources) {
+ if (this.resources == null) {
+ this.resources = new HashMap<>();
+ }
+
+ if (this.resources == resources) {
+ return;
+ }
+
+ this.resources.clear();
+
+ if (resources != null) {
+ for (Map.Entry<String, RangerPolicyResource> e :
resources.entrySet()) {
+ this.resources.put(e.getKey(), e.getValue());
+ }
+ }
+ }
+
+ public StringBuilder toString(StringBuilder sb) {
+ sb.append("id={").append(getId()).append("} ");
+ sb.append("guid={").append(getGuid()).append("} ");
+ sb.append("name={").append(name).append("} ");
+ sb.append("resources={");
+ if (resources != null) {
+ for (Map.Entry<String, RangerPolicyResource> e :
resources.entrySet()) {
+ sb.append(e.getKey()).append("={");
+ e.getValue().toString(sb);
+ sb.append("} ");
+ }
+ }
+ sb.append("} ");
+ sb.append("isEnabled={").append(getIsEnabled()).append("} ");
+ sb.append("service={").append(service).append("} ");
+ sb.append("version={").append(name).append("} ");
+ sb.append("policyType={").append(policyType).append("} ");
+ sb.append("zoneName={").append(zoneName).append("} ");
+
+ return sb;
+ }
+}
diff --git
a/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
b/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
index a6c6746b3..0f143e303 100755
---
a/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
+++
b/security-admin/src/main/java/org/apache/ranger/common/RangerSearchUtil.java
@@ -699,7 +699,7 @@ public class RangerSearchUtil extends SearchUtil {
* @param paramName
* @return
*/
- String[] getParamMultiValues(HttpServletRequest request, String
paramName) {
+ public String[] getParamMultiValues(HttpServletRequest request, String
paramName) {
String[] values = request.getParameterValues(paramName);
if (values == null || values.length == 0) {
diff --git a/security-admin/src/main/java/org/apache/ranger/rest/GdsREST.java
b/security-admin/src/main/java/org/apache/ranger/rest/GdsREST.java
index c66429834..f9593b469 100755
--- a/security-admin/src/main/java/org/apache/ranger/rest/GdsREST.java
+++ b/security-admin/src/main/java/org/apache/ranger/rest/GdsREST.java
@@ -20,7 +20,10 @@
package org.apache.ranger.rest;
import org.apache.commons.collections4.CollectionUtils;
+import java.util.function.Predicate;
+import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
+import
org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting;
import org.apache.ranger.authorization.hadoop.config.RangerAdminConfig;
import org.apache.ranger.biz.AssetMgr;
import org.apache.ranger.biz.GdsDBStore;
@@ -31,17 +34,25 @@ import org.apache.ranger.common.RESTErrorUtil;
import org.apache.ranger.common.RangerSearchUtil;
import org.apache.ranger.common.ServiceUtil;
import org.apache.ranger.plugin.model.RangerGds;
+import org.apache.ranger.plugin.model.RangerGds.DatasetSummary;
+import org.apache.ranger.plugin.model.RangerGds.DataShareSummary;
+import org.apache.ranger.plugin.model.RangerGds.DataShareInDatasetSummary;
import org.apache.ranger.plugin.model.RangerGds.RangerDataset;
import org.apache.ranger.plugin.model.RangerGds.RangerDatasetInProject;
import org.apache.ranger.plugin.model.RangerGds.RangerDataShareInDataset;
import org.apache.ranger.plugin.model.RangerGds.RangerDataShare;
import org.apache.ranger.plugin.model.RangerGds.RangerProject;
import org.apache.ranger.plugin.model.RangerGds.RangerSharedResource;
+import org.apache.ranger.plugin.model.RangerGrant;
+
import org.apache.ranger.plugin.model.RangerPluginInfo;
import org.apache.ranger.plugin.model.RangerPolicy;
-import org.apache.ranger.plugin.model.RangerGds.DatasetSummary;
-import org.apache.ranger.plugin.model.RangerGds.DataShareSummary;
-import org.apache.ranger.plugin.model.RangerGds.DataShareInDatasetSummary;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemAccess;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemCondition;
+import org.apache.ranger.plugin.model.RangerPolicyHeader;
+import org.apache.ranger.plugin.model.RangerPrincipal;
+import org.apache.ranger.plugin.model.RangerPrincipal.PrincipalType;
import org.apache.ranger.plugin.model.RangerSecurityZone;
import org.apache.ranger.plugin.model.RangerService;
import org.apache.ranger.plugin.store.PList;
@@ -70,11 +81,15 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
-import java.util.Arrays;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
@Path("gds")
@Component
@@ -88,7 +103,15 @@ public class GdsREST {
private final int SHARED_RESOURCES_MAX_BATCH_SIZE =
config.getInt("ranger.admin.rest.gds.shared.resources.max.batch.size", 100);
- public static final String EMPTY_STRING = "";
+ private static final String PRINCIPAL_TYPE_USER =
RangerPrincipal.PrincipalType.USER.name().toLowerCase();
+
+ private static final String PRINCIPAL_TYPE_GROUP =
RangerPrincipal.PrincipalType.GROUP.name().toLowerCase();
+
+ private static final String PRINCIPAL_TYPE_ROLE =
RangerPrincipal.PrincipalType.ROLE.name().toLowerCase();
+
+ private static final String DEFAULT_PRINCIPAL_TYPE = PRINCIPAL_TYPE_USER;
+
+ public static final String GDS_POLICY_EXPR_CONDITION = "expression";
@Autowired
GdsDBStore gdsStore;
@@ -1873,4 +1896,332 @@ public class GdsREST {
return ret;
}
+
+ @GET
+ @Path("/dataset/{id}/grants")
+ @Produces({"application/json"})
+ @PreAuthorize("@rangerPreAuthSecurityHandler.isAPIAccessible(\"" +
RangerAPIList.GET_DATASET_GRANTS + "\")")
+ public List<RangerGrant> getDataSetGrants(@PathParam("id") Long id,
@Context HttpServletRequest request) {
+ LOG.debug("==> GdsREST.getDataSetGrants(dataSetId: {})", id);
+
+ RangerPerfTracer perf = null;
+ List<RangerGrant> ret = null;
+
+ try {
+ if (RangerPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+ perf = RangerPerfTracer.getPerfTracer(PERF_LOG,
"GdsREST.getDataSetGrants( DataSetId: " + id + ")");
+ }
+
+ List<RangerPolicy> policies = gdsStore.getDatasetPolicies(id);
+
+ if (CollectionUtils.isNotEmpty(policies)) {
+ List<RangerPolicyItem> filteredPolicyItems =
filterPolicyItemsByRequest(policies.get(0), request);
+
+ if (CollectionUtils.isNotEmpty(filteredPolicyItems)) {
+ ret = transformPolicyItemsToGrants(filteredPolicyItems);
+ } else {
+ LOG.debug("getDataSetGrants(): no grants available in
dataset(id={}), policy(id={}) for query {}", id, policies.get(0).getId(),
request.getQueryString());
+ }
+ } else {
+ LOG.debug("getDataSetGrants(): no policy found for
dataset(id={})", id);
+ }
+
+ } catch (WebApplicationException excp) {
+ throw excp;
+ } catch (Throwable excp) {
+ LOG.error("getDataSetGrants (dataSetId: {}) failed!..error: {}",
id, excp);
+ throw restErrorUtil.createRESTException(excp.getMessage());
+ } finally {
+ RangerPerfTracer.log(perf);
+ }
+
+ LOG.debug("<== GdsREST.getDataSetGrants(dataSetId: {}): ret= {}", id,
ret);
+
+ return ret != null ? ret : Collections.emptyList();
+ }
+
+ @PUT
+ @Path("/dataset/{id}/grant")
+ @Consumes({"application/json"})
+ @Produces({"application/json"})
+ @PreAuthorize("@rangerPreAuthSecurityHandler.isAPIAccessible(\"" +
RangerAPIList.UPDATE_DATASET_GRANTS + "\")")
+ public RangerPolicyHeader updateDataSetGrants(@PathParam("id") Long id,
List<RangerGrant> rangerGrants) {
+ LOG.debug("==> GdsREST.updateDataSetGrants(dataSetId: {},
rangerGrants: {})", id, rangerGrants);
+
+ RangerPerfTracer perf = null;
+ RangerPolicyHeader ret = null;
+
+ try {
+ if (RangerPerfTracer.isPerfTraceEnabled(PERF_LOG)) {
+ perf = RangerPerfTracer.getPerfTracer(PERF_LOG,
"GdsREST.updateDataSetGrants( DataSetId: " + id + "rangerGrants: " +
rangerGrants + ")");
+ }
+
+ List<RangerPolicy> policies = gdsStore.getDatasetPolicies(id);
+ RangerPolicy policy = CollectionUtils.isNotEmpty(policies) ?
policies.get(0) : gdsStore.addDatasetPolicy(id, new RangerPolicy());
+ RangerPolicy policyWithModifiedGrants =
updatePolicyWithModifiedGrants(policy, rangerGrants);
+
+ if (policyWithModifiedGrants != null) {
+ RangerPolicy updatedPolicy = gdsStore.updateDatasetPolicy(id,
policyWithModifiedGrants);
+ ret = rangerPolicyHeaderOf(updatedPolicy);
+ } else {
+ throw
restErrorUtil.createRESTException(HttpServletResponse.SC_NOT_MODIFIED, "No
action performed: The grant may already exist or may not be found for
deletion.", false);
+ }
+ } catch (WebApplicationException excp) {
+ throw excp;
+ } catch (Throwable excp) {
+ LOG.error("updateDataSetGrants (dataSetId: {}, rangerGrants: {})
failed!..error: {}", id, rangerGrants, excp);
+ throw restErrorUtil.createRESTException(excp.getMessage());
+ } finally {
+ RangerPerfTracer.log(perf);
+ }
+
+ LOG.debug("<== GdsREST.updateDataSetGrants(dataSetId: {},
rangerGrants: {}): ret= {}", id, rangerGrants, ret);
+
+ return ret;
+ }
+
+ @VisibleForTesting
+ List<RangerPolicyItem> filterPolicyItemsByRequest(RangerPolicy
rangerPolicy, HttpServletRequest request) {
+ LOG.debug("==> GdsREST.filterPolicyItemsByRequest(rangerPolicy: {})",
rangerPolicy);
+
+ if (rangerPolicy == null ||
CollectionUtils.isEmpty(rangerPolicy.getPolicyItems())) {
+ return Collections.emptyList();
+ }
+
+ List<RangerPolicyItem> policyItems = rangerPolicy.getPolicyItems();
+ String[] filteringPrincipals =
searchUtil.getParamMultiValues(request, "principal");
+ String[] filteringAccessTypes =
searchUtil.getParamMultiValues(request, "accessType");
+
+ Predicate<RangerPolicyItem> byPrincipalPredicate =
filterByPrincipalsPredicate(filteringPrincipals);
+ Predicate<RangerPolicyItem> byAccessTypePredicate =
filterByAccessTypesPredicate(filteringAccessTypes);
+
+ List<RangerPolicyItem> filteredPolicyItems = policyItems.stream()
+ .filter(byPrincipalPredicate.and(byAccessTypePredicate))
+ .collect(Collectors.toList());
+
+ LOG.debug("<== GdsREST.filterPolicyItemsByRequest(rangerPolicy: {}):
filteredPolicyItems= {}", rangerPolicy, filteredPolicyItems);
+
+ return filteredPolicyItems;
+ }
+
+ @VisibleForTesting
+ List<RangerGrant> transformPolicyItemsToGrants(List<RangerPolicyItem>
policyItems) {
+ LOG.debug("==> GdsREST.transformPolicyItemsToGrants(policyItems: {})",
policyItems);
+ if (CollectionUtils.isEmpty(policyItems)) {
+ return null;
+ }
+
+ List<RangerGrant> ret = new ArrayList<>();
+
+ for (RangerPolicyItem policyItem : policyItems) {
+ List<String> policyItemUsers = policyItem.getUsers();
+ List<String> policyItemGroups = policyItem.getGroups();
+ List<String> policyItemRoles = policyItem.getRoles();
+
+ List<RangerPolicyItemAccess> policyItemAccesses =
policyItem.getAccesses();
+ List<RangerPolicyItemCondition> policyItemConditions =
policyItem.getConditions();
+
+ List<String> policyItemAccessTypes =
policyItemAccesses.stream().map(x -> x.getType()).collect(Collectors.toList());
+ List<String> policyItemConditionValues =
policyItemConditions.stream().flatMap(x ->
x.getValues().stream()).collect(Collectors.toList());
+
+ if (CollectionUtils.isNotEmpty(policyItemUsers)) {
+ policyItemUsers.stream().forEach(x -> ret.add(new
RangerGrant(new RangerPrincipal(RangerPrincipal.PrincipalType.USER, x),
policyItemAccessTypes, policyItemConditionValues)));
+ }
+
+ if (CollectionUtils.isNotEmpty(policyItemGroups)) {
+ policyItemGroups.stream().forEach(x -> ret.add(new
RangerGrant(new RangerPrincipal(RangerPrincipal.PrincipalType.GROUP, x),
policyItemAccessTypes, policyItemConditionValues)));
+ }
+
+ if (CollectionUtils.isNotEmpty(policyItemRoles)) {
+ policyItemRoles.stream().forEach(x -> ret.add(new
RangerGrant(new RangerPrincipal(RangerPrincipal.PrincipalType.ROLE, x),
policyItemAccessTypes, policyItemConditionValues)));
+ }
+ }
+
+ LOG.debug("<== GdsREST.transformPolicyItemsToGrants(policyItems: {}):
ret= {}", policyItems, ret);
+
+ return ret;
+ }
+
+ private RangerPolicyHeader rangerPolicyHeaderOf(RangerPolicy rangerPolicy)
{
+ LOG.debug("==> GdsREST.rangerPolicyHeaderOf(rangerPolicy: {})",
rangerPolicy);
+
+ RangerPolicyHeader ret = null;
+ if (rangerPolicy != null) {
+ ret = new RangerPolicyHeader(rangerPolicy);
+ }
+
+ LOG.debug("<== GdsREST.rangerPolicyHeaderOf(rangerPolicy: {}): ret=
{}", rangerPolicy, ret);
+ return ret;
+ }
+
+ @VisibleForTesting
+ RangerPolicy updatePolicyWithModifiedGrants(RangerPolicy policy,
List<RangerGrant> rangerGrants) {
+ LOG.debug("==> GdsREST.updatePolicyWithModifiedGrants(policy: {},
rangerGrants: {})", policy, rangerGrants);
+ try {
+ List<RangerPolicyItem> policyItems = policy.getPolicyItems();
+ List<RangerPolicyItem> policyItemsToUpdate =
policyItems.stream().map(this::copyOf).collect(Collectors.toList());
+
+ Set<RangerPrincipal> principalsToUpdate =
rangerGrants.stream().map(RangerGrant::getPrincipal).collect(Collectors.toSet());
+
+ for (RangerPrincipal principal : principalsToUpdate) {
+ List<RangerPolicyItem> policyItemsToRemove = new ArrayList<>();
+ policyItemsToUpdate.stream()
+ .filter(matchesPrincipalPredicate(principal))
+ .forEach(policyItem -> {
+ removeMatchingPrincipalFromPolicyItem(policyItem,
principal);
+ if (isPolicyItemEmpty(policyItem)) {
+ policyItemsToRemove.add(policyItem);
+ }
+
+ });
+ policyItemsToUpdate.removeAll(policyItemsToRemove);
+ }
+
+ for (RangerGrant grant : rangerGrants) {
+ if (hasAccessTypes(grant)) {
+ policyItemsToUpdate.add(transformGrantToPolicyItem(grant));
+ }
+ }
+
+ if (CollectionUtils.isEqualCollection(policyItems,
policyItemsToUpdate)) {
+ // Skip DataSet update if no policy changes detected, avoiding
unnecessary updates.
+ policy = null;
+ } else {
+ policy.setPolicyItems(policyItemsToUpdate);
+ }
+ } catch (Exception e) {
+ throw
restErrorUtil.createRESTException(HttpServletResponse.SC_BAD_REQUEST,
e.getMessage(), true);
+ }
+ LOG.debug("<== GdsREST.updatePolicyWithModifiedGrants(updatedPolicy:
{})", policy);
+ return policy;
+ }
+
+ private boolean isPolicyItemEmpty(RangerPolicyItem policyItem) {
+ return CollectionUtils.isEmpty(policyItem.getUsers()) &&
+ CollectionUtils.isEmpty(policyItem.getGroups()) &&
+ CollectionUtils.isEmpty(policyItem.getRoles());
+ }
+
+ private void removeMatchingPrincipalFromPolicyItem(RangerPolicyItem
policyItem, RangerPrincipal principal) {
+ String principalName = principal.getName();
+ PrincipalType principalType = principal.getType();
+
+ if (principalType == PrincipalType.USER && policyItem.getUsers() !=
null) {
+ policyItem.getUsers().remove(principalName);
+ } else if (principalType == PrincipalType.GROUP &&
policyItem.getGroups() != null) {
+ policyItem.getGroups().remove(principalName);
+ } else if (principalType == PrincipalType.ROLE &&
policyItem.getRoles() != null) {
+ policyItem.getRoles().remove(principalName);
+ }
+ }
+
+ private RangerPolicyItem transformGrantToPolicyItem(RangerGrant grant) {
+ LOG.debug("==> GdsREST.transformGrantToPolicyItem(grant: {})", grant);
+
+ if (grant == null) {
+ return null;
+ }
+
+ RangerPolicyItem policyItem = new RangerPolicyItem();
+
+ List<String> permissions = grant.getAccessTypes();
+ if (CollectionUtils.isNotEmpty(permissions)) {
+ policyItem.setAccesses(permissions.stream()
+ .map(accessType -> new RangerPolicyItemAccess(accessType,
true))
+ .collect(Collectors.toList()));
+ }
+
+ List<String> conditions = grant.getConditions();
+ if (CollectionUtils.isNotEmpty(conditions)) {
+ policyItem.setConditions(conditions.stream()
+ .map(condition -> new
RangerPolicyItemCondition(GDS_POLICY_EXPR_CONDITION,
Collections.singletonList(condition)))
+ .collect(Collectors.toList()));
+ }
+
+ switch (grant.getPrincipal().getType()) {
+ case USER:
+
policyItem.setUsers(Collections.singletonList(grant.getPrincipal().getName()));
+ break;
+ case GROUP:
+
policyItem.setGroups(Collections.singletonList(grant.getPrincipal().getName()));
+ break;
+ case ROLE:
+
policyItem.setRoles(Collections.singletonList(grant.getPrincipal().getName()));
+ break;
+ }
+
+ LOG.debug("<== GdsREST.transformGrantToPolicyItem(grant: {}):
policyItem= {}", grant, policyItem);
+
+ return policyItem;
+ }
+
+ private Predicate<RangerPolicyItem>
matchesPrincipalPredicate(RangerPrincipal principal) {
+ String principalName = principal.getName();
+ PrincipalType principalType = principal.getType();
+
+ return policyItem -> {
+ switch (principalType) {
+ case USER:
+ return policyItem.getUsers().contains(principalName);
+ case GROUP:
+ return policyItem.getGroups().contains(principalName);
+ case ROLE:
+ return policyItem.getRoles().contains(principalName);
+ }
+ return false;
+ };
+ }
+
+ private boolean hasAccessTypes(RangerGrant grant) {
+ return grant.getAccessTypes() != null &&
!grant.getAccessTypes().isEmpty();
+ }
+
+ private Predicate<RangerPolicyItem> filterByPrincipalsPredicate(String[]
filteringPrincipals) {
+ if (ArrayUtils.isEmpty(filteringPrincipals)) {
+ return policyItem -> true; // No filtering by principal if no
principals specified
+ }
+
+ Map<String, Set<String>> principalCriteriaMap = new HashMap<>();
+ for (String principal : filteringPrincipals) {
+ String[] parts = principal.split(":");
+ String principalType = parts.length > 1 ? parts[0] :
DEFAULT_PRINCIPAL_TYPE;
+ String principalName = parts.length > 1 ? parts[1] : parts[0];
+
+ principalCriteriaMap
+ .computeIfAbsent(principalType.toLowerCase(), k -> new
HashSet<>())
+ .add(principalName);
+ }
+
+ return policyItem -> {
+ Set<String> users =
principalCriteriaMap.getOrDefault(PRINCIPAL_TYPE_USER, Collections.emptySet());
+ Set<String> groups =
principalCriteriaMap.getOrDefault(PRINCIPAL_TYPE_GROUP, Collections.emptySet());
+ Set<String> roles =
principalCriteriaMap.getOrDefault(PRINCIPAL_TYPE_ROLE, Collections.emptySet());
+
+ return (policyItem.getUsers() != null &&
policyItem.getUsers().stream().anyMatch(users::contains)) ||
+ (policyItem.getGroups() != null &&
policyItem.getGroups().stream().anyMatch(groups::contains)) ||
+ (policyItem.getRoles() != null &&
policyItem.getRoles().stream().anyMatch(roles::contains));
+ };
+ }
+
+ private Predicate<RangerPolicyItem> filterByAccessTypesPredicate(String[]
filteringAccessTypes) {
+ if (ArrayUtils.isEmpty(filteringAccessTypes)) {
+ return policyItem -> true; // No filtering by access type if no
access types specified
+ }
+
+ Set<String> accessTypeSet = new
HashSet<>(Arrays.asList(filteringAccessTypes));
+ return policyItem -> policyItem.getAccesses().stream()
+ .anyMatch(access -> accessTypeSet.contains(access.getType()));
+ }
+
+ private RangerPolicyItem copyOf(RangerPolicyItem policyItem) {
+ RangerPolicyItem copy = new RangerPolicyItem();
+ copy.setAccesses(new ArrayList<>(policyItem.getAccesses()));
+ copy.setUsers(new ArrayList<>(policyItem.getUsers()));
+ copy.setGroups(new ArrayList<>(policyItem.getGroups()));
+ copy.setRoles(new ArrayList<>(policyItem.getRoles()));
+ copy.setConditions(new ArrayList<>(policyItem.getConditions()));
+ copy.setDelegateAdmin(new Boolean(policyItem.getDelegateAdmin()));
+
+ return copy;
+ }
}
diff --git
a/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIList.java
b/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIList.java
index acfce5f0a..509ed58f4 100755
---
a/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIList.java
+++
b/security-admin/src/main/java/org/apache/ranger/security/context/RangerAPIList.java
@@ -254,6 +254,8 @@ public class RangerAPIList {
public static final String REMOVE_DATASET_IN_PROJECT =
"GdsREST.removeDatasetInProject";
public static final String GET_DATASET_IN_PROJECT =
"GdsREST.getDatasetInProject";
public static final String SEARCH_DATASET_IN_PROJECT =
"GdsREST.searchDatasetInProject";
+ public static final String GET_DATASET_GRANTS =
"GdsREST.getDataSetGrants";
+ public static final String UPDATE_DATASET_GRANTS =
"GdsREST.updateDataSetGrants";
/**
* List of APIs for PublicAPIsv2
diff --git
a/security-admin/src/test/java/org/apache/ranger/rest/TestGdsREST.java
b/security-admin/src/test/java/org/apache/ranger/rest/TestGdsREST.java
new file mode 100644
index 000000000..cdfce50b9
--- /dev/null
+++ b/security-admin/src/test/java/org/apache/ranger/rest/TestGdsREST.java
@@ -0,0 +1,253 @@
+/*
+ * 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.ranger.rest;
+import org.apache.ranger.common.RangerSearchUtil;
+import org.apache.ranger.plugin.model.RangerGds;
+import org.apache.ranger.plugin.model.RangerGrant;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerPrincipal;
+
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+
+
+
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+@RunWith(MockitoJUnitRunner.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class TestGdsREST {
+
+ @InjectMocks
+ private GdsREST gdsREST = new GdsREST();
+
+ @Mock
+ RangerSearchUtil searchUtil;
+
+ private final HttpServletRequest request =
Mockito.mock(HttpServletRequest.class);;
+
+ @Test
+ public void testAddDataSetGrants() throws Exception {
+
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+
+ List<RangerPolicy.RangerPolicyItem> policyItems = new
ArrayList<>(policy.getPolicyItems());
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ List<RangerPolicy.RangerPolicyItem> updatedPolicyItems =
policy.getPolicyItems();
+
+ assertNotEquals(policyItems, updatedPolicyItems);
+
+ assertEquals(policyItems.size() + rangerGrants.size(),
updatedPolicyItems.size());
+
+ List<RangerPolicy.RangerPolicyItem> filteredPolicyItems = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+ assertEquals(filteredPolicyItems, updatedPolicyItems);
+ }
+
+ @Test
+ public void testUpdateDataSetGrants() throws Exception {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ String[] requestedPrincipals = {"group:hdfs"};
+ when(searchUtil.getParamMultiValues(request,
"principal")).thenReturn(requestedPrincipals);
+
+ List<RangerPolicy.RangerPolicyItem> hdfsPolicyItems = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ RangerGrant grant3 = new RangerGrant(new
RangerPrincipal(RangerPrincipal.PrincipalType.GROUP, "hdfs"),
+ Arrays.asList("_READ"),
Collections.emptyList());
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
Arrays.asList(grant3));
+
+ List<RangerPolicy.RangerPolicyItem> updatedHdfsPolicyItems =
new ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ assertNotNull(updatedHdfsPolicyItems);
+ assertEquals(hdfsPolicyItems.size(),
updatedHdfsPolicyItems.size());
+ assertNotEquals(hdfsPolicyItems, updatedHdfsPolicyItems);
+ }
+
+ @Test
+ public void testRemoveDataSetGrants() throws Exception {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+ List<RangerPolicy.RangerPolicyItem> newPolicyItems =
policy.getPolicyItems();
+
+
+ String[] requestedPrincipals = {"group:hdfs"};
+ when(searchUtil.getParamMultiValues(request,
"principal")).thenReturn(requestedPrincipals);
+
+ List<RangerPolicy.RangerPolicyItem> existingHdfsPolicyItems =
new ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ RangerGrant grant4 = new RangerGrant(new
RangerPrincipal(RangerPrincipal.PrincipalType.GROUP, "hdfs"),
+ Collections.emptyList(),
Collections.emptyList());
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
Arrays.asList(grant4));
+
+ List<RangerPolicy.RangerPolicyItem> updatedHdfsPolicyItems =
gdsREST.filterPolicyItemsByRequest(policy, request);
+
+ assertNotEquals(existingHdfsPolicyItems,
updatedHdfsPolicyItems);
+ assertTrue( "Grants for "+ Arrays.toString(requestedPrincipals)
+" should be empty", updatedHdfsPolicyItems.isEmpty());
+ }
+
+ @Test
+ public void testGetAllDataSetGrants() {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ List<RangerPolicy.RangerPolicyItem> policyItems = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+ List<RangerGrant> policyItemsAsGrants =
gdsREST.transformPolicyItemsToGrants(policyItems);
+
+ assertEquals(rangerGrants, policyItemsAsGrants);
+ }
+
+ @Test
+ public void testGetDataSetGrantsByPrincipal() throws Exception {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ String[] existingRequestedPrincipals = {"user:hive"};
+ when(searchUtil.getParamMultiValues(request,
"principal")).thenReturn(existingRequestedPrincipals);
+
+ List<RangerPolicy.RangerPolicyItem>
filteredPolicyItemsByPrincipal = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ assertTrue(filteredPolicyItemsByPrincipal.size() == 1);
+
assertTrue(filteredPolicyItemsByPrincipal.get(0).getUsers().contains("hive"));
+
+ String[] nonexistentRequestedPrincipals = {"user:hadoop"};
+ when(searchUtil.getParamMultiValues(request,
"principal")).thenReturn(nonexistentRequestedPrincipals);
+
+ filteredPolicyItemsByPrincipal = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+ assertTrue("Grants for Principals: "+
Arrays.toString(nonexistentRequestedPrincipals) +" should be empty",
filteredPolicyItemsByPrincipal.size() == 0);
+ }
+
+ @Test
+ public void testGetDataSetGrantsByAccessType() throws Exception {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ String[] requestedAccessTypes = {"_MANAGE"};
+ when(searchUtil.getParamMultiValues(request,
"accessType")).thenReturn(requestedAccessTypes);
+
+ List<RangerPolicy.RangerPolicyItem> policyItemsByAccessType =
new ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ assertTrue(policyItemsByAccessType.size() == 1);
+
assertTrue(policyItemsByAccessType.get(0).getAccesses().stream().anyMatch(x ->
Arrays.asList(requestedAccessTypes).contains(x.getType())));
+
+ String[] nonexistentRequestedAccessTypes = {"_DELETE"};
+ when(searchUtil.getParamMultiValues(request,
"accessType")).thenReturn(nonexistentRequestedAccessTypes);
+
+ List<RangerPolicy.RangerPolicyItem>
updatedPolicyItemsByAccessType = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+ assertTrue( "Grants for AccessTypes: "+
Arrays.toString(nonexistentRequestedAccessTypes) +" should be empty",
updatedPolicyItemsByAccessType.isEmpty());
+ }
+
+ @Test
+ public void testGetDataSetGrantsByPrincipalAndAccessType() throws
Exception {
+ RangerGds.RangerDataset rangerDataset = createRangerDataSet();
+ RangerPolicy policy = createPolicyForDataSet(rangerDataset);
+ List<RangerGrant> rangerGrants = createAndGetSampleGrantData();
+
+ policy = gdsREST.updatePolicyWithModifiedGrants(policy,
rangerGrants);
+
+ String[] requestedPrincipals = {"user:hive"};
+ String[] requestedAccessTypes = {"_READ"};
+
+ when(searchUtil.getParamMultiValues(request,
"principal")).thenReturn(requestedPrincipals);
+ when(searchUtil.getParamMultiValues(request,
"accessType")).thenReturn(requestedAccessTypes);
+
+ List<RangerPolicy.RangerPolicyItem> filteredPolicyItems = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+
+ assertTrue("Grants for Principals: "+
Arrays.toString(requestedPrincipals) + " and AccessTypes: "+
Arrays.toString(requestedAccessTypes) +" should exist",
filteredPolicyItems.size() == 1);
+ assertTrue("Grants for Principals: "+
Arrays.toString(requestedPrincipals) + "should exist",
filteredPolicyItems.get(0).getUsers().contains("hive"));
+ assertTrue("Grants for AccessTypes: "+
Arrays.toString(requestedAccessTypes) + "should exist",
filteredPolicyItems.get(0).getAccesses().stream().anyMatch(x ->
Arrays.asList(requestedAccessTypes).contains(x.getType())));
+
+ String[] nonexistentRequestedAccessTypes = {"_DELETE"};
+ when(searchUtil.getParamMultiValues(request,
"accessType")).thenReturn(nonexistentRequestedAccessTypes);
+
+ List<RangerPolicy.RangerPolicyItem>
updatedPolicyItemsByAccessType = new
ArrayList<>(gdsREST.filterPolicyItemsByRequest(policy, request));
+ assertTrue( "Grants for Principals: "+
Arrays.toString(requestedPrincipals) + " and AccessTypes: "+
Arrays.toString(nonexistentRequestedAccessTypes) +" should be empty",
updatedPolicyItemsByAccessType.isEmpty());
+ }
+
+ private RangerGds.RangerDataset createRangerDataSet() {
+ long id = new Random().nextInt(100);
+ RangerGds.RangerDataset dataset = new RangerGds.RangerDataset();
+ dataset.setId(id);
+ dataset.setName("dataset-" + id);
+ dataset.setGuid(UUID.randomUUID().toString());
+
+ return dataset;
+ }
+
+ private RangerPolicy createPolicyForDataSet(RangerGds.RangerDataset
dataset) {
+ RangerPolicy policy = new RangerPolicy();
+ policy.setName("DATASET: " + dataset.getName() + "@" +
System.currentTimeMillis());
+ policy.setDescription("Policy for dataset: " +
dataset.getName());
+ policy.setServiceType("gds");
+ policy.setService("_gds");
+ policy.setZoneName(null);
+ policy.setResources(Collections.singletonMap("dataset-id", new
RangerPolicy.RangerPolicyResource(dataset.getId().toString())));
+ policy.setPolicyType(RangerPolicy.POLICY_TYPE_ACCESS);
+ policy.setPolicyPriority(RangerPolicy.POLICY_PRIORITY_NORMAL);
+ policy.setAllowExceptions(Collections.emptyList());
+ policy.setDenyPolicyItems(Collections.emptyList());
+ policy.setDenyExceptions(Collections.emptyList());
+ policy.setDataMaskPolicyItems(Collections.emptyList());
+ policy.setRowFilterPolicyItems(Collections.emptyList());
+ policy.setIsDenyAllElse(Boolean.FALSE);
+
+ return policy;
+ }
+
+ private List<RangerGrant> createAndGetSampleGrantData() {
+ RangerGrant grant1 = new RangerGrant(new
RangerPrincipal(RangerPrincipal.PrincipalType.USER, "hive"),
+ Arrays.asList("_READ"),
Arrays.asList("IS_ACCESSED_BEFORE('2024/12/12')"));
+ RangerGrant grant2 = new RangerGrant(new
RangerPrincipal(RangerPrincipal.PrincipalType.GROUP, "hdfs"),
+ Arrays.asList("_MANAGE"),
Collections.emptyList());
+
+ return Arrays.asList(grant1, grant2);
+ }
+}