This is an automated email from the ASF dual-hosted git repository.
mneethiraj 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 fcc9cdc1d RANGER-5858: Authz API: return constraints in the
authorization result (#1329)
fcc9cdc1d is described below
commit fcc9cdc1df907cfca6ef159c68b3b3c504fe5c30
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Fri Oct 30 03:14:41 2026 +0530
RANGER-5858: Authz API: return constraints in the authorization result
(#1329)
---
.../ranger/authz/model/RangerAuthzResult.java | 119 +++++++++++++++++-
.../ranger/authz/model/TestRangerAuthzResult.java | 91 ++++++++++++++
.../ranger/authz/embedded/RangerAuthzPlugin.java | 30 +++++
.../embedded/TestRangerAuthzPluginConstraints.java | 139 +++++++++++++++++++++
4 files changed, 373 insertions(+), 6 deletions(-)
diff --git
a/authz-api/src/main/java/org/apache/ranger/authz/model/RangerAuthzResult.java
b/authz-api/src/main/java/org/apache/ranger/authz/model/RangerAuthzResult.java
index 0dbf6b4cb..394b2c1bf 100644
---
a/authz-api/src/main/java/org/apache/ranger/authz/model/RangerAuthzResult.java
+++
b/authz-api/src/main/java/org/apache/ranger/authz/model/RangerAuthzResult.java
@@ -24,6 +24,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -121,6 +122,7 @@ public static class PermissionResult {
private AccessResult access;
private DataMaskResult dataMask;
private RowFilterResult rowFilter;
+ private List<ConstraintResult> constraints;
private Map<String, Object> additionalInfo;
private Map<String, ResultInfo> subResources;
@@ -143,6 +145,7 @@ public PermissionResult(String permission, ResultInfo
result, Map<String, Result
this.access = result.getAccess();
this.dataMask = result.getDataMask();
this.rowFilter = result.getRowFilter();
+ this.constraints = result.getConstraints();
this.additionalInfo = result.getAdditionalInfo();
}
}
@@ -180,6 +183,14 @@ public void setRowFilter(RowFilterResult rowFilter) {
this.rowFilter = rowFilter;
}
+ public List<ConstraintResult> getConstraints() {
+ return constraints;
+ }
+
+ public void setConstraints(List<ConstraintResult> constraints) {
+ this.constraints = constraints;
+ }
+
public Map<String, Object> getAdditionalInfo() {
return additionalInfo;
}
@@ -212,7 +223,7 @@ public void addSubResourceResult(String resourceName,
ResultInfo result) {
@Override
public int hashCode() {
- return Objects.hash(permission, access, dataMask, rowFilter,
additionalInfo, subResources);
+ return Objects.hash(permission, access, dataMask, rowFilter,
constraints, additionalInfo, subResources);
}
@Override
@@ -229,6 +240,7 @@ public boolean equals(Object o) {
Objects.equals(access, that.access) &&
Objects.equals(dataMask, that.dataMask) &&
Objects.equals(rowFilter, that.rowFilter) &&
+ Objects.equals(constraints, that.constraints) &&
Objects.equals(additionalInfo, that.additionalInfo) &&
Objects.equals(subResources, that.subResources);
}
@@ -240,6 +252,7 @@ public String toString() {
", access=" + access +
", dataMask=" + dataMask +
", rowFilter=" + rowFilter +
+ ", constraints=" + constraints +
", additionalInfo=" + additionalInfo +
", subResources=" + subResources +
'}';
@@ -250,10 +263,11 @@ public String toString() {
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
public static class ResultInfo {
- private AccessResult access;
- private DataMaskResult dataMask;
- private RowFilterResult rowFilter;
- private Map<String, Object> additionalInfo;
+ private AccessResult access;
+ private DataMaskResult dataMask;
+ private RowFilterResult rowFilter;
+ private List<ConstraintResult> constraints;
+ private Map<String, Object> additionalInfo;
public ResultInfo() {
}
@@ -289,6 +303,14 @@ public void setRowFilter(RowFilterResult rowFilter) {
this.rowFilter = rowFilter;
}
+ public List<ConstraintResult> getConstraints() {
+ return constraints;
+ }
+
+ public void setConstraints(List<ConstraintResult> constraints) {
+ this.constraints = constraints;
+ }
+
public Map<String, Object> getAdditionalInfo() {
return additionalInfo;
}
@@ -299,7 +321,7 @@ public void setAdditionalInfo(Map<String, Object>
additionalInfo) {
@Override
public int hashCode() {
- return Objects.hash(access, dataMask, rowFilter, additionalInfo);
+ return Objects.hash(access, dataMask, rowFilter, constraints,
additionalInfo);
}
@Override
@@ -315,6 +337,7 @@ public boolean equals(Object o) {
return Objects.equals(access, that.access) &&
Objects.equals(dataMask, that.dataMask) &&
Objects.equals(rowFilter, that.rowFilter) &&
+ Objects.equals(constraints, that.constraints) &&
Objects.equals(additionalInfo, that.additionalInfo);
}
@@ -324,6 +347,7 @@ public String toString() {
"access=" + access +
", dataMask=" + dataMask +
", rowFilter=" + rowFilter +
+ ", constraints=" + constraints +
", additionalInfo=" + additionalInfo +
'}';
}
@@ -518,6 +542,89 @@ public String toString() {
}
}
+ @JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
+ @JsonInclude(JsonInclude.Include.NON_EMPTY)
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class ConstraintResult {
+ private String name;
+ private Map<String, Object> parameters;
+ private PolicyInfo policy;
+ private Integer policyPriority;
+
+ public ConstraintResult() {
+ }
+
+ public ConstraintResult(String name, Map<String, Object> parameters,
PolicyInfo policy, Integer policyPriority) {
+ this.name = name;
+ this.parameters = parameters;
+ this.policy = policy;
+ this.policyPriority = policyPriority;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Map<String, Object> getParameters() {
+ return parameters;
+ }
+
+ public void setParameters(Map<String, Object> parameters) {
+ this.parameters = parameters;
+ }
+
+ public PolicyInfo getPolicy() {
+ return policy;
+ }
+
+ public void setPolicy(PolicyInfo policy) {
+ this.policy = policy;
+ }
+
+ public Integer getPolicyPriority() {
+ return policyPriority;
+ }
+
+ public void setPolicyPriority(Integer policyPriority) {
+ this.policyPriority = policyPriority;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, parameters, policy, policyPriority);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ } else if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ConstraintResult that = (ConstraintResult) o;
+
+ return Objects.equals(name, that.name) &&
+ Objects.equals(parameters, that.parameters) &&
+ Objects.equals(policy, that.policy) &&
+ Objects.equals(policyPriority, that.policyPriority);
+ }
+
+ @Override
+ public String toString() {
+ return "ConstraintResult{" +
+ "name='" + name + '\'' +
+ ", parameters=" + parameters +
+ ", policy=" + policy +
+ ", policyPriority=" + policyPriority +
+ '}';
+ }
+ }
+
@JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@JsonIgnoreProperties(ignoreUnknown = true)
diff --git
a/authz-api/src/test/java/org/apache/ranger/authz/model/TestRangerAuthzResult.java
b/authz-api/src/test/java/org/apache/ranger/authz/model/TestRangerAuthzResult.java
new file mode 100644
index 000000000..1b712645e
--- /dev/null
+++
b/authz-api/src/test/java/org/apache/ranger/authz/model/TestRangerAuthzResult.java
@@ -0,0 +1,91 @@
+/*
+ * 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.authz.model;
+
+import org.apache.ranger.authz.model.RangerAuthzResult.AccessDecision;
+import org.apache.ranger.authz.model.RangerAuthzResult.AccessResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.ConstraintResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.PermissionResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.PolicyInfo;
+import org.apache.ranger.authz.model.RangerAuthzResult.ResultInfo;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestRangerAuthzResult {
+ @Test
+ public void testPermissionResultCopiesConstraintsFromResultInfo() {
+ List<ConstraintResult> constraints =
Collections.singletonList(requireApproval(21L));
+ ResultInfo resultInfo = new ResultInfo(new
AccessResult(AccessDecision.ALLOW, new PolicyInfo(12L, 3L)), null, null, null);
+
+ resultInfo.setConstraints(constraints);
+
+ PermissionResult permResult = new PermissionResult("execute",
resultInfo);
+
+ assertEquals(constraints, permResult.getConstraints());
+ assertEquals(AccessDecision.ALLOW,
permResult.getAccess().getDecision());
+ }
+
+ @Test
+ public void testConstraintsInEqualsAndHashCode() {
+ ResultInfo withConstraint = new ResultInfo(new
AccessResult(AccessDecision.ALLOW, null), null, null, null);
+ ResultInfo withoutConstraint = new ResultInfo(new
AccessResult(AccessDecision.ALLOW, null), null, null, null);
+
+
withConstraint.setConstraints(Collections.singletonList(requireApproval(21L)));
+
+ assertNotEquals(withConstraint, withoutConstraint);
+
+
withoutConstraint.setConstraints(Collections.singletonList(requireApproval(21L)));
+
+ assertEquals(withConstraint, withoutConstraint);
+ assertEquals(withConstraint.hashCode(), withoutConstraint.hashCode());
+
+ PermissionResult perm1 = new PermissionResult("execute",
withConstraint);
+ PermissionResult perm2 = new PermissionResult("execute",
withoutConstraint);
+
+ assertEquals(perm1, perm2);
+
+ perm2.setConstraints(Collections.singletonList(requireApproval(22L)));
+
+ assertNotEquals(perm1, perm2);
+ }
+
+ @Test
+ public void testConstraintResult() {
+ ConstraintResult constraint = requireApproval(21L);
+
+ assertEquals("requireApproval", constraint.getName());
+ assertEquals("finance-approvers",
constraint.getParameters().get("approver"));
+ assertEquals(new PolicyInfo(21L, 1L), constraint.getPolicy());
+ assertEquals(Integer.valueOf(0), constraint.getPolicyPriority());
+ assertEquals(requireApproval(21L), constraint);
+ assertNotEquals(requireApproval(22L), constraint);
+ assertTrue(constraint.toString().contains("requireApproval"));
+ }
+
+ private static ConstraintResult requireApproval(Long policyId) {
+ return new ConstraintResult("requireApproval",
Collections.singletonMap("approver", "finance-approvers"), new
PolicyInfo(policyId, 1L), 0);
+ }
+}
diff --git
a/authz-embedded/src/main/java/org/apache/ranger/authz/embedded/RangerAuthzPlugin.java
b/authz-embedded/src/main/java/org/apache/ranger/authz/embedded/RangerAuthzPlugin.java
index 28ed1b9d5..3c6d6c2f4 100644
---
a/authz-embedded/src/main/java/org/apache/ranger/authz/embedded/RangerAuthzPlugin.java
+++
b/authz-embedded/src/main/java/org/apache/ranger/authz/embedded/RangerAuthzPlugin.java
@@ -20,6 +20,7 @@
package org.apache.ranger.authz.embedded;
import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
import org.apache.ranger.authz.api.RangerAuthzApiErrorCode;
@@ -30,6 +31,7 @@
import org.apache.ranger.authz.model.RangerAuthzResult;
import org.apache.ranger.authz.model.RangerAuthzResult.AccessDecision;
import org.apache.ranger.authz.model.RangerAuthzResult.AccessResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.ConstraintResult;
import org.apache.ranger.authz.model.RangerAuthzResult.DataMaskResult;
import org.apache.ranger.authz.model.RangerAuthzResult.PermissionResult;
import org.apache.ranger.authz.model.RangerAuthzResult.PolicyInfo;
@@ -47,6 +49,7 @@
import org.apache.ranger.plugin.policyengine.RangerAccessResource;
import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.policyengine.RangerConstraintResult;
import org.apache.ranger.plugin.policyengine.RangerResourceACLs;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
@@ -54,8 +57,10 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
@@ -133,6 +138,7 @@ public RangerAuthzResult authorize(RangerAuthzRequest
request, RangerAuthzAuditH
if (permResult.getAccess().getDecision() == AccessDecision.DENY) {
permResult.setDataMask(null);
permResult.setRowFilter(null);
+ permResult.setConstraints(null);
}
ret.getPermissions().put(permission, permResult);
@@ -308,6 +314,30 @@ private ResultInfo toPermissionResult(RangerAccessResult
result) {
ret.setRowFilter(new RowFilterResult(result.getFilterExpr(),
ret.getAccess().getPolicy()));
}
+ ret.setConstraints(toConstraintResults(result));
+
+ return ret;
+ }
+
+ // Constraints are returned only for an allowed access, ordered by
RangerConstraintResult.CONSTRAINT_RESULT_COMPARATOR
+ static List<ConstraintResult> toConstraintResults(RangerAccessResult
result) {
+ List<ConstraintResult> ret = null;
+
+ if (result != null && result.getIsAccessDetermined() &&
result.getIsAllowed() && CollectionUtils.isNotEmpty(result.getConstraints())) {
+ List<RangerConstraintResult> constraints = new
ArrayList<>(result.getConstraints());
+
+
constraints.sort(RangerConstraintResult.CONSTRAINT_RESULT_COMPARATOR);
+
+ ret = new ArrayList<>(constraints.size());
+
+ for (RangerConstraintResult constraint : constraints) {
+ Map<String, Object> parameters =
MapUtils.isEmpty(constraint.getParameters()) ? null : new
HashMap<>(constraint.getParameters());
+ PolicyInfo policy = new
PolicyInfo(constraint.getPolicyId(), constraint.getPolicyVersion());
+
+ ret.add(new ConstraintResult(constraint.getConstraintName(),
parameters, policy, constraint.getPolicyPriority()));
+ }
+ }
+
return ret;
}
diff --git
a/authz-embedded/src/test/java/org/apache/ranger/authz/embedded/TestRangerAuthzPluginConstraints.java
b/authz-embedded/src/test/java/org/apache/ranger/authz/embedded/TestRangerAuthzPluginConstraints.java
new file mode 100644
index 000000000..b4a391347
--- /dev/null
+++
b/authz-embedded/src/test/java/org/apache/ranger/authz/embedded/TestRangerAuthzPluginConstraints.java
@@ -0,0 +1,139 @@
+/*
+ * 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.authz.embedded;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.ranger.authz.model.RangerAuthzResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.AccessDecision;
+import org.apache.ranger.authz.model.RangerAuthzResult.AccessResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.ConstraintResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.PermissionResult;
+import org.apache.ranger.authz.model.RangerAuthzResult.PolicyInfo;
+import org.apache.ranger.authz.model.RangerAuthzResult.ResultInfo;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.policyengine.RangerConstraintResult;
+import
org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher.MatchType;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class TestRangerAuthzPluginConstraints {
+ @Test
+ public void testAllowedResultReturnsSortedConstraints() {
+ RangerAccessResult result = accessResult(true);
+
+ result.addConstraint(constraint("requireApproval", 30L,
RangerPolicy.POLICY_PRIORITY_NORMAL));
+ result.addConstraint(constraint("maxRows", 20L,
RangerPolicy.POLICY_PRIORITY_NORMAL));
+ result.addConstraint(constraint("requireApproval", 40L,
RangerPolicy.POLICY_PRIORITY_OVERRIDE));
+
+ List<ConstraintResult> constraints =
RangerAuthzPlugin.toConstraintResults(result);
+
+ assertEquals(3, constraints.size());
+
+ // name, then policy priority (OVERRIDE first), then policy id
+ assertEquals("maxRows", constraints.get(0).getName());
+ assertEquals(new PolicyInfo(40L, 2L), constraints.get(1).getPolicy());
+ assertEquals(Integer.valueOf(RangerPolicy.POLICY_PRIORITY_OVERRIDE),
constraints.get(1).getPolicyPriority());
+ assertEquals(new PolicyInfo(30L, 2L), constraints.get(2).getPolicy());
+ assertEquals("finance-approvers",
constraints.get(2).getParameters().get("approver"));
+ }
+
+ @Test
+ public void testNoConstraintsWhenNoneMatched() {
+ assertNull(RangerAuthzPlugin.toConstraintResults(accessResult(true)));
+ }
+
+ @Test
+ public void testNoConstraintsWhenDeniedOrNotDetermined() {
+ RangerAccessResult denied = accessResult(true);
+
+ denied.addConstraint(constraint("requireApproval", 30L,
RangerPolicy.POLICY_PRIORITY_NORMAL));
+ denied.setIsAllowed(false);
+
+ assertNull(RangerAuthzPlugin.toConstraintResults(denied));
+
+ RangerAccessResult notDetermined = new
RangerAccessResult(RangerPolicy.POLICY_TYPE_ACCESS, "agent", null, null);
+
+ notDetermined.addConstraint(constraint("requireApproval", 30L,
RangerPolicy.POLICY_PRIORITY_NORMAL));
+
+ assertNull(RangerAuthzPlugin.toConstraintResults(notDetermined));
+ assertNull(RangerAuthzPlugin.toConstraintResults(null));
+ }
+
+ @Test
+ public void testParametersAreCopied() {
+ RangerAccessResult result = accessResult(true);
+
+ result.addConstraint(constraint("requireApproval", 30L,
RangerPolicy.POLICY_PRIORITY_NORMAL));
+
+ ConstraintResult constraint =
RangerAuthzPlugin.toConstraintResults(result).get(0);
+
+ constraint.getParameters().put("approver", "changed");
+
+ assertEquals("finance-approvers",
result.getConstraints().get(0).getParameters().get("approver"));
+ }
+
+ @Test
+ public void testJsonRoundTrip() throws Exception {
+ ObjectMapper mapper = new ObjectMapper();
+ ResultInfo resultInfo = new ResultInfo(new
AccessResult(AccessDecision.ALLOW, new PolicyInfo(12L, 3L)), null, null, null);
+
+ resultInfo.setConstraints(Collections.singletonList(new
ConstraintResult("requireApproval", Collections.singletonMap("approver",
"finance-approvers"), new PolicyInfo(21L, 1L), 0)));
+
+ Map<String, PermissionResult> permissions = new HashMap<>();
+
+ permissions.put("execute", new PermissionResult("execute",
resultInfo));
+
+ RangerAuthzResult authzResult = new RangerAuthzResult("req-1",
AccessDecision.ALLOW, permissions);
+ String json = mapper.writeValueAsString(authzResult);
+
+ assertEquals(authzResult, mapper.readValue(json,
RangerAuthzResult.class));
+
+ RangerAuthzResult noConstraints = new RangerAuthzResult("req-2",
AccessDecision.ALLOW, Collections.singletonMap("execute",
+ new PermissionResult("execute", new ResultInfo(new
AccessResult(AccessDecision.ALLOW, null), null, null, null))));
+
+
assertFalse(mapper.writeValueAsString(noConstraints).contains("constraints"));
+ }
+
+ private static RangerAccessResult accessResult(boolean isAllowed) {
+ RangerAccessResult ret = new
RangerAccessResult(RangerPolicy.POLICY_TYPE_ACCESS, "agent", null, null);
+
+ ret.setIsAccessDetermined(true);
+ ret.setIsAllowed(isAllowed);
+
+ return ret;
+ }
+
+ private static RangerConstraintResult constraint(String name, long
policyId, int policyPriority) {
+ Map<String, Object> parameters = new HashMap<>();
+
+ parameters.put("approver", "finance-approvers");
+
+ return new RangerConstraintResult(name, parameters, policyId, 2L,
policyPriority, MatchType.SELF);
+ }
+}