diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessResult.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessResult.java index 8088b022f9..76d942794e 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessResult.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerAccessResult.java @@ -24,7 +24,10 @@ import org.apache.ranger.plugin.model.RangerServiceDef; import org.apache.ranger.plugin.util.ServiceDefUtil; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; @@ -38,24 +41,25 @@ public class RangerAccessResult { private static final String KEY_ALLOWED_BY_DATASETS = "allowedByDatasets"; private static final String KEY_ALLOWED_BY_PROJECTS = "allowedByProjects"; - private final String serviceName; - private final RangerServiceDef serviceDef; - private final RangerAccessRequest request; - - private final int policyType; // Really, policy-type for audit purpose - private boolean isAccessDetermined; - private boolean isAllowed; - private boolean isAuditedDetermined; - private boolean isAudited; - private long auditPolicyId = -1; - private String auditLogId; - private long policyId = -1; - private int policyPriority; - private String zoneName; - private Long policyVersion; - private long evaluatedPoliciesCount; - private String reason; - private Map additionalInfo; + private final String serviceName; + private final RangerServiceDef serviceDef; + private final RangerAccessRequest request; + + private final int policyType; // Really, policy-type for audit purpose + private boolean isAccessDetermined; + private boolean isAllowed; + private boolean isAuditedDetermined; + private boolean isAudited; + private long auditPolicyId = -1; + private String auditLogId; + private long policyId = -1; + private int policyPriority; + private String zoneName; + private Long policyVersion; + private long evaluatedPoliciesCount; + private String reason; + private Map additionalInfo; + private List constraints; public RangerAccessResult(final int policyType, final String serviceName, final RangerServiceDef serviceDef, final RangerAccessRequest request) { this.serviceName = serviceName; @@ -83,6 +87,7 @@ public void setAccessResultFrom(final RangerAccessResult other) { this.evaluatedPoliciesCount = other.evaluatedPoliciesCount; this.reason = other.getReason(); this.additionalInfo = other.additionalInfo == null ? new HashMap<>() : new HashMap<>(other.additionalInfo); + this.constraints = copyConstraints(other.constraints); } public void setAuditResultFrom(final RangerAccessResult other) { @@ -138,6 +143,7 @@ public boolean getIsAllowed() { public void setIsAllowed(boolean isAllowed) { if (!isAllowed) { setIsAccessDetermined(true); + this.constraints = null; } this.isAllowed = isAllowed; @@ -408,6 +414,29 @@ public void setAllowedByProjects(Set projects) { } } + /** + * Constraints that matched this request. Empty when access is denied or nothing matched. + */ + public List getConstraints() { + return constraints == null ? Collections.emptyList() : constraints; + } + + public void setConstraints(List constraints) { + this.constraints = copyConstraints(constraints); + } + + public void addConstraint(RangerConstraintResult constraint) { + if (constraint == null) { + return; + } + + if (constraints == null) { + constraints = new ArrayList<>(); + } + + constraints.add(constraint); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); @@ -439,10 +468,16 @@ public StringBuilder toString(StringBuilder sb) { sb.append(entry.getKey()).append("=").append(entry.getValue()).append(", "); } } - sb.append("}"); + sb.append("} "); + + sb.append("constraints={").append(getConstraints()).append("}"); sb.append("}"); return sb; } + + private static List copyConstraints(List constraints) { + return constraints == null || constraints.isEmpty() ? null : new ArrayList<>(constraints); + } } diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerConstraintResult.java b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerConstraintResult.java new file mode 100644 index 0000000000..7e7083fd45 --- /dev/null +++ b/agents-common/src/main/java/org/apache/ranger/plugin/policyengine/RangerConstraintResult.java @@ -0,0 +1,136 @@ +/* + * 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.policyengine; + +import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher; +import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher.MatchType; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class RangerConstraintResult { + public static final Comparator CONSTRAINT_RESULT_COMPARATOR = new ConstraintResultComparator(); + + private final Map parameters; + private String constraintName; + private long policyId; + private Long policyVersion; + private int policyPriority; + private MatchType matchType; + + public RangerConstraintResult(String constraintName, Map parameters, long policyId, Long policyVersion, int policyPriority, MatchType matchType) { + this.constraintName = constraintName; + this.parameters = parameters == null ? new HashMap<>() : new HashMap<>(parameters); + this.policyId = policyId; + this.policyVersion = policyVersion; + this.policyPriority = policyPriority; + this.matchType = matchType; + } + + public String getConstraintName() { + return constraintName; + } + + public Map getParameters() { + return parameters; + } + + public long getPolicyId() { + return policyId; + } + + public Long getPolicyVersion() { + return policyVersion; + } + + public int getPolicyPriority() { + return policyPriority; + } + + public MatchType getMatchType() { + return matchType; + } + + @Override + public int hashCode() { + return Objects.hash(constraintName, parameters, policyId, policyVersion, policyPriority, matchType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + RangerConstraintResult other = (RangerConstraintResult) obj; + + return policyId == other.policyId && + policyPriority == other.policyPriority && + Objects.equals(policyVersion, other.policyVersion) && + Objects.equals(constraintName, other.constraintName) && + Objects.equals(matchType, other.matchType) && + Objects.equals(parameters, other.parameters); + } + + @Override + public String toString() { + return "RangerConstraintResult{constraintName=" + constraintName + + ", parameters=" + parameters + + ", policyId=" + policyId + + ", policyVersion=" + policyVersion + + ", policyPriority=" + policyPriority + + ", matchType=" + matchType + + "}"; + } + + /** + * Orders by constraint name, policy priority (higher first), match type, then policy id. + */ + public static class ConstraintResultComparator implements Comparator, Serializable { + @Override + public int compare(RangerConstraintResult me, RangerConstraintResult other) { + int ret = compareStrings(me.getConstraintName(), other.getConstraintName()); + + if (ret == 0) { + ret = Integer.compare(other.getPolicyPriority(), me.getPolicyPriority()); + } + + if (ret == 0) { + ret = RangerPolicyResourceMatcher.MATCH_TYPE_COMPARATOR.compare(me.getMatchType(), other.getMatchType()); + } + + if (ret == 0) { + ret = Long.compare(me.getPolicyId(), other.getPolicyId()); + } + + return ret; + } + + private static int compareStrings(String me, String other) { + return me == null ? (other == null ? 0 : -1) : (other == null ? 1 : me.compareTo(other)); + } + } +} diff --git a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java index f11f982650..686f5bf04a 100644 --- a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java +++ b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerBasePlugin.java @@ -1423,6 +1423,10 @@ private void updateResultFromChainedResult(RangerAccessResult result, RangerAcce } else if (policyType == RangerPolicy.POLICY_TYPE_ROWFILTER) { result.setFilterExpr(chainedResult.getFilterExpr()); } + + if (result.getIsAllowed()) { + result.setConstraints(chainedResult.getConstraints()); + } } if (!result.getIsAuditedDetermined() && chainedResult.getIsAuditedDetermined()) { diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerAccessResult.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerAccessResult.java new file mode 100644 index 0000000000..8fcdbe6283 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerAccessResult.java @@ -0,0 +1,99 @@ +/* + * 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.policyengine; + +import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher.MatchType; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestRangerAccessResult { + @Test + public void constraintsAreEmptyUntilOneIsAdded() { + RangerAccessResult result = newResult(); + + assertTrue(result.getConstraints().isEmpty()); + + result.addConstraint(null); + assertTrue(result.getConstraints().isEmpty()); + + RangerConstraintResult constraint = constraint(10L); + + result.addConstraint(constraint); + + assertEquals(Arrays.asList(constraint), result.getConstraints()); + } + + @Test + public void setConstraintsCopiesTheInputList() { + RangerAccessResult result = newResult(); + List constraints = new ArrayList<>(); + RangerConstraintResult constraint = constraint(10L); + + constraints.add(constraint); + result.setConstraints(constraints); + constraints.clear(); + + assertEquals(Arrays.asList(constraint), result.getConstraints()); + + result.setConstraints(new ArrayList<>()); + + assertTrue(result.getConstraints().isEmpty()); + } + + @Test + public void setAccessResultFromCopiesConstraints() { + RangerAccessResult source = newResult(); + RangerAccessResult copied = newResult(); + RangerConstraintResult constraint = constraint(10L); + + source.addConstraint(constraint); + copied.setAccessResultFrom(source); + + assertEquals(Arrays.asList(constraint), copied.getConstraints()); + } + + @Test + public void denyClearsConstraints() { + RangerAccessResult result = newResult(); + + result.addConstraint(constraint(10L)); + result.setIsAllowed(false); + + assertTrue(result.getConstraints().isEmpty()); + + result.addConstraint(constraint(11L)); + result.setIsAllowed(true); + + assertEquals(1, result.getConstraints().size()); + } + + private static RangerAccessResult newResult() { + return new RangerAccessResult(0, "agent-studio", null, null); + } + + private static RangerConstraintResult constraint(long policyId) { + return new RangerConstraintResult("requireApproval", null, policyId, 1L, 0, MatchType.SELF); + } +} diff --git a/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerConstraintResult.java b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerConstraintResult.java new file mode 100644 index 0000000000..26ead3d837 --- /dev/null +++ b/agents-common/src/test/java/org/apache/ranger/plugin/policyengine/TestRangerConstraintResult.java @@ -0,0 +1,98 @@ +/* + * 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.policyengine; + +import org.apache.ranger.plugin.policyresourcematcher.RangerPolicyResourceMatcher.MatchType; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +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.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class TestRangerConstraintResult { + @Test + public void nullParametersBecomeAnEmptyMap() { + RangerConstraintResult result = result("requireApproval", null, 10L, 3L, 0, MatchType.SELF); + + assertNotNull(result.getParameters()); + assertTrue(result.getParameters().isEmpty()); + } + + @Test + public void parametersAreCopiedFromTheInputMap() { + Map parameters = new HashMap<>(); + + parameters.put("approver", "alice"); + + RangerConstraintResult result = result("requireApproval", parameters, 10L, 3L, 0, MatchType.SELF); + + parameters.put("approver", "bob"); + parameters.put("extra", "x"); + + assertEquals("alice", result.getParameters().get("approver")); + assertFalse(result.getParameters().containsKey("extra")); + assertEquals(1, result.getParameters().size()); + } + + @Test + public void equalsAndHashCodeIncludePolicyPriority() { + RangerConstraintResult result = result("requireApproval", mapOf("approver", "alice"), 10L, 3L, 0, MatchType.SELF); + RangerConstraintResult same = result("requireApproval", mapOf("approver", "alice"), 10L, 3L, 0, MatchType.SELF); + RangerConstraintResult higher = result("requireApproval", mapOf("approver", "alice"), 10L, 3L, 1, MatchType.SELF); + + assertEquals(result, same); + assertEquals(result.hashCode(), same.hashCode()); + assertNotEquals(result, higher); + assertTrue(result.toString().contains("policyPriority=0")); + assertTrue(result.toString().contains("requireApproval")); + } + + @Test + public void comparatorOrdersByNamePriorityMatchTypeAndPolicyId() { + RangerConstraintResult maxRows = result("maxRows", null, 20L, 1L, 1, MatchType.DESCENDANT); + RangerConstraintResult lowPriority = result("requireApproval", null, 30L, 1L, 0, MatchType.DESCENDANT); + RangerConstraintResult selfMatch = result("requireApproval", null, 40L, 1L, 1, MatchType.SELF); + RangerConstraintResult laterPolicy = result("requireApproval", null, 12L, 1L, 1, MatchType.DESCENDANT); + RangerConstraintResult earlierPolicy = result("requireApproval", null, 11L, 1L, 1, MatchType.DESCENDANT); + List constraints = Arrays.asList(laterPolicy, selfMatch, maxRows, lowPriority, earlierPolicy); + + constraints.sort(RangerConstraintResult.CONSTRAINT_RESULT_COMPARATOR); + + assertEquals(Arrays.asList(maxRows, selfMatch, earlierPolicy, laterPolicy, lowPriority), constraints); + } + + private static RangerConstraintResult result(String name, Map parameters, long policyId, Long policyVersion, int policyPriority, MatchType matchType) { + return new RangerConstraintResult(name, parameters, policyId, policyVersion, policyPriority, matchType); + } + + private static Map mapOf(String key, String value) { + Map ret = new HashMap<>(); + + ret.put(key, value); + + return ret; + } +}