This is an automated email from the ASF dual-hosted git repository.
pradeepagrawal8184 pushed a commit to branch ranger-2.10
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/ranger-2.10 by this push:
new df1001251 RANGER-5793: Align delegated-admin checks for policy update
and delete when access grants are unchanged Backport of #1223 to ranger-2.10
(#1254)
df1001251 is described below
commit df100125109f981737e5eddeb0174741b66b5382
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Fri Sep 25 09:38:49 2026 +0530
RANGER-5793: Align delegated-admin checks for policy update and delete when
access grants are unchanged Backport of #1223 to ranger-2.10 (#1254)
---
.../apache/ranger/biz/RangerPolicyAdminImpl.java | 87 +++++++++++++++++-----
.../org/apache/ranger/rest/TestServiceREST.java | 34 +++++++++
2 files changed, 101 insertions(+), 20 deletions(-)
diff --git
a/security-admin/src/main/java/org/apache/ranger/biz/RangerPolicyAdminImpl.java
b/security-admin/src/main/java/org/apache/ranger/biz/RangerPolicyAdminImpl.java
index 55a1a01cc..7997b0918 100644
---
a/security-admin/src/main/java/org/apache/ranger/biz/RangerPolicyAdminImpl.java
+++
b/security-admin/src/main/java/org/apache/ranger/biz/RangerPolicyAdminImpl.java
@@ -56,10 +56,12 @@
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
public class RangerPolicyAdminImpl implements RangerPolicyAdmin {
@@ -222,7 +224,7 @@ boolean isDelegatedAdminAccessAllowed(RangerPolicy policy,
String user, Set<Stri
String oldResourceSignature =
getResourceSignature(oldPolicy);
String newResourceSignature =
getResourceSignature(policy);
- if (StringUtils.equals(oldResourceSignature,
newResourceSignature)) {
+ if (StringUtils.equals(oldResourceSignature,
newResourceSignature) && !isPolicyScopeChanged(oldPolicy, policy)) {
Set<String> modifiedAccessTypes =
getAllModifiedAccessTypes(oldPolicy, policy, getServiceDef());
ret =
isDelegatedAdminAccessAllowedForPolicy(matchedRepository, policy, user,
userGroups, roles, modifiedAccessTypes, false, evalContext);
} else {
@@ -858,7 +860,9 @@ private Set<String> getAllModifiedAccessTypes(RangerPolicy
oldPolicy, RangerPoli
ret.addAll(getAccessTypesDiff(newRoleAccesses, oldRoleAccesses));
if (ret.isEmpty()) {
- ret.add(RangerPolicyEngine.ADMIN_ACCESS);
+ // Empty access-type delta (delete, or updates that do not
add/remove grants) must be authorized against
+ // every access-type in the stored policy, not a generic _admin
token.
+ ret.addAll(getAllAccessTypes(oldPolicy, serviceDef));
}
return ret;
}
@@ -894,31 +898,18 @@ private void collectAccessTypes(Map<String,
Collection<String>> expandedAccesses
accessTypes.addAll(expandedAccesses.get(access.getType()));
}
+ // each principal must get its own copy: a shared instance would
let a later addAll() for one principal
+ // leak access-types into every other principal of this item and
make the diff miss real changes
for (String user : item.getUsers()) {
- Set<String> oldAccesses = userAccesses.get(user);
- if (oldAccesses != null) {
- oldAccesses.addAll(accessTypes);
- } else {
- userAccesses.put(user, accessTypes);
- }
+ userAccesses.computeIfAbsent(user, k -> new
HashSet<>()).addAll(accessTypes);
}
for (String group : item.getGroups()) {
- Set<String> oldAccesses = groupAccesses.get(group);
- if (oldAccesses != null) {
- oldAccesses.addAll(accessTypes);
- } else {
- groupAccesses.put(group, accessTypes);
- }
+ groupAccesses.computeIfAbsent(group, k -> new
HashSet<>()).addAll(accessTypes);
}
for (String role : item.getRoles()) {
- Set<String> oldAccesses = roleAccesses.get(role);
- if (oldAccesses != null) {
- oldAccesses.addAll(accessTypes);
- } else {
- roleAccesses.put(role, accessTypes);
- }
+ roleAccesses.computeIfAbsent(role, k -> new
HashSet<>()).addAll(accessTypes);
}
}
}
@@ -947,6 +938,62 @@ private Set<String> getAccessTypesDiff(Map<String,
Set<String>> newAccessesMap,
return ret;
}
+ private boolean isPolicyScopeChanged(RangerPolicy oldPolicy, RangerPolicy
policy) {
+ boolean ret = !Objects.equals(oldPolicy.getIsEnabled() == null ||
oldPolicy.getIsEnabled(), policy.getIsEnabled() == null ||
policy.getIsEnabled())
+ || !Objects.equals(oldPolicy.getPolicyPriority() == null ?
RangerPolicy.POLICY_PRIORITY_NORMAL : oldPolicy.getPolicyPriority(),
policy.getPolicyPriority() == null ? RangerPolicy.POLICY_PRIORITY_NORMAL :
policy.getPolicyPriority())
+ ||
!Objects.equals(Boolean.TRUE.equals(oldPolicy.getIsDenyAllElse()),
Boolean.TRUE.equals(policy.getIsDenyAllElse()))
+ || !Objects.equals(nullToEmpty(oldPolicy.getConditions()),
nullToEmpty(policy.getConditions()))
+ ||
!Objects.equals(toStrings(oldPolicy.getValiditySchedules()),
toStrings(policy.getValiditySchedules()))
+ || !Objects.equals(getDelegateAdminPrincipals(oldPolicy),
getDelegateAdminPrincipals(policy));
+ if (ret) {
+ LOG.debug("isPolicyScopeChanged(policy-id:[{}]):
isEnabled/policyPriority/isDenyAllElse/conditions/validitySchedules/delegateAdmin
changed", policy.getId());
+ }
+ return ret;
+ }
+
+ private static <T> List<T> nullToEmpty(List<T> list) {
+ return list == null ? Collections.emptyList() : list;
+ }
+
+ private static List<String> toStrings(List<?> list) {
+ List<String> ret = new ArrayList<>();
+ if (list != null) {
+ for (Object obj : list) {
+ ret.add(String.valueOf(obj));
+ }
+ }
+ return ret;
+ }
+
+ private static Set<String> getDelegateAdminPrincipals(RangerPolicy policy)
{
+ Set<String> ret = new HashSet<>();
+ collectDelegateAdminPrincipals(policy.getPolicyItems(), ret);
+ collectDelegateAdminPrincipals(policy.getDenyPolicyItems(), ret);
+ collectDelegateAdminPrincipals(policy.getAllowExceptions(), ret);
+ collectDelegateAdminPrincipals(policy.getDenyExceptions(), ret);
+ collectDelegateAdminPrincipals(policy.getDataMaskPolicyItems(), ret);
+ collectDelegateAdminPrincipals(policy.getRowFilterPolicyItems(), ret);
+ return ret;
+ }
+
+ private static void collectDelegateAdminPrincipals(List<? extends
RangerPolicy.RangerPolicyItem> policyItems, Set<String> ret) {
+ if (policyItems != null) {
+ for (RangerPolicy.RangerPolicyItem item : policyItems) {
+ if (Boolean.TRUE.equals(item.getDelegateAdmin())) {
+ for (String user : nullToEmpty(item.getUsers())) {
+ ret.add("user:" + user);
+ }
+ for (String group : nullToEmpty(item.getGroups())) {
+ ret.add("group:" + group);
+ }
+ for (String role : nullToEmpty(item.getRoles())) {
+ ret.add("role:" + role);
+ }
+ }
+ }
+ }
+ }
+
private String getResourceSignature(final RangerPolicy policy) {
return
RangerPolicyResourceSignature.toSignatureString(policy.getResources(),
policy.getAdditionalResources());
}
diff --git
a/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
b/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
index f70fe27dd..026d10c57 100644
--- a/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
+++ b/security-admin/src/test/java/org/apache/ranger/rest/TestServiceREST.java
@@ -1032,6 +1032,40 @@ public void test18deletePolicyFalse() throws Exception {
Mockito.verify(validatorFactory).getPolicyValidator(svcStore);
}
+ @Test
+ public void
testDeletePolicy_delegatedAdminWithoutModifyAccessIsForbidden() throws
Exception {
+ // DELETE /service/plugins/policies/{id} by a non-admin
delegated-admin whose grants do not
+ // cover the policy's access-types. deletePolicy() must route
the stored policy through the modify check and
+ // must not delete when that check fails.
+ RangerPolicy rangerPolicy = rangerPolicy();
+ ServiceREST spy = Mockito.spy(serviceREST);
+
+
Mockito.when(validatorFactory.getPolicyValidator(svcStore)).thenReturn(policyValidator);
+ Mockito.when(svcStore.getPolicy(Id)).thenReturn(rangerPolicy);
+
Mockito.when(bizUtil.getCurrentUserLoginId()).thenReturn("alice");
+ Mockito.when(bizUtil.isAdmin()).thenReturn(false);
+ Mockito.when(bizUtil.isKeyAdmin()).thenReturn(false);
+ Mockito.when(svcStore.isServiceAdminUser(Mockito.anyString(),
Mockito.anyString())).thenReturn(false);
+
Mockito.when(userMgr.getGroupsForUser(Mockito.anyString())).thenReturn(new
HashSet<String>());
+
+ RangerPolicyAdmin policyAdmin =
Mockito.mock(RangerPolicyAdmin.class);
+
Mockito.doReturn(policyAdmin).when(spy).getPolicyAdminForDelegatedAdmin(Mockito.anyString());
+
Mockito.when(policyAdmin.getRolesFromUserAndGroups(Mockito.anyString(),
Mockito.anySet())).thenReturn(new HashSet<String>());
+
Mockito.when(policyAdmin.isDelegatedAdminAccessAllowedForModify(Mockito.same(rangerPolicy),
Mockito.eq("alice"), Mockito.anySet(), Mockito.anySet(),
Mockito.anyMap())).thenReturn(false);
+
Mockito.when(restErrorUtil.createRESTException(Mockito.eq(HttpServletResponse.SC_FORBIDDEN),
Mockito.anyString(), Mockito.eq(true)))
+ .thenReturn(new
WebApplicationException(HttpServletResponse.SC_FORBIDDEN));
+
+ try {
+ spy.deletePolicy(Id);
+ Assert.fail("deletePolicy() must throw when the modify
check fails");
+ } catch (WebApplicationException expected) {
+ // expected
+ }
+
+
Mockito.verify(policyAdmin).isDelegatedAdminAccessAllowedForModify(Mockito.same(rangerPolicy),
Mockito.eq("alice"), Mockito.anySet(), Mockito.anySet(), Mockito.anyMap());
+ Mockito.verify(svcStore,
Mockito.never()).deletePolicy(Mockito.any(RangerPolicy.class));
+ }
+
@Test
public void test19getPolicyFalse() throws Exception {
RangerPolicy rangerPolicy = rangerPolicy();