This is an automated email from the ASF dual-hosted git repository.
vyommani 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 97c53ffeb RANGER-5771: Improve Ranger Kafka Plugin to Prevent OOM
During SRM St… (#1265)
97c53ffeb is described below
commit 97c53ffeb33512d348b10e63d76d4026493a778d
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Wed Sep 30 12:03:15 2026 +0530
RANGER-5771: Improve Ranger Kafka Plugin to Prevent OOM During SRM St…
(#1265)
* RANGER-5771: Improve Ranger Kafka Plugin to Prevent OOM During SRM Startup
* RANGER-5771: addressed review comments
* RANGER-5771: addressed review comments
* RANGER-5771: addressed review comments
---
.../ranger/plugin/service/RangerBasePlugin.java | 36 ++-
.../service/RangerDefaultRequestProcessor.java | 85 +++++--
.../plugin/util/RangerAccessRequestUtil.java | 23 ++
.../ranger/plugin/util/RangerBatchEvalContext.java | 142 +++++++++++
.../TestRangerBasePluginBatchEvalContext.java | 260 +++++++++++++++++++++
.../service/TestRangerDefaultRequestProcessor.java | 236 +++++++++++++++++++
.../TestRangerKafkaAuthorizerSharedRoles.java | 223 ++++++++++++++++++
7 files changed, 984 insertions(+), 21 deletions(-)
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 33bf33949..f11f98265 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
@@ -62,6 +62,8 @@
import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
import org.apache.ranger.plugin.util.PerfDataRecorder;
import org.apache.ranger.plugin.util.PolicyRefresher;
+import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerBatchEvalContext;
import org.apache.ranger.plugin.util.RangerPolicyDeltaUtil;
import org.apache.ranger.plugin.util.RangerRoles;
import org.apache.ranger.plugin.util.RangerRolesUtil;
@@ -712,11 +714,27 @@ public Collection<RangerAccessResult>
isAccessAllowed(Collection<RangerAccessReq
refreshPoliciesAndTags();
}
- Collection<RangerAccessResult> ret = null;
- RangerPolicyEngine policyEngine = this.policyEngine;
+ Collection<RangerAccessResult> ret = null;
+ RangerPolicyEngine policyEngine = this.policyEngine;
+ RangerBatchEvalContext batchEvalContext = null;
if (policyEngine != null) {
- ret = policyEngine.evaluatePolicies(requests,
RangerPolicy.POLICY_TYPE_ACCESS, null);
+ if (CollectionUtils.isNotEmpty(requests)) {
+ batchEvalContext = new RangerBatchEvalContext();
+
+ for (RangerAccessRequest request : requests) {
+ if (request == null) {
+ continue;
+ }
+
RangerAccessRequestUtil.setBatchEvalContext(request.getContext(),
batchEvalContext);
+ }
+ }
+
+ try {
+ ret = policyEngine.evaluatePolicies(requests,
RangerPolicy.POLICY_TYPE_ACCESS, null);
+ } finally {
+ clearBatchEvalContext(requests, batchEvalContext);
+ }
}
if (CollectionUtils.isNotEmpty(ret)) {
@@ -752,6 +770,18 @@ public Collection<RangerAccessResult>
isAccessAllowed(Collection<RangerAccessReq
return ret;
}
+ private static void clearBatchEvalContext(Collection<RangerAccessRequest>
requests, RangerBatchEvalContext batchEvalContext) {
+ if (batchEvalContext == null || CollectionUtils.isEmpty(requests)) {
+ return;
+ }
+
+ for (RangerAccessRequest request : requests) {
+ if (request != null) {
+
RangerAccessRequestUtil.removeBatchEvalContext(request.getContext());
+ }
+ }
+ }
+
public RangerAccessResult evalDataMaskPolicies(RangerAccessRequest
request, RangerAccessResultProcessor resultProcessor) {
if (this.synchronousPolicyRefresh) {
refreshPoliciesAndTags();
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerDefaultRequestProcessor.java
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerDefaultRequestProcessor.java
index 6661e34e1..05c040f1f 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerDefaultRequestProcessor.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/service/RangerDefaultRequestProcessor.java
@@ -31,6 +31,7 @@
import org.apache.ranger.plugin.policyengine.RangerMutableResource;
import org.apache.ranger.plugin.policyengine.RangerPluginContext;
import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerBatchEvalContext;
import org.apache.ranger.plugin.util.RangerCommonConstants;
import org.apache.ranger.plugin.util.RangerPerfTracer;
import org.apache.ranger.plugin.util.RangerUserStoreUtil;
@@ -84,8 +85,11 @@ public void preProcess(RangerAccessRequest request) {
setResourceServiceDef(request);
- RangerPluginContext pluginContext =
policyEngine.getPluginContext();
- RangerAccessRequestImpl reqImpl = null;
+ RangerPluginContext pluginContext =
policyEngine.getPluginContext();
+ RangerAccessRequestImpl reqImpl = null;
+ RangerBatchEvalContext batchEvalContext =
RangerAccessRequestUtil.getBatchEvalContext(request.getContext());
+ String originalUser = request.getUser();
+ Set<String> originalGroups = request.getUserGroups();
if (request instanceof RangerAccessRequestImpl) {
reqImpl = (RangerAccessRequestImpl) request;
@@ -103,20 +107,7 @@ public void preProcess(RangerAccessRequest request) {
reqImpl.setClusterType(pluginContext.getClusterType());
}
- RangerPluginConfig config =
policyEngine.getPluginContext().getConfig();
-
- boolean isNameTransformationSupported =
config.getBoolean(config.getPropertyPrefix() +
RangerCommonConstants.PLUGIN_CONFIG_SUFFIX_NAME_TRANSFORMATION, false);
-
- LOG.debug("isNameTransformationSupported = {}",
isNameTransformationSupported);
-
- if (isNameTransformationSupported) {
- reqImpl.setUser(getTransformedUser(policyEngine, request));
- reqImpl.setUserGroups(getTransformedGroups(policyEngine,
request));
- }
-
- convertEmailToUsername(reqImpl);
-
- updateUserGroups(reqImpl);
+ normalizeUserAndGroups(reqImpl, pluginContext,
batchEvalContext, originalUser, originalGroups);
}
}
@@ -130,9 +121,20 @@ public void preProcess(RangerAccessRequest request) {
Set<String> roles = request.getUserRoles();
if (pluginContext != null && CollectionUtils.isEmpty(roles)) {
- roles =
pluginContext.getAuthContext().getRolesForUserAndGroups(request.getUser(),
request.getUserGroups());
+ if (batchEvalContext != null) {
+ roles = batchEvalContext.getMappedUserRoles(originalUser,
originalGroups);
+
+ if (roles == null) {
+ roles =
pluginContext.getAuthContext().getRolesForUserAndGroups(request.getUser(),
request.getUserGroups());
+ roles = readOnly(roles);
- if (reqImpl != null && roles != null && !roles.isEmpty()) {
+ batchEvalContext.setUserRolesMapping(originalUser,
originalGroups, roles);
+ }
+ } else {
+ roles =
pluginContext.getAuthContext().getRolesForUserAndGroups(request.getUser(),
request.getUserGroups());
+ }
+
+ if (reqImpl != null && CollectionUtils.isNotEmpty(roles)) {
reqImpl.setUserRoles(roles);
}
}
@@ -173,6 +175,53 @@ public void enrich(RangerAccessRequest request) {
}
}
+ private void normalizeUserAndGroups(RangerAccessRequestImpl reqImpl,
RangerPluginContext pluginContext, RangerBatchEvalContext batchEvalContext,
String originalUser, Set<String> originalGroups) {
+ if (batchEvalContext != null) {
+ String mappedUser =
batchEvalContext.getMappedUserName(originalUser);
+ Set<String> mappedUserGroups =
batchEvalContext.getMappedUserGroups(originalUser, originalGroups);
+ // A mapped user can be null, so a null user is a hit when the key
is present.
+ boolean userMissing = mappedUser == null &&
!batchEvalContext.hasMappingForUserName(originalUser);
+
+ if (userMissing || mappedUserGroups == null) {
+ mapUserAndGroups(reqImpl, pluginContext);
+
+ Set<String> groups = copyReadOnly(reqImpl.getUserGroups());
+
+ batchEvalContext.setUserNameMapping(originalUser,
reqImpl.getUser());
+ batchEvalContext.setUserGroupsMapping(originalUser,
originalGroups, groups);
+ reqImpl.setUserGroups(groups);
+ } else {
+ reqImpl.setUser(mappedUser);
+ reqImpl.setUserGroups(mappedUserGroups);
+ }
+ } else {
+ mapUserAndGroups(reqImpl, pluginContext);
+ }
+ }
+
+ private void mapUserAndGroups(RangerAccessRequestImpl req,
RangerPluginContext pluginContext) {
+ RangerPluginConfig config = pluginContext.getConfig();
+ boolean needsTransform =
config.getBoolean(config.getPropertyPrefix() +
RangerCommonConstants.PLUGIN_CONFIG_SUFFIX_NAME_TRANSFORMATION, false);
+
+ LOG.debug("isNameTransformationSupported = {}", needsTransform);
+
+ if (needsTransform) {
+ req.setUser(getTransformedUser(policyEngine, req));
+ req.setUserGroups(getTransformedGroups(policyEngine, req));
+ }
+
+ convertEmailToUsername(req);
+ updateUserGroups(req);
+ }
+
+ private static Set<String> readOnly(Set<String> values) {
+ return values == null || values.isEmpty() ? Collections.emptySet() :
Collections.unmodifiableSet(values);
+ }
+
+ private static Set<String> copyReadOnly(Set<String> values) {
+ return values == null || values.isEmpty() ? Collections.emptySet() :
Collections.unmodifiableSet(new HashSet<>(values));
+ }
+
private String getTransformedUser(PolicyEngine policyEngine,
RangerAccessRequest request) {
RangerAuthContext authContext =
policyEngine.getPluginContext().getAuthContext();
boolean toLowerCase =
authContext.getUserNameCaseConversion() ==
UgsyncCommonConstants.CaseConversion.TO_LOWER;
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
index 52a643ead..8e39c5c50 100644
---
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerAccessRequestUtil.java
@@ -57,6 +57,7 @@ public class RangerAccessRequestUtil {
public static final String KEY_CONTEXT_REQUEST =
"_REQUEST";
public static final String KEY_CONTEXT_GDS_RESULT =
"_GDS_RESULT";
public static final String KEY_CONTEXT_IS_REQUEST_PREPROCESSED =
"ISREQUESTPREPROCESSED";
+ public static final String KEY_CONTEXT_BATCH_EVAL_CONTEXT =
"BATCH_EVAL_CONTEXT";
public static final String KEY_CONTEXT_RESOURCE_ZONE_NAMES =
"RESOURCE_ZONE_NAMES";
public static final String KEY_CONTEXT_ACL_ENFORCER =
"_ACL_ENFORCER";
@@ -239,6 +240,28 @@ public static boolean getIsRequestPreprocessed(Map<String,
Object> context) {
return value != null && value;
}
+ public static void setBatchEvalContext(Map<String, Object> context,
RangerBatchEvalContext batchEvalContext) {
+ if (context != null && batchEvalContext != null) {
+ context.put(KEY_CONTEXT_BATCH_EVAL_CONTEXT, batchEvalContext);
+ }
+ }
+
+ public static RangerBatchEvalContext getBatchEvalContext(Map<String,
Object> context) {
+ if (context == null) {
+ return null;
+ }
+
+ Object value = context.get(KEY_CONTEXT_BATCH_EVAL_CONTEXT);
+
+ return value instanceof RangerBatchEvalContext ?
(RangerBatchEvalContext) value : null;
+ }
+
+ public static void removeBatchEvalContext(Map<String, Object> context) {
+ if (context != null) {
+ context.remove(KEY_CONTEXT_BATCH_EVAL_CONTEXT);
+ }
+ }
+
public static void setAllRequestedAccessTypes(Map<String, Object> context,
Set<String> accessTypes) {
context.put(KEY_CONTEXT_ALL_ACCESSTYPES, accessTypes);
}
diff --git
a/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerBatchEvalContext.java
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerBatchEvalContext.java
new file mode 100644
index 000000000..12d5f3195
--- /dev/null
+++
b/agents-common/src/main/java/org/apache/ranger/plugin/util/RangerBatchEvalContext.java
@@ -0,0 +1,142 @@
+/*
+ * 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.util;
+
+import org.apache.commons.collections.CollectionUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Not thread-safe. One instance per evaluation call; do not mutate a groups
set during that call, because the hot key matches the same set instance.
+ */
+public class RangerBatchEvalContext {
+ private final Map<String, String> userNameMappings = new HashMap<>();
+ private final Map<IdentityKey, Set<String>> userGroupsMappings = new
HashMap<>();
+ private final Map<IdentityKey, Set<String>> userRolesMappings = new
HashMap<>();
+
+ private IdentityKey hotKey;
+ private String hotUser;
+ private Set<String> hotGroupsRef;
+
+ public boolean hasMappingForUserName(String user) {
+ return userNameMappings.containsKey(user);
+ }
+
+ public String getMappedUserName(String user) {
+ return userNameMappings.get(user);
+ }
+
+ public void setUserNameMapping(String user, String normalizedUser) {
+ userNameMappings.put(user, normalizedUser);
+ }
+
+ public boolean hasMappingForUserGroups(String user, Set<String> groups) {
+ return userGroupsMappings.containsKey(keyFor(user, groups));
+ }
+
+ public Set<String> getMappedUserGroups(String user, Set<String> groups) {
+ return userGroupsMappings.get(keyFor(user, groups));
+ }
+
+ public void setUserGroupsMapping(String user, Set<String> groups,
Set<String> normalizedGroups) {
+ userGroupsMappings.put(storedKey(user, groups), normalizedGroups);
+ }
+
+ public boolean hasMappingForUserRoles(String user, Set<String> groups) {
+ return userRolesMappings.containsKey(keyFor(user, groups));
+ }
+
+ public Set<String> getMappedUserRoles(String user, Set<String> groups) {
+ return userRolesMappings.get(keyFor(user, groups));
+ }
+
+ public void setUserRolesMapping(String user, Set<String> groups,
Set<String> roles) {
+ userRolesMappings.put(storedKey(user, groups), roles);
+ }
+
+ private IdentityKey keyFor(String user, Set<String> groups) {
+ if (hotKey != null && Objects.equals(user, hotUser) && groups ==
hotGroupsRef) {
+ return hotKey;
+ }
+
+ IdentityKey key = new IdentityKey(user, groups, false);
+
+ hotKey = key;
+ hotUser = user;
+ hotGroupsRef = groups;
+
+ return key;
+ }
+
+ private IdentityKey storedKey(String user, Set<String> groups) {
+ IdentityKey key = new IdentityKey(user, groups, true);
+
+ hotKey = key;
+ hotUser = user;
+ hotGroupsRef = groups;
+
+ return key;
+ }
+
+ private static final class IdentityKey {
+ private final String user;
+ private final Set<String> groups;
+ private final int hash;
+
+ private IdentityKey(String user, Set<String> groups, boolean copy) {
+ this.user = user;
+ this.groups = copy || CollectionUtils.isEmpty(groups) ?
copyGroups(groups) : groups;
+ this.hash = Objects.hash(user, this.groups);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this == other) {
+ return true;
+ }
+
+ if (!(other instanceof IdentityKey)) {
+ return false;
+ }
+
+ IdentityKey that = (IdentityKey) other;
+
+ return Objects.equals(user, that.user) &&
groups.equals(that.groups);
+ }
+
+ @Override
+ public int hashCode() {
+ return hash;
+ }
+
+ private static Set<String> copyGroups(Set<String> groups) {
+ if (CollectionUtils.isEmpty(groups)) {
+ return Collections.emptySet();
+ }
+
+ return Collections.unmodifiableSet(new HashSet<>(groups));
+ }
+ }
+}
diff --git
a/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerBasePluginBatchEvalContext.java
b/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerBasePluginBatchEvalContext.java
new file mode 100644
index 000000000..33557d6c2
--- /dev/null
+++
b/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerBasePluginBatchEvalContext.java
@@ -0,0 +1,260 @@
+/*
+ * 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.service;
+
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerRole;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.policyengine.RangerPolicyEngine;
+import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
+import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
+import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerBatchEvalContext;
+import org.apache.ranger.plugin.util.RangerRoles;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+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 static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestRangerBasePluginBatchEvalContext {
+ @Test
+ void collectionAccessAttachesOneBatchEvalContextAndRemovesIt() throws
Exception {
+ RangerBasePlugin plugin = new RangerBasePlugin("hbase", "hbase");
+ RangerPolicyEngine policyEngine =
Mockito.mock(RangerPolicyEngine.class);
+ setPolicyEngine(plugin, policyEngine);
+
+ RangerAccessRequestImpl first = new RangerAccessRequestImpl();
+ RangerAccessRequestImpl second = new RangerAccessRequestImpl();
+ RangerAccessRequest nullContext =
Mockito.mock(RangerAccessRequest.class);
+ Mockito.when(nullContext.getContext()).thenReturn(null);
+
+ Mockito.when(policyEngine.evaluatePolicies(Mockito.anyCollection(),
Mockito.eq(RangerPolicy.POLICY_TYPE_ACCESS),
Mockito.isNull())).thenAnswer(invocation -> {
+ Collection<RangerAccessRequest> seen = invocation.getArgument(0);
+ RangerBatchEvalContext shared = null;
+
+ for (RangerAccessRequest request : seen) {
+ if (request == null || request.getContext() == null) {
+ continue;
+ }
+
+ RangerBatchEvalContext batchEvalContext =
RangerAccessRequestUtil.getBatchEvalContext(request.getContext());
+
+ assertNotNull(batchEvalContext);
+
+ if (shared == null) {
+ shared = batchEvalContext;
+ } else {
+ assertSame(shared, batchEvalContext);
+ }
+ }
+
+ assertNotNull(shared);
+
+ return Collections.emptyList();
+ });
+
+ plugin.isAccessAllowed(Arrays.asList(first, null, second,
nullContext), null);
+
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(first.getContext()));
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(second.getContext()));
+ }
+
+ @Test
+ void collectionAccessRemovesBatchEvalContextWhenEvaluationThrows() throws
Exception {
+ RangerBasePlugin plugin = new RangerBasePlugin("hbase", "hbase");
+ RangerPolicyEngine policyEngine =
Mockito.mock(RangerPolicyEngine.class);
+ setPolicyEngine(plugin, policyEngine);
+
+ RangerAccessRequestImpl request = new RangerAccessRequestImpl();
+ Mockito.when(policyEngine.evaluatePolicies(Mockito.anyCollection(),
Mockito.eq(RangerPolicy.POLICY_TYPE_ACCESS), Mockito.isNull())).thenThrow(new
IllegalStateException("evaluation failed"));
+
+ assertThrows(IllegalStateException.class, () ->
plugin.isAccessAllowed(Collections.singletonList(request), null));
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(request.getContext()));
+ }
+
+ @Test
+ void singleAccessDoesNotAttachBatchEvalContext() throws Exception {
+ RangerBasePlugin plugin = new RangerBasePlugin("hbase", "hbase");
+ RangerPolicyEngine policyEngine =
Mockito.mock(RangerPolicyEngine.class);
+ setPolicyEngine(plugin, policyEngine);
+
+ RangerAccessRequestImpl request = new RangerAccessRequestImpl();
+
+ plugin.isAccessAllowed(request, null);
+
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(request.getContext()));
+ }
+
+ @Test
+ void bulkEvaluationMatchesSingleRequestEvaluation() throws Exception {
+ RangerBasePlugin plugin = newPlugin();
+ plugin.setPolicies(servicePolicies());
+ plugin.setRoles(roles());
+
+ List<RangerAccessRequest> bulkRequests = new ArrayList<>(Arrays.asList(
+ request("alice"),
+ request("alice"),
+ request("bob"),
+ request("carol"),
+ request("carol"),
+ request("dave"),
+ request(null)));
+ List<RangerAccessRequest> singleRequests = new
ArrayList<>(Arrays.asList(
+ request("alice"),
+ request("alice"),
+ request("bob"),
+ request("carol"),
+ request("carol"),
+ request("dave"),
+ request(null)));
+
+ Collection<RangerAccessResult> bulkResults =
plugin.isAccessAllowed(bulkRequests, null);
+ List<Boolean> bulkAllowed = new ArrayList<>();
+
+ for (RangerAccessResult result : bulkResults) {
+ bulkAllowed.add(result.getIsAllowed());
+ }
+
+ List<Boolean> singleAllowed = new ArrayList<>();
+
+ for (RangerAccessRequest singleRequest : singleRequests) {
+
singleAllowed.add(plugin.isAccessAllowed(singleRequest).getIsAllowed());
+ }
+
+ assertEquals(Arrays.asList(true, true, false, true, true, false,
false), singleAllowed);
+ assertEquals(singleAllowed, bulkAllowed);
+ assertSame(bulkRequests.get(3).getUserRoles(),
bulkRequests.get(4).getUserRoles());
+ assertTrue(bulkRequests.get(3).getUserRoles().contains("readers"));
+ assertFalse(bulkRequests.get(0).getUserRoles().contains("readers"));
+
+ for (RangerAccessRequest bulkRequest : bulkRequests) {
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(bulkRequest.getContext()));
+ }
+ }
+
+ private static void setPolicyEngine(RangerBasePlugin plugin,
RangerPolicyEngine policyEngine) throws Exception {
+ Field field = RangerBasePlugin.class.getDeclaredField("policyEngine");
+
+ field.setAccessible(true);
+ field.set(plugin, policyEngine);
+ }
+
+ private static RangerBasePlugin newPlugin() {
+ RangerPolicyEngineOptions options = new RangerPolicyEngineOptions();
+ options.disablePolicyRefresher = true;
+ options.disableTagRetriever = true;
+ options.disableUserStoreRetriever = true;
+ options.disableGdsInfoRetriever = true;
+
+ return new RangerBasePlugin(new RangerPluginConfig("hive", "svc",
"app", null, null, options));
+ }
+
+ private static ServicePolicies servicePolicies() throws Exception {
+ RangerPolicy.RangerPolicyItem allowAlice =
policyItem(Collections.singletonList("alice"), Collections.emptyList());
+ RangerPolicy.RangerPolicyItem allowReaders =
policyItem(Collections.emptyList(), Collections.singletonList("readers"));
+ RangerPolicy.RangerPolicyItem denyBob =
policyItem(Collections.singletonList("bob"), Collections.emptyList());
+
+ RangerPolicy policy = new RangerPolicy();
+ policy.setId(1L);
+ policy.setName("db1-select");
+ policy.setService("svc");
+ policy.setIsEnabled(true);
+ policy.setResources(resources());
+ policy.setPolicyItems(Arrays.asList(allowAlice, allowReaders));
+ policy.setDenyPolicyItems(Collections.singletonList(denyBob));
+
+ ServicePolicies policies = new ServicePolicies();
+ policies.setServiceName("svc");
+ policies.setPolicyVersion(1L);
+
policies.setServiceDef(EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_HIVE_NAME));
+ policies.setPolicies(Collections.singletonList(policy));
+
+ return policies;
+ }
+
+ private static Map<String, RangerPolicy.RangerPolicyResource> resources() {
+ Map<String, RangerPolicy.RangerPolicyResource> resources = new
HashMap<>();
+ resources.put("database", new
RangerPolicy.RangerPolicyResource("db1"));
+ resources.put("table", new RangerPolicy.RangerPolicyResource("tbl1"));
+ resources.put("column", new RangerPolicy.RangerPolicyResource("col1"));
+
+ return resources;
+ }
+
+ private static RangerPolicy.RangerPolicyItem policyItem(List<String>
users, List<String> roles) {
+ RangerPolicy.RangerPolicyItemAccess access = new
RangerPolicy.RangerPolicyItemAccess();
+ access.setType("select");
+ access.setIsAllowed(true);
+
+ RangerPolicy.RangerPolicyItem item = new
RangerPolicy.RangerPolicyItem();
+ item.setUsers(users);
+ item.setRoles(roles);
+ item.setAccesses(Collections.singletonList(access));
+
+ return item;
+ }
+
+ private static RangerRoles roles() {
+ RangerRole readers = new RangerRole();
+ readers.setName("readers");
+ readers.setUsers(Collections.singletonList(new
RangerRole.RoleMember("carol", false)));
+
+ RangerRoles roles = new RangerRoles();
+ roles.setRangerRoles(Collections.singleton(readers));
+
+ return roles;
+ }
+
+ private static RangerAccessRequestImpl request(String user) {
+ RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue("database", "db1");
+ resource.setValue("table", "tbl1");
+ resource.setValue("column", "col1");
+
+ RangerAccessRequestImpl request = new RangerAccessRequestImpl();
+ request.setResource(resource);
+ request.setAccessType("select");
+ request.setUser(user);
+ request.setUserGroups(new HashSet<>(Collections.singleton("public")));
+
+ return request;
+ }
+}
diff --git
a/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerDefaultRequestProcessor.java
b/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerDefaultRequestProcessor.java
index 9b4411722..18c727532 100644
---
a/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerDefaultRequestProcessor.java
+++
b/agents-common/src/test/java/org/apache/ranger/plugin/service/TestRangerDefaultRequestProcessor.java
@@ -29,6 +29,7 @@
import org.apache.ranger.plugin.policyengine.RangerPluginContext;
import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerBatchEvalContext;
import org.apache.ranger.plugin.util.RangerCommonConstants;
import org.apache.ranger.plugin.util.RangerRoles;
import org.apache.ranger.plugin.util.RangerUserStore;
@@ -40,6 +41,7 @@
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -48,7 +50,11 @@
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -332,4 +338,234 @@ public void
test4_NameAndGroupCaseConversionWithoutMapper() {
assertTrue(req.getUserGroups().contains("GRP"));
assertTrue(req.getUserGroups().contains("GRP2"));
}
+
+ @Test
+ public void test5_BatchEvalContextSharesNormalizedUserGroupsAndRoles() {
+ RangerUserStore userStore = new RangerUserStore();
+ Map<String, Map<String, String>> userAttrs = new HashMap<>();
+ Map<String, String> attrs = new HashMap<>();
+ attrs.put(RangerCommonConstants.SCRIPT_FIELD__EMAIL_ADDRESS,
"[email protected]");
+ userAttrs.put("u2", attrs);
+ userStore.setUserAttrMapping(userAttrs);
+ Map<String, Set<String>> userGroups = new HashMap<>();
+ userGroups.put("u2", new HashSet<>(Collections.singletonList("rg9")));
+ userStore.setUserGroupMapping(userGroups);
+
+ RangerRole role = new RangerRole();
+ role.setName("roleX");
+ role.setUsers(Collections.singletonList(new
RangerRole.RoleMember("u2", false)));
+ ProcessorFixture fixture = newFixture(true, true, true, true,
"ranger.plugin.dummy", userStore, roles(role));
+
+ RangerAccessRequestImpl first = newRequest(fixture.serviceDef,
"[email protected]", Collections.singleton("g1"));
+ RangerAccessRequestImpl second = newRequest(fixture.serviceDef,
"[email protected]", Collections.singleton("g1"));
+ attach(fixture.batchEvalContext, first, second);
+
+ fixture.processor.preProcess(first);
+ fixture.processor.preProcess(second);
+
+ assertEquals("u2", first.getUser());
+ assertEquals("u2", second.getUser());
+ assertEquals("clA", first.getClusterName());
+ assertEquals("clA", second.getClusterName());
+ assertSame(first.getUserGroups(), second.getUserGroups());
+ assertTrue(first.getUserGroups().contains("rg9"));
+ assertSame(first.getUserRoles(), second.getUserRoles());
+ assertTrue(first.getUserRoles().contains("roleX"));
+ assertSame(first.getUserRoles(),
RangerAccessRequestUtil.getCurrentUserRolesFromContext(second.getContext()));
+ Mockito.verify(fixture.authContext,
Mockito.times(1)).getRolesForUserAndGroups(Mockito.eq("u2"), Mockito.any());
+ }
+
+ @Test
+ public void test6_BatchEvalContextKeepsSeparateRolesForDifferentGroups() {
+ RangerRole readers = new RangerRole();
+ readers.setName("readers");
+ readers.setGroups(Collections.singletonList(new
RangerRole.RoleMember("g1", false)));
+ RangerRole writers = new RangerRole();
+ writers.setName("writers");
+ writers.setGroups(Collections.singletonList(new
RangerRole.RoleMember("g2", false)));
+ ProcessorFixture fixture = newFixture(false, false, false, false,
"ranger.plugin.svc", new RangerUserStore(), roles(readers, writers));
+
+ RangerAccessRequestImpl readersRequest =
newRequest(fixture.serviceDef, "alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl writersRequest =
newRequest(fixture.serviceDef, "alice", Collections.singleton("g2"));
+ RangerAccessRequestImpl readersAgain = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ attach(fixture.batchEvalContext, readersRequest, writersRequest,
readersAgain);
+
+ fixture.processor.preProcess(readersRequest);
+ fixture.processor.preProcess(writersRequest);
+ fixture.processor.preProcess(readersAgain);
+
+ assertSame(readersRequest.getUserRoles(), readersAgain.getUserRoles());
+ assertNotSame(readersRequest.getUserRoles(),
writersRequest.getUserRoles());
+ assertSame(readersRequest.getUserGroups(),
readersAgain.getUserGroups());
+ assertTrue(readersRequest.getUserRoles().contains("readers"));
+ assertFalse(readersRequest.getUserRoles().contains("writers"));
+ assertTrue(writersRequest.getUserRoles().contains("writers"));
+ assertFalse(writersRequest.getUserRoles().contains("readers"));
+ Mockito.verify(fixture.authContext,
Mockito.times(2)).getRolesForUserAndGroups(Mockito.eq("alice"), Mockito.any());
+ }
+
+ @Test
+ public void test7_BatchEvalContextDoesNotReplacePresetRoles() {
+ RangerRole role = new RangerRole();
+ role.setName("roleX");
+ role.setUsers(Collections.singletonList(new
RangerRole.RoleMember("alice", false)));
+ ProcessorFixture fixture = newFixture(false, false, false, false,
"ranger.plugin.svc", new RangerUserStore(), roles(role));
+
+ Set<String> presetRoles = new
HashSet<>(Collections.singleton("preset"));
+ RangerAccessRequestImpl preset = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl resolved = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl resolvedAgain = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ preset.setUserRoles(presetRoles);
+ attach(fixture.batchEvalContext, preset, resolved, resolvedAgain);
+
+ fixture.processor.preProcess(preset);
+ fixture.processor.preProcess(resolved);
+ fixture.processor.preProcess(resolvedAgain);
+
+ assertSame(presetRoles, preset.getUserRoles());
+ assertSame(resolved.getUserRoles(), resolvedAgain.getUserRoles());
+ assertTrue(resolved.getUserRoles().contains("roleX"));
+ assertFalse(resolved.getUserRoles().contains("preset"));
+ Mockito.verify(fixture.authContext,
Mockito.times(1)).getRolesForUserAndGroups(Mockito.eq("alice"), Mockito.any());
+ }
+
+ @Test
+ public void test8_BatchEvalContextDoesNotReplaceEmptyRoleSet() {
+ ProcessorFixture fixture = newFixture(false, false, false, false,
"ranger.plugin.svc", new RangerUserStore(), new RangerRoles());
+
+ RangerAccessRequestImpl first = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl second = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ Set<String> firstRoles = first.getUserRoles();
+ Set<String> secondRoles = second.getUserRoles();
+ attach(fixture.batchEvalContext, first, second);
+
+ fixture.processor.preProcess(first);
+ fixture.processor.preProcess(second);
+
+ assertTrue(first.getUserRoles().isEmpty());
+ assertSame(firstRoles, first.getUserRoles());
+ assertSame(secondRoles, second.getUserRoles());
+ assertNotSame(first.getUserRoles(), second.getUserRoles());
+ Mockito.verify(fixture.authContext,
Mockito.times(1)).getRolesForUserAndGroups(Mockito.eq("alice"), Mockito.any());
+ }
+
+ @Test
+ public void test9_BatchEvalContextKeepsSeparateRolesForDifferentUsers() {
+ RangerRole aliceRole = new RangerRole();
+ aliceRole.setName("aliceRole");
+ aliceRole.setUsers(Collections.singletonList(new
RangerRole.RoleMember("alice", false)));
+ RangerRole bobRole = new RangerRole();
+ bobRole.setName("bobRole");
+ bobRole.setUsers(Collections.singletonList(new
RangerRole.RoleMember("bob", false)));
+ ProcessorFixture fixture = newFixture(false, false, false, false,
"ranger.plugin.svc", new RangerUserStore(), roles(aliceRole, bobRole));
+
+ RangerAccessRequestImpl alice = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl aliceAgain = newRequest(fixture.serviceDef,
"alice", Collections.singleton("g1"));
+ RangerAccessRequestImpl bob = newRequest(fixture.serviceDef, "bob",
Collections.singleton("g1"));
+ RangerAccessRequestImpl bobAgain = newRequest(fixture.serviceDef,
"bob", Collections.singleton("g1"));
+ attach(fixture.batchEvalContext, alice, bob, aliceAgain, bobAgain);
+
+ fixture.processor.preProcess(alice);
+ fixture.processor.preProcess(bob);
+ fixture.processor.preProcess(aliceAgain);
+ fixture.processor.preProcess(bobAgain);
+
+ assertSame(alice.getUserRoles(), aliceAgain.getUserRoles());
+ assertSame(bob.getUserRoles(), bobAgain.getUserRoles());
+ assertNotSame(alice.getUserRoles(), bob.getUserRoles());
+ assertTrue(alice.getUserRoles().contains("aliceRole"));
+ assertFalse(alice.getUserRoles().contains("bobRole"));
+ assertTrue(bob.getUserRoles().contains("bobRole"));
+ Mockito.verify(fixture.authContext,
Mockito.times(1)).getRolesForUserAndGroups(Mockito.eq("alice"), Mockito.any());
+ Mockito.verify(fixture.authContext,
Mockito.times(1)).getRolesForUserAndGroups(Mockito.eq("bob"), Mockito.any());
+ }
+
+ @Test
+ public void test10_BatchEvalContextAllowsNullUser() {
+ RangerRole groupRole = new RangerRole();
+ groupRole.setName("fromGroup");
+ groupRole.setGroups(Collections.singletonList(new
RangerRole.RoleMember("g1", false)));
+ ProcessorFixture fixture = newFixture(false, false, false, false,
"ranger.plugin.svc", new RangerUserStore(), roles(groupRole));
+
+ RangerAccessRequestImpl first = newRequest(fixture.serviceDef, null,
Collections.singleton("g1"));
+ RangerAccessRequestImpl second = newRequest(fixture.serviceDef, null,
Collections.singleton("g1"));
+ RangerAccessRequestImpl otherGroups = newRequest(fixture.serviceDef,
null, Collections.singleton("g2"));
+ attach(fixture.batchEvalContext, first, second, otherGroups);
+
+ fixture.processor.preProcess(first);
+ fixture.processor.preProcess(second);
+ fixture.processor.preProcess(otherGroups);
+
+ assertNull(first.getUser());
+ assertSame(first.getUserRoles(), second.getUserRoles());
+ assertTrue(first.getUserRoles().contains("fromGroup"));
+ assertFalse(otherGroups.getUserRoles().contains("fromGroup"));
+ assertNotSame(first.getUserRoles(), otherGroups.getUserRoles());
+ Mockito.verify(fixture.authContext,
Mockito.times(2)).getRolesForUserAndGroups(Mockito.isNull(), Mockito.any());
+ }
+
+ private static RangerAccessRequestImpl newRequest(RangerServiceDef
serviceDef, String user, Set<String> groups) {
+ RangerAccessRequestImpl request = new RangerAccessRequestImpl();
+ TestResource resource = new TestResource();
+ resource.setServiceDef(serviceDef);
+ request.setResource(resource);
+ request.setUser(user);
+ request.setUserGroups(new HashSet<>(groups));
+
+ return request;
+ }
+
+ private static void attach(RangerBatchEvalContext batchEvalContext,
RangerAccessRequestImpl... requests) {
+ for (RangerAccessRequestImpl request : requests) {
+ RangerAccessRequestUtil.setBatchEvalContext(request.getContext(),
batchEvalContext);
+ }
+ }
+
+ private static RangerRoles roles(RangerRole... roleArray) {
+ RangerRoles roles = new RangerRoles();
+ roles.setRangerRoles(new HashSet<>(Arrays.asList(roleArray)));
+
+ return roles;
+ }
+
+ private static ProcessorFixture newFixture(boolean useRangerGroups,
boolean useOnlyRangerGroups, boolean convertEmail, boolean nameTransformation,
String prefix, RangerUserStore userStore, RangerRoles roles) {
+ RangerPluginConfig config = Mockito.mock(RangerPluginConfig.class);
+ Mockito.when(config.isUseRangerGroups()).thenReturn(useRangerGroups);
+
Mockito.when(config.isUseOnlyRangerGroups()).thenReturn(useOnlyRangerGroups);
+
Mockito.when(config.isConvertEmailToUsername()).thenReturn(convertEmail);
+ Mockito.when(config.getPropertyPrefix()).thenReturn(prefix);
+ Mockito.when(config.getBoolean(prefix +
RangerCommonConstants.PLUGIN_CONFIG_SUFFIX_NAME_TRANSFORMATION,
false)).thenReturn(nameTransformation);
+
+ RangerPluginContext pluginContext =
Mockito.mock(RangerPluginContext.class);
+ Mockito.when(pluginContext.getConfig()).thenReturn(config);
+ Mockito.when(pluginContext.getClusterName()).thenReturn("clA");
+ Mockito.when(pluginContext.getClusterType()).thenReturn("typeB");
+
+ PolicyEngine policyEngine = Mockito.mock(PolicyEngine.class);
+
Mockito.when(policyEngine.getPluginContext()).thenReturn(pluginContext);
+
Mockito.when(policyEngine.getUseForwardedIPAddress()).thenReturn(false);
+ Mockito.when(policyEngine.getTrustedProxyAddresses()).thenReturn(null);
+
+ RangerAuthContext authContext = Mockito.spy(new RangerAuthContext(new
HashMap<>(), null, roles, userStore));
+ Mockito.when(pluginContext.getAuthContext()).thenReturn(authContext);
+
+ RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("dummy");
+
+ return new ProcessorFixture(new
RangerDefaultRequestProcessor(policyEngine), authContext, serviceDef, new
RangerBatchEvalContext());
+ }
+
+ private static final class ProcessorFixture {
+ private final RangerDefaultRequestProcessor processor;
+ private final RangerAuthContext authContext;
+ private final RangerServiceDef serviceDef;
+ private final RangerBatchEvalContext batchEvalContext;
+
+ private ProcessorFixture(RangerDefaultRequestProcessor processor,
RangerAuthContext authContext, RangerServiceDef serviceDef,
RangerBatchEvalContext batchEvalContext) {
+ this.processor = processor;
+ this.authContext = authContext;
+ this.serviceDef = serviceDef;
+ this.batchEvalContext = batchEvalContext;
+ }
+ }
}
diff --git
a/plugin-kafka/src/test/java/org/apache/ranger/authorization/kafka/authorizer/TestRangerKafkaAuthorizerSharedRoles.java
b/plugin-kafka/src/test/java/org/apache/ranger/authorization/kafka/authorizer/TestRangerKafkaAuthorizerSharedRoles.java
new file mode 100644
index 000000000..9d9e9af6b
--- /dev/null
+++
b/plugin-kafka/src/test/java/org/apache/ranger/authorization/kafka/authorizer/TestRangerKafkaAuthorizerSharedRoles.java
@@ -0,0 +1,223 @@
+/*
+ * 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.authorization.kafka.authorizer;
+
+import org.apache.kafka.common.acl.AclOperation;
+import org.apache.kafka.common.resource.PatternType;
+import org.apache.kafka.common.resource.ResourcePattern;
+import org.apache.kafka.common.resource.ResourceType;
+import org.apache.kafka.common.security.auth.KafkaPrincipal;
+import org.apache.kafka.common.security.auth.SecurityProtocol;
+import org.apache.kafka.server.authorizer.Action;
+import org.apache.kafka.server.authorizer.AuthorizableRequestContext;
+import org.apache.kafka.server.authorizer.AuthorizationResult;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerRole;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.apache.ranger.plugin.policyengine.RangerPolicyEngineOptions;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.store.EmbeddedServiceDefsUtil;
+import org.apache.ranger.plugin.util.RangerAccessRequestUtil;
+import org.apache.ranger.plugin.util.RangerRoles;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Field;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class TestRangerKafkaAuthorizerSharedRoles {
+ @AfterEach
+ void clearPlugin() throws Exception {
+ setPlugin(null);
+ }
+
+ @Test
+ void authorizeSharesOneRoleSetAcrossActions() throws Exception {
+ RecordingPlugin plugin = new RecordingPlugin(pluginConfig());
+ plugin.setPolicies(servicePolicies());
+ plugin.setRoles(roles());
+ setPlugin(plugin);
+
+ AuthorizableRequestContext context = new RequestContext(new
KafkaPrincipal(KafkaPrincipal.USER_TYPE, "alice"));
+
+ List<Action> actions = new ArrayList<>();
+ actions.add(topicRead("orders"));
+ actions.add(topicRead("payments"));
+ actions.add(topicRead("shipping"));
+
+ List<AuthorizationResult> results = new
RangerKafkaAuthorizer().authorize(context, actions);
+
+ assertEquals(Collections.nCopies(actions.size(),
AuthorizationResult.ALLOWED), results);
+ assertEquals(actions.size(), plugin.captured.size());
+
+ Set<String> roles = null;
+
+ for (RangerAccessRequest request : plugin.captured) {
+ assertTrue(request.getUserRoles().contains("readers"));
+
assertNull(RangerAccessRequestUtil.getBatchEvalContext(request.getContext()));
+
+ if (roles == null) {
+ roles = request.getUserRoles();
+ } else {
+ assertSame(roles, request.getUserRoles());
+ }
+ }
+ }
+
+ private static RangerPluginConfig pluginConfig() {
+ RangerPolicyEngineOptions options = new RangerPolicyEngineOptions();
+ options.disablePolicyRefresher = true;
+ options.disableTagRetriever = true;
+ options.disableUserStoreRetriever = true;
+ options.disableGdsInfoRetriever = true;
+
+ return new RangerPluginConfig("kafka", "svc", "app", null, null,
options);
+ }
+
+ private static ServicePolicies servicePolicies() throws Exception {
+ RangerPolicy.RangerPolicyItemAccess access = new
RangerPolicy.RangerPolicyItemAccess();
+ access.setType(RangerKafkaAuthorizer.ACCESS_TYPE_READ);
+ access.setIsAllowed(true);
+
+ RangerPolicy.RangerPolicyItem item = new
RangerPolicy.RangerPolicyItem();
+ item.setUsers(Collections.singletonList("alice"));
+ item.setRoles(Collections.singletonList("readers"));
+ item.setAccesses(Collections.singletonList(access));
+
+ Map<String, RangerPolicy.RangerPolicyResource> resources = new
HashMap<>();
+ resources.put("topic", new RangerPolicy.RangerPolicyResource("*"));
+
+ RangerPolicy policy = new RangerPolicy();
+ policy.setId(1L);
+ policy.setName("all-topics");
+ policy.setService("svc");
+ policy.setIsEnabled(true);
+ policy.setResources(resources);
+ policy.setPolicyItems(Collections.singletonList(item));
+
+ ServicePolicies policies = new ServicePolicies();
+ policies.setServiceName("svc");
+ policies.setPolicyVersion(1L);
+
policies.setServiceDef(EmbeddedServiceDefsUtil.instance().getEmbeddedServiceDef(EmbeddedServiceDefsUtil.EMBEDDED_SERVICEDEF_KAFKA_NAME));
+ policies.setPolicies(Collections.singletonList(policy));
+
+ return policies;
+ }
+
+ private static RangerRoles roles() {
+ RangerRole readers = new RangerRole();
+ readers.setName("readers");
+ readers.setUsers(Collections.singletonList(new
RangerRole.RoleMember("alice", false)));
+
+ RangerRoles roles = new RangerRoles();
+ roles.setRangerRoles(Collections.singleton(readers));
+
+ return roles;
+ }
+
+ private static Action topicRead(String topic) {
+ return new Action(AclOperation.READ, new
ResourcePattern(ResourceType.TOPIC, topic, PatternType.LITERAL), 1, true, true);
+ }
+
+ private static void setPlugin(RangerBasePlugin plugin) throws Exception {
+ Field field =
RangerKafkaAuthorizer.class.getDeclaredField("rangerPlugin");
+
+ field.setAccessible(true);
+ field.set(null, plugin);
+ }
+
+ private static final class RequestContext implements
AuthorizableRequestContext {
+ private final KafkaPrincipal principal;
+
+ private RequestContext(KafkaPrincipal principal) {
+ this.principal = principal;
+ }
+
+ @Override
+ public String listenerName() {
+ return "SASL_PLAINTEXT";
+ }
+
+ @Override
+ public SecurityProtocol securityProtocol() {
+ return SecurityProtocol.SASL_PLAINTEXT;
+ }
+
+ @Override
+ public KafkaPrincipal principal() {
+ return principal;
+ }
+
+ @Override
+ public InetAddress clientAddress() {
+ return InetAddress.getLoopbackAddress();
+ }
+
+ @Override
+ public int requestType() {
+ return 0;
+ }
+
+ @Override
+ public int requestVersion() {
+ return 0;
+ }
+
+ @Override
+ public String clientId() {
+ return "cid";
+ }
+
+ @Override
+ public int correlationId() {
+ return 1;
+ }
+ }
+
+ private static final class RecordingPlugin extends RangerBasePlugin {
+ private Collection<RangerAccessRequest> captured;
+
+ private RecordingPlugin(RangerPluginConfig config) {
+ super(config);
+ }
+
+ @Override
+ public Collection<RangerAccessResult>
isAccessAllowed(Collection<RangerAccessRequest> requests) {
+ captured = requests;
+
+ return super.isAccessAllowed(requests);
+ }
+ }
+}