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);
+       }
+}


Reply via email to