You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/20 17:57:07 UTC
[3/4] nifi-registry git commit: NIFIREG-186: Adding Ranger authorizer
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/e1bd6e26/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml
new file mode 100644
index 0000000..8b4daee
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>nifi-registry-extensions</artifactId>
+ <groupId>org.apache.nifi.registry</groupId>
+ <version>0.3.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>nifi-registry-ranger-plugin</artifactId>
+ <packaging>jar</packaging>
+
+ <properties>
+ <ranger.version>1.0.0</ranger.version>
+ <ranger.hadoop.version>3.0.0</ranger.hadoop.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-data-model</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <!--
+ Since using the one in the war causes class loading issue between war and ranger/lib,
+ this needs to be in ranger/lib.
+ -->
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-security-api</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <!-- The one in registry/lib can be used -->
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-properties</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <!-- The one in registry/lib can be used -->
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.nifi.registry</groupId>
+ <artifactId>nifi-registry-ranger-jersey-bundle</artifactId>
+ <version>0.3.0-SNAPSHOT</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-bundle</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- Ranger dependencies -->
+ <dependency>
+ <groupId>org.apache.ranger</groupId>
+ <artifactId>ranger-plugins-common</artifactId>
+ <version>${ranger.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </exclusion>
+ <exclusion>
+ <!-- Use nifi-registry-ranger-jersey-bundle instead to avoid
+ javax.ws.rs version conflict. -->
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-bundle</artifactId>
+ </exclusion>
+ <exclusion>
+ <!-- The one in hadoop-common conflicts with jersey-bundle. -->
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-json</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ranger</groupId>
+ <artifactId>ranger-plugins-audit</artifactId>
+ <version>${ranger.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.ranger</groupId>
+ <artifactId>credentialbuilder</artifactId>
+ <version>${ranger.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+
+ <!-- hadoop-client is needed for auditing to HDFS -->
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-client</artifactId>
+ <version>${ranger.hadoop.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-yarn-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-yarn-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-mapreduce-client</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-mapreduce-client-core</artifactId>
+ </exclusion>
+ <exclusion>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-mapreduce-client-jobclient</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- hadoop-common and hadoop-auth are transitive dependencies of ranger client, but we need to make sure they
+ are the same version as hadoop-client above -->
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-common</artifactId>
+ <version>${ranger.hadoop.version}</version>
+ <exclusions>
+ <exclusion>
+ <groupId>com.google.code.findbugs</groupId>
+ <artifactId>jsr305</artifactId>
+ </exclusion>
+ <exclusion>
+ <!-- Avoid using old jsr311 which does not have
+ javax.ws.rs.core.Application.getProperties method
+ that is used by newer Jetty. -->
+ <groupId>javax.ws.rs</groupId>
+ <artifactId>jsr311-api</artifactId>
+ </exclusion>
+ <exclusion>
+ <!-- Avoid using old jersey-core which does not have
+ javax.ws.rs.core.Application.getProperties method
+ that is used by newer Jetty. -->
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hadoop</groupId>
+ <artifactId>hadoop-auth</artifactId>
+ <version>${ranger.hadoop.version}</version>
+ </dependency>
+
+ <!-- Followings are required by com.sun.jersey.core.spi.factory.MessageBodyFactory -->
+ <dependency>
+ <groupId>javax.mail</groupId>
+ <artifactId>mail</artifactId>
+ <version>1.4.7</version>
+ </dependency>
+ <dependency>
+ <groupId>net.java.dev.rome</groupId>
+ <artifactId>rome</artifactId>
+ <version>1.0.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.jettison</groupId>
+ <artifactId>jettison</artifactId>
+ <version>1.3.8</version>
+ </dependency>
+
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>1.7.12</version>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+</project>
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/e1bd6e26/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java
new file mode 100644
index 0000000..05582b6
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java
@@ -0,0 +1,431 @@
+/*
+ * 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.nifi.registry.ranger;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authorization.AccessPolicy;
+import org.apache.nifi.registry.security.authorization.AccessPolicyProvider;
+import org.apache.nifi.registry.security.authorization.AccessPolicyProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.AuthorizationAuditor;
+import org.apache.nifi.registry.security.authorization.AuthorizationRequest;
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.security.authorization.ManagedAuthorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.UserContextKeys;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderLookup;
+import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.util.PropertyValue;
+import org.apache.ranger.audit.model.AuthzAuditEvent;
+import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
+import org.apache.ranger.plugin.audit.RangerDefaultAuditHandler;
+import org.apache.ranger.plugin.policyengine.RangerAccessRequestImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl;
+import org.apache.ranger.plugin.policyengine.RangerAccessResult;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.MalformedURLException;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+
+/**
+ * Authorizer implementation that uses Apache Ranger to make authorization decisions.
+ */
+public class RangerAuthorizer implements ManagedAuthorizer, AuthorizationAuditor {
+
+ private static final Logger logger = LoggerFactory.getLogger(RangerAuthorizer.class);
+
+ private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+
+ private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
+
+ static final String USER_GROUP_PROVIDER = "User Group Provider";
+
+ static final String RANGER_AUDIT_PATH_PROP = "Ranger Audit Config Path";
+ static final String RANGER_SECURITY_PATH_PROP = "Ranger Security Config Path";
+ static final String RANGER_KERBEROS_ENABLED_PROP = "Ranger Kerberos Enabled";
+ static final String RANGER_ADMIN_IDENTITY_PROP = "Ranger Admin Identity";
+ static final String RANGER_SERVICE_TYPE_PROP = "Ranger Service Type";
+ static final String RANGER_APP_ID_PROP = "Ranger Application Id";
+
+ static final String RANGER_NIFI_REG_RESOURCE_NAME = "nifi-registry-resource";
+ private static final String DEFAULT_SERVICE_TYPE = "nifi-registry";
+ private static final String DEFAULT_APP_ID = "nifi-registry";
+ static final String RESOURCES_RESOURCE = "/policies";
+ static final String HADOOP_SECURITY_AUTHENTICATION = "hadoop.security.authentication";
+ private static final String KERBEROS_AUTHENTICATION = "kerberos";
+
+ private final Map<AuthorizationRequest, RangerAccessResult> resultLookup = new WeakHashMap<>();
+
+ private volatile RangerBasePluginWithPolicies rangerPlugin = null;
+ private volatile RangerDefaultAuditHandler defaultAuditHandler = null;
+ private volatile String rangerAdminIdentity = null;
+ private volatile NiFiRegistryProperties registryProperties;
+
+ private UserGroupProviderLookup userGroupProviderLookup;
+ private UserGroupProvider userGroupProvider;
+
+
+ @Override
+ public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException {
+ userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+ final String userGroupProviderKey = configurationContext.getProperty(USER_GROUP_PROVIDER).getValue();
+ if (StringUtils.isEmpty(userGroupProviderKey)) {
+ throw new SecurityProviderCreationException(USER_GROUP_PROVIDER + " must be specified.");
+ }
+ userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey);
+
+ // ensure the desired access policy provider has a user group provider
+ if (userGroupProvider == null) {
+ throw new SecurityProviderCreationException(String.format("Unable to locate configured User Group Provider: %s", userGroupProviderKey));
+ }
+
+ try {
+ if (rangerPlugin == null) {
+ logger.info("initializing base plugin");
+
+ final PropertyValue securityConfigValue = configurationContext.getProperty(RANGER_SECURITY_PATH_PROP);
+ addRequiredResource(RANGER_SECURITY_PATH_PROP, securityConfigValue);
+
+ final PropertyValue auditConfigValue = configurationContext.getProperty(RANGER_AUDIT_PATH_PROP);
+ addRequiredResource(RANGER_AUDIT_PATH_PROP, auditConfigValue);
+
+ boolean rangerKerberosEnabled = Boolean.valueOf(getConfigValue(configurationContext, RANGER_KERBEROS_ENABLED_PROP, Boolean.FALSE.toString()));
+
+ if (rangerKerberosEnabled) {
+ // configure UGI for when RangerAdminRESTClient calls UserGroupInformation.isSecurityEnabled()
+ final Configuration securityConf = new Configuration();
+ securityConf.set(HADOOP_SECURITY_AUTHENTICATION, KERBEROS_AUTHENTICATION);
+ UserGroupInformation.setConfiguration(securityConf);
+
+ // login with the nifi registry principal and keytab, RangerAdminRESTClient will use Ranger's MiscUtil which
+ // will grab UserGroupInformation.getLoginUser() and call ugi.checkTGTAndReloginFromKeytab();
+ final String registryPrincipal = registryProperties.getKerberosServicePrincipal();
+ final String registryKeytab = registryProperties.getKerberosServiceKeytabLocation();
+
+ if (StringUtils.isBlank(registryPrincipal) || StringUtils.isBlank(registryKeytab)) {
+ throw new SecurityProviderCreationException("Principal and Keytab must be provided when Kerberos is enabled");
+ }
+
+ UserGroupInformation.loginUserFromKeytab(registryPrincipal.trim(), registryKeytab.trim());
+ }
+
+ final String serviceType = getConfigValue(configurationContext, RANGER_SERVICE_TYPE_PROP, DEFAULT_SERVICE_TYPE);
+ final String appId = getConfigValue(configurationContext, RANGER_APP_ID_PROP, DEFAULT_APP_ID);
+
+ rangerPlugin = createRangerBasePlugin(serviceType, appId);
+ rangerPlugin.init();
+
+ defaultAuditHandler = new RangerDefaultAuditHandler();
+ rangerAdminIdentity = getConfigValue(configurationContext, RANGER_ADMIN_IDENTITY_PROP, null);
+
+ } else {
+ logger.info("base plugin already initialized");
+ }
+ } catch (Throwable t) {
+ throw new SecurityProviderCreationException("Error creating RangerBasePlugin", t);
+ }
+ }
+
+ protected RangerBasePluginWithPolicies createRangerBasePlugin(final String serviceType, final String appId) {
+ return new RangerBasePluginWithPolicies(serviceType, appId, userGroupProvider);
+ }
+
+ @Override
+ public AuthorizationResult authorize(final AuthorizationRequest request) throws SecurityProviderCreationException {
+ final String identity = request.getIdentity();
+ final Set<String> userGroups = request.getGroups();
+ final String resourceIdentifier = request.getResource().getIdentifier();
+
+ // if a ranger admin identity was provided, and it equals the identity making the request,
+ // and the request is to retrieve the resources, then allow it through
+ if (StringUtils.isNotBlank(rangerAdminIdentity) && rangerAdminIdentity.equals(identity)
+ && resourceIdentifier.equals(RESOURCES_RESOURCE)) {
+ return AuthorizationResult.approved();
+ }
+
+ final String clientIp;
+ if (request.getUserContext() != null) {
+ clientIp = request.getUserContext().get(UserContextKeys.CLIENT_ADDRESS.name());
+ } else {
+ clientIp = null;
+ }
+
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RANGER_NIFI_REG_RESOURCE_NAME, resourceIdentifier);
+
+ final RangerAccessRequestImpl rangerRequest = new RangerAccessRequestImpl();
+ rangerRequest.setResource(resource);
+ rangerRequest.setAction(request.getAction().name());
+ rangerRequest.setAccessType(request.getAction().name());
+ rangerRequest.setUser(identity);
+ rangerRequest.setUserGroups(userGroups);
+ rangerRequest.setAccessTime(new Date());
+
+ if (!StringUtils.isBlank(clientIp)) {
+ rangerRequest.setClientIPAddress(clientIp);
+ }
+
+ final RangerAccessResult result = rangerPlugin.isAccessAllowed(rangerRequest);
+
+ // store the result for auditing purposes later if appropriate
+ if (request.isAccessAttempt()) {
+ synchronized (resultLookup) {
+ resultLookup.put(request, result);
+ }
+ }
+
+ if (result != null && result.getIsAllowed()) {
+ // return approved
+ return AuthorizationResult.approved();
+ } else {
+ // if result.getIsAllowed() is false, then we need to determine if it was because no policy exists for the
+ // given resource, or if it was because a policy exists but not for the given user or action
+ final boolean doesPolicyExist = rangerPlugin.doesPolicyExist(request.getResource().getIdentifier(), request.getAction());
+
+ if (doesPolicyExist) {
+ final String reason = result == null ? null : result.getReason();
+ if (reason != null) {
+ logger.debug(String.format("Unable to authorize %s due to %s", identity, reason));
+ }
+
+ // a policy does exist for the resource so we were really denied access here
+ return AuthorizationResult.denied(request.getExplanationSupplier().get());
+ } else {
+ // a policy doesn't exist so return resource not found so NiFi Registry can work back up the resource hierarchy
+ return AuthorizationResult.resourceNotFound();
+ }
+ }
+ }
+
+ @Override
+ public void auditAccessAttempt(final AuthorizationRequest request, final AuthorizationResult result) {
+ final RangerAccessResult rangerResult;
+ synchronized (resultLookup) {
+ rangerResult = resultLookup.remove(request);
+ }
+
+ if (rangerResult != null && rangerResult.getIsAudited()) {
+ AuthzAuditEvent event = defaultAuditHandler.getAuthzEvents(rangerResult);
+
+ // update the event with the originally requested resource
+ event.setResourceType(RANGER_NIFI_REG_RESOURCE_NAME);
+ event.setResourcePath(request.getRequestedResource().getIdentifier());
+
+ defaultAuditHandler.logAuthzAudit(event);
+ }
+ }
+
+ @Override
+ public void preDestruction() throws SecurityProviderCreationException {
+ if (rangerPlugin != null) {
+ try {
+ rangerPlugin.cleanup();
+ rangerPlugin = null;
+ } catch (Throwable t) {
+ throw new SecurityProviderCreationException("Error cleaning up RangerBasePlugin", t);
+ }
+ }
+ }
+
+ @AuthorizerContext
+ public void setRegistryProperties(final NiFiRegistryProperties properties) {
+ this.registryProperties = properties;
+ }
+
+ /**
+ * Adds a resource to the RangerConfiguration singleton so it is already there by the time RangerBasePlugin.init()
+ * is called.
+ *
+ * @param name the name of the given PropertyValue from the AuthorizationConfigurationContext
+ * @param resourceValue the value for the given name, should be a full path to a file
+ */
+ private void addRequiredResource(final String name, final PropertyValue resourceValue) {
+ if (resourceValue == null || StringUtils.isBlank(resourceValue.getValue())) {
+ throw new SecurityProviderCreationException(name + " must be specified.");
+ }
+
+ final File resourceFile = new File(resourceValue.getValue());
+ if (!resourceFile.exists() || !resourceFile.canRead()) {
+ throw new SecurityProviderCreationException(resourceValue + " does not exist, or can not be read");
+ }
+
+ try {
+ RangerConfiguration.getInstance().addResource(resourceFile.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new SecurityProviderCreationException("Error creating URI for " + resourceValue, e);
+ }
+ }
+
+ private String getConfigValue(final AuthorizerConfigurationContext context, final String name, final String defaultValue) {
+ final PropertyValue configValue = context.getProperty(name);
+
+ String retValue = defaultValue;
+ if (configValue != null && !StringUtils.isBlank(configValue.getValue())) {
+ retValue = configValue.getValue();
+ }
+
+ return retValue;
+ }
+
+ @Override
+ public String getFingerprint() throws AuthorizationAccessException {
+ final StringWriter out = new StringWriter();
+ try {
+ // create the document
+ final DocumentBuilder documentBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+ final Document document = documentBuilder.newDocument();
+
+ // create the root element
+ final Element managedRangerAuthorizationsElement = document.createElement("managedRangerAuthorizations");
+ document.appendChild(managedRangerAuthorizationsElement);
+
+ // create the user group provider element
+ final Element userGroupProviderElement = document.createElement(USER_GROUP_PROVIDER_ELEMENT);
+ managedRangerAuthorizationsElement.appendChild(userGroupProviderElement);
+
+ // append fingerprint if the provider is configurable
+ if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+ userGroupProviderElement.appendChild(document.createTextNode(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint()));
+ }
+
+ final Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.transform(new DOMSource(document), new StreamResult(out));
+ } catch (ParserConfigurationException | TransformerException e) {
+ throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+ }
+
+ return out.toString();
+ }
+
+ private String parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
+ final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+
+ try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+ final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+ final Document document = docBuilder.parse(in);
+ final Element rootElement = document.getDocumentElement();
+
+ final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT);
+ if (userGroupProviderList.getLength() != 1) {
+ throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint));
+ }
+
+ final Node userGroupProvider = userGroupProviderList.item(0);
+ return userGroupProvider.getTextContent();
+ } catch (SAXException | ParserConfigurationException | IOException e) {
+ throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+ }
+ }
+
+ @Override
+ public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+ if (StringUtils.isBlank(fingerprint)) {
+ return;
+ }
+
+ final String userGroupFingerprint = parseFingerprint(fingerprint);
+
+ if (StringUtils.isNotBlank(userGroupFingerprint) && userGroupProvider instanceof ConfigurableUserGroupProvider) {
+ ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(userGroupFingerprint);
+ }
+ }
+
+ @Override
+ public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+ final String userGroupFingerprint = parseFingerprint(proposedFingerprint);
+
+ if (StringUtils.isNotBlank(userGroupFingerprint)) {
+ if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+ ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(userGroupFingerprint);
+ } else {
+ throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting.");
+ }
+ }
+ }
+
+ @Override
+ public AccessPolicyProvider getAccessPolicyProvider() {
+ return new AccessPolicyProvider() {
+ @Override
+ public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+ return rangerPlugin.getAccessPolicies();
+ }
+
+ @Override
+ public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+ return rangerPlugin.getAccessPolicy(identifier);
+ }
+
+ @Override
+ public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+ return rangerPlugin.getAccessPolicy(resourceIdentifier, action);
+ }
+
+ @Override
+ public UserGroupProvider getUserGroupProvider() {
+ return userGroupProvider;
+ }
+
+ @Override
+ public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+ }
+
+ @Override
+ public void preDestruction() throws SecurityProviderCreationException {
+ }
+ };
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/e1bd6e26/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerBasePluginWithPolicies.java
----------------------------------------------------------------------
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerBasePluginWithPolicies.java b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerBasePluginWithPolicies.java
new file mode 100644
index 0000000..96994da
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerBasePluginWithPolicies.java
@@ -0,0 +1,291 @@
+/*
+ * 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.nifi.registry.ranger;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.security.authorization.AccessPolicy;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.ranger.plugin.service.RangerBasePlugin;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Extends the base plugin to convert service policies into NiFi Registry policy domain model.
+ */
+public class RangerBasePluginWithPolicies extends RangerBasePlugin {
+
+ private static final Logger logger = LoggerFactory.getLogger(RangerBasePluginWithPolicies.class);
+
+ private final static String WILDCARD_ASTERISK = "*";
+
+ private UserGroupProvider userGroupProvider;
+ private AtomicReference<PolicyLookup> policies = new AtomicReference<>(new PolicyLookup());
+
+ public RangerBasePluginWithPolicies(final String serviceType, final String appId) {
+ this(serviceType, appId, null);
+ }
+
+ public RangerBasePluginWithPolicies(final String serviceType, final String appId, final UserGroupProvider userGroupProvider) {
+ super(serviceType, appId);
+ this.userGroupProvider = userGroupProvider; // will be null if used outside of the managed RangerAuthorizer
+ }
+
+ @Override
+ public void setPolicies(final ServicePolicies policies) {
+ super.setPolicies(policies);
+
+ if (policies == null || policies.getPolicies() == null) {
+ this.policies.set(new PolicyLookup());
+ } else {
+ this.policies.set(createPolicyLookup(policies));
+ }
+ }
+
+ /**
+ * Determines if a policy exists for the given resource.
+ *
+ * @param resourceIdentifier the id of the resource
+ *
+ * @return true if a policy exists for the given resource, false otherwise
+ */
+ public boolean doesPolicyExist(final String resourceIdentifier, final RequestAction requestAction) {
+ if (resourceIdentifier == null) {
+ return false;
+ }
+
+ final PolicyLookup policyLookup = policies.get();
+ return policyLookup.getAccessPolicy(resourceIdentifier, requestAction) != null;
+ }
+
+ public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+ return policies.get().getAccessPolicies();
+ }
+
+ public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+ return policies.get().getAccessPolicy(identifier);
+ }
+
+ public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+ return policies.get().getAccessPolicy(resourceIdentifier, action);
+ }
+
+ private PolicyLookup createPolicyLookup(final ServicePolicies servicePolicies) {
+ final Map<String, AccessPolicy> policiesByIdentifier = new HashMap<>();
+ final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource = new HashMap<>();
+
+ logger.debug("Converting Ranger ServicePolicies model into NiFi Registry policy model for viewing purposes in NiFi Registry UI.");
+
+ servicePolicies.getPolicies().stream().forEach(policy -> {
+ // only consider policies that are enabled
+ if (Boolean.TRUE.equals(policy.getIsEnabled())) {
+ // get all the resources for this policy - excludes/recursive support disabled
+ final Set<String> resources = policy.getResources().values().stream()
+ .filter(resource -> {
+ final boolean isMissingResource;
+ final boolean isWildcard;
+ if (resource.getValues() == null) {
+ isMissingResource = true;
+ isWildcard = false;
+ } else {
+ isMissingResource = false;
+ isWildcard = resource.getValues().stream().anyMatch(value -> value.contains(WILDCARD_ASTERISK));
+ }
+
+ final boolean isExclude = Boolean.TRUE.equals(resource.getIsExcludes());
+ final boolean isRecursive = Boolean.TRUE.equals(resource.getIsRecursive());
+
+ if (isMissingResource) {
+ logger.warn("Encountered resources missing values. Skipping policy for viewing purposes. Will still be used for access decisions.");
+ }
+ if (isWildcard) {
+ logger.warn(String.format("Resources [%s] include a wildcard value. Skipping policy for viewing purposes. "
+ + "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
+ }
+ if (isExclude) {
+ logger.warn(String.format("Resources [%s] marked as an exclude policy. Skipping policy for viewing purposes. "
+ + "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
+ }
+ if (isRecursive) {
+ logger.warn(String.format("Resources [%s] marked as a recursive policy. Skipping policy for viewing purposes. "
+ + "Will still be used for access decisions.", StringUtils.join(resource.getValues(), ", ")));
+ }
+
+ return !isMissingResource && !isWildcard && !isExclude && !isRecursive;
+ })
+ .flatMap(resource -> resource.getValues().stream())
+ .collect(Collectors.toSet());
+
+ policy.getPolicyItems().forEach(policyItem -> {
+ // get all the users for this policy item, excluding unknown users
+ final Set<String> userIds = policyItem.getUsers().stream()
+ .map(userIdentity -> getUser(userIdentity))
+ .filter(Objects::nonNull)
+ .map(user -> user.getIdentifier())
+ .collect(Collectors.toSet());
+
+ // get all groups for this policy item, excluding unknown groups
+ final Set<String> groupIds = policyItem.getGroups().stream()
+ .map(groupName -> getGroup(groupName))
+ .filter(Objects::nonNull)
+ .map(group -> group.getIdentifier())
+ .collect(Collectors.toSet());
+
+ // check if this policy item is a delegate admin
+ final boolean isDelegateAdmin = Boolean.TRUE.equals(policyItem.getDelegateAdmin());
+
+ policyItem.getAccesses().forEach(access -> {
+ try {
+ // interpret the request action
+ final RequestAction action = RequestAction.valueOf(access.getType());
+
+ // function for creating an access policy
+ final Function<String, AccessPolicy> createPolicy = resource -> new AccessPolicy.Builder()
+ .identifierGenerateFromSeed(resource + access.getType())
+ .resource(resource)
+ .action(action)
+ .addUsers(userIds)
+ .addGroups(groupIds)
+ .build();
+
+ resources.forEach(resource -> {
+ // create the access policy for the specified resource
+ final AccessPolicy accessPolicy = createPolicy.apply(resource);
+ policiesByIdentifier.put(accessPolicy.getIdentifier(), accessPolicy);
+ policiesByResource.computeIfAbsent(resource, r -> new HashMap<>()).put(action, accessPolicy);
+
+ // if this is a delegate admin, also create the admin policy for the specified resource
+ if (isDelegateAdmin) {
+ // build the admin resource identifier
+ final String adminResource;
+ if (resource.startsWith("/")) {
+ adminResource = "/policies" + resource;
+ } else {
+ adminResource = "/policies/" + resource;
+ }
+
+ final AccessPolicy adminAccessPolicy = createPolicy.apply(adminResource);
+ policiesByIdentifier.put(adminAccessPolicy.getIdentifier(), adminAccessPolicy);
+ policiesByResource.computeIfAbsent(adminResource, ar -> new HashMap<>()).put(action, adminAccessPolicy);
+ }
+ });
+ } catch (final IllegalArgumentException e) {
+ logger.warn(String.format("Unrecognized request action '%s'. Skipping policy for viewing purposes. Will still be used for access decisions.", access.getType()));
+ }
+ });
+ });
+ }
+ });
+
+ return new PolicyLookup(policiesByIdentifier, policiesByResource);
+ }
+
+ private User getUser(final String identity) {
+ if (userGroupProvider == null) {
+ // generate the user deterministically when running outside of the ManagedRangerAuthorizer
+ return new User.Builder().identifierGenerateFromSeed(identity).identity(identity).build();
+ } else {
+ // find the user in question
+ final User user = userGroupProvider.getUserByIdentity(identity);
+
+ if (user == null) {
+ logger.warn(String.format("Cannot find user '%s' in the configured User Group Provider. Skipping user for viewing purposes. Will still be used for access decisions.", identity));
+ }
+
+ return user;
+ }
+ }
+
+ private Group getGroup(final String name) {
+ if (userGroupProvider == null) {
+ // generate the group deterministically when running outside of the ManagedRangerAuthorizer
+ return new Group.Builder().identifierGenerateFromSeed(name).name(name).build();
+ } else {
+ // find the group in question
+ final Group group = userGroupProvider.getGroups().stream().filter(g -> g.getName().equals(name)).findFirst().orElse(null);
+
+ if (group == null) {
+ logger.warn(String.format("Cannot find group '%s' in the configured User Group Provider. Skipping group for viewing purposes. Will still be used for access decisions.", name));
+ }
+
+ return group;
+ }
+ }
+
+ private static class PolicyLookup {
+
+ private final Map<String, AccessPolicy> policiesByIdentifier;
+ private final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource;
+ private final Set<AccessPolicy> allPolicies;
+
+ private PolicyLookup() {
+ this(null, null);
+ }
+
+ private PolicyLookup(final Map<String, AccessPolicy> policiesByIdentifier, final Map<String, Map<RequestAction, AccessPolicy>> policiesByResource) {
+ if (policiesByIdentifier == null) {
+ allPolicies = Collections.EMPTY_SET;
+ } else {
+ allPolicies = Collections.unmodifiableSet(new HashSet<>(policiesByIdentifier.values()));
+ }
+
+ this.policiesByIdentifier = policiesByIdentifier;
+ this.policiesByResource = policiesByResource;
+ }
+
+ private Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+ return allPolicies;
+ }
+
+ private AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+ if (policiesByIdentifier == null) {
+ return null;
+ }
+
+ return policiesByIdentifier.get(identifier);
+ }
+
+ private AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+ if (policiesByResource == null) {
+ return null;
+ }
+
+ final Map<RequestAction, AccessPolicy> policiesForResource = policiesByResource.get(resourceIdentifier);
+
+ if (policiesForResource != null) {
+ return policiesForResource.get(action);
+ }
+
+ return null;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/e1bd6e26/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.Authorizer
----------------------------------------------------------------------
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.Authorizer b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.Authorizer
new file mode 100644
index 0000000..f8c1bc3
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.registry.security.authorization.Authorizer
@@ -0,0 +1,15 @@
+# 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.
+org.apache.nifi.registry.ranger.RangerAuthorizer
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/e1bd6e26/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java
new file mode 100644
index 0000000..c97d27a
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java
@@ -0,0 +1,718 @@
+/*
+ * 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.nifi.registry.ranger;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.security.authorization.AuthorizationRequest;
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+import org.apache.nifi.registry.security.authorization.UserContextKeys;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderLookup;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.util.StandardPropertyValue;
+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.RangerAccessResultProcessor;
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+
+import javax.security.auth.login.LoginException;
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TestRangerAuthorizer {
+
+ private static final String TENANT_FINGERPRINT =
+ "<tenants>"
+ + "<user identifier=\"user-id-1\" identity=\"user-1\"></user>"
+ + "<group identifier=\"group-id-1\" name=\"group-1\">"
+ + "<groupUser identifier=\"user-id-1\"></groupUser>"
+ + "</group>"
+ + "</tenants>";
+
+ private static final String EMPTY_FINGERPRINT = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
+ + "<managedRangerAuthorizations>"
+ + "<userGroupProvider/>"
+ + "</managedRangerAuthorizations>";
+
+ private static final String NON_EMPTY_FINGERPRINT = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>"
+ + "<managedRangerAuthorizations>"
+ + "<userGroupProvider>"
+ + "<tenants>"
+ + "<user identifier=\"user-id-1\" identity=\"user-1\"></user>"
+ + "<group identifier=\"group-id-1\" name=\"group-1\">"
+ + "<groupUser identifier=\"user-id-1\"></groupUser>"
+ + "</group>"
+ + "</tenants>"
+ + "</userGroupProvider>"
+ + "</managedRangerAuthorizations>";
+
+ private MockRangerAuthorizer authorizer;
+ private RangerBasePluginWithPolicies rangerBasePlugin;
+
+ private final String serviceType = "nifiRegistryService";
+ private final String appId = "nifiRegistryAppId";
+
+ private RangerAccessResult allowedResult;
+ private RangerAccessResult notAllowedResult;
+
+ private void setup(final NiFiRegistryProperties registryProperties,
+ final UserGroupProvider userGroupProvider,
+ final AuthorizerConfigurationContext configurationContext) {
+ // have to initialize this system property before anything else
+ File krb5conf = new File("src/test/resources/krb5.conf");
+ assertTrue(krb5conf.exists());
+ System.setProperty("java.security.krb5.conf", krb5conf.getAbsolutePath());
+
+ // rest the authentication to simple in case any tests set it to kerberos
+ final Configuration securityConf = new Configuration();
+ securityConf.set(RangerAuthorizer.HADOOP_SECURITY_AUTHENTICATION, "simple");
+ UserGroupInformation.setConfiguration(securityConf);
+
+ rangerBasePlugin = mock(RangerBasePluginWithPolicies.class);
+ authorizer = new MockRangerAuthorizer(rangerBasePlugin);
+
+ final UserGroupProviderLookup userGroupProviderLookup = mock(UserGroupProviderLookup.class);
+ when(userGroupProviderLookup.getUserGroupProvider(eq("user-group-provider"))).thenReturn(userGroupProvider);
+
+ final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+ when(initializationContext.getUserGroupProviderLookup()).thenReturn(userGroupProviderLookup);
+
+ authorizer.setRegistryProperties(registryProperties);
+ authorizer.initialize(initializationContext);
+ authorizer.onConfigured(configurationContext);
+
+ assertFalse(UserGroupInformation.isSecurityEnabled());
+
+ allowedResult = mock(RangerAccessResult.class);
+ when(allowedResult.getIsAllowed()).thenReturn(true);
+
+ notAllowedResult = mock(RangerAccessResult.class);
+ when(notAllowedResult.getIsAllowed()).thenReturn(false);
+ }
+
+ private AuthorizerConfigurationContext createMockConfigContext() {
+ AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.USER_GROUP_PROVIDER)))
+ .thenReturn(new StandardPropertyValue("user-group-provider"));
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_SECURITY_PATH_PROP)))
+ .thenReturn(new StandardPropertyValue("src/test/resources/ranger/ranger-nifi-registry-security.xml"));
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_AUDIT_PATH_PROP)))
+ .thenReturn(new StandardPropertyValue("src/test/resources/ranger/ranger-nifi-registry-audit.xml"));
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_APP_ID_PROP)))
+ .thenReturn(new StandardPropertyValue(appId));
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_SERVICE_TYPE_PROP)))
+ .thenReturn(new StandardPropertyValue(serviceType));
+
+ return configurationContext;
+ }
+
+ @Test
+ public void testOnConfigured() {
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), createMockConfigContext());
+
+ verify(rangerBasePlugin, times(1)).init();
+
+ assertEquals(appId, authorizer.mockRangerBasePlugin.getAppId());
+ assertEquals(serviceType, authorizer.mockRangerBasePlugin.getServiceType());
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutKeytab() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new StandardPropertyValue("true"));
+
+ NiFiRegistryProperties registryProperties = mock(NiFiRegistryProperties.class);
+ when(registryProperties.getKerberosServicePrincipal()).thenReturn("");
+
+
+ try {
+ setup(registryProperties, mock(UserGroupProvider.class), configurationContext);
+ Assert.fail("Should have thrown exception");
+ } catch (SecurityProviderCreationException e) {
+ // want to make sure this exception is from our authorizer code
+ verifyOnlyAuthorizeCreationExceptions(e);
+ }
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutPrincipal() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new StandardPropertyValue("true"));
+
+ NiFiRegistryProperties registryProperties = mock(NiFiRegistryProperties.class);
+ when(registryProperties.getKerberosServiceKeytabLocation()).thenReturn("");
+
+ try {
+ setup(registryProperties, mock(UserGroupProvider.class), configurationContext);
+ Assert.fail("Should have thrown exception");
+ } catch (SecurityProviderCreationException e) {
+ // want to make sure this exception is from our authorizer code
+ verifyOnlyAuthorizeCreationExceptions(e);
+ }
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutKeytabOrPrincipal() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new StandardPropertyValue("true"));
+
+ NiFiRegistryProperties registryProperties = mock(NiFiRegistryProperties.class);
+ when(registryProperties.getKerberosServiceKeytabLocation()).thenReturn("");
+ when(registryProperties.getKerberosServicePrincipal()).thenReturn("");
+
+ try {
+ setup(registryProperties, mock(UserGroupProvider.class), configurationContext);
+ Assert.fail("Should have thrown exception");
+ } catch (SecurityProviderCreationException e) {
+ // want to make sure this exception is from our authorizer code
+ verifyOnlyAuthorizeCreationExceptions(e);
+ }
+ }
+
+ private void verifyOnlyAuthorizeCreationExceptions(SecurityProviderCreationException e) {
+ boolean foundOtherException = false;
+ Throwable cause = e.getCause();
+ while (cause != null) {
+ if (!(cause instanceof SecurityProviderCreationException)) {
+ foundOtherException = true;
+ break;
+ }
+ cause = cause.getCause();
+ }
+ assertFalse(foundOtherException);
+ }
+
+ @Test
+ public void testKerberosEnabled() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new StandardPropertyValue("true"));
+
+ NiFiRegistryProperties registryProperties = mock(NiFiRegistryProperties.class);
+ when(registryProperties.getKerberosServiceKeytabLocation()).thenReturn("test");
+ when(registryProperties.getKerberosServicePrincipal()).thenReturn("test");
+
+ try {
+ setup(registryProperties, mock(UserGroupProvider.class), configurationContext);
+ Assert.fail("Should have thrown exception");
+ } catch (SecurityProviderCreationException e) {
+ // getting a LoginException here means we attempted to login which is what we want
+ boolean foundLoginException = false;
+ Throwable cause = e.getCause();
+ while (cause != null) {
+ if (cause instanceof LoginException) {
+ foundLoginException = true;
+ break;
+ }
+ cause = cause.getCause();
+ }
+ assertTrue(foundLoginException);
+ }
+ }
+
+ @Test
+ public void testApprovedWithDirectAccess() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ final String systemResource = "/system";
+ final RequestAction action = RequestAction.WRITE;
+ final String user = "admin";
+ final String clientIp = "192.168.1.1";
+
+ final Map<String,String> userContext = new HashMap<>();
+ userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), clientIp);
+
+ // the incoming NiFi request to test
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new MockResource(systemResource, systemResource))
+ .action(action)
+ .identity(user)
+ .resourceContext(new HashMap<>())
+ .userContext(userContext)
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerAuthorizer.RANGER_NIFI_REG_RESOURCE_NAME, systemResource);
+
+ final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl();
+ expectedRangerRequest.setResource(resource);
+ expectedRangerRequest.setAction(request.getAction().name());
+ expectedRangerRequest.setAccessType(request.getAction().name());
+ expectedRangerRequest.setUser(request.getIdentity());
+ expectedRangerRequest.setClientIPAddress(clientIp);
+
+ // a non-null result processor should be used for direct access
+ when(rangerBasePlugin.isAccessAllowed(
+ argThat(new RangerAccessRequestMatcher(expectedRangerRequest)))
+ ).thenReturn(allowedResult);
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ assertEquals(AuthorizationResult.approved().getResult(), result.getResult());
+ }
+
+ @Test
+ public void testApprovedWithNonDirectAccess() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ final String systemResource = "/system";
+ final RequestAction action = RequestAction.WRITE;
+ final String user = "admin";
+
+ // the incoming NiFi request to test
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new MockResource(systemResource, systemResource))
+ .action(action)
+ .identity(user)
+ .resourceContext(new HashMap<>())
+ .accessAttempt(false)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerAuthorizer.RANGER_NIFI_REG_RESOURCE_NAME, systemResource);
+
+ final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl();
+ expectedRangerRequest.setResource(resource);
+ expectedRangerRequest.setAction(request.getAction().name());
+ expectedRangerRequest.setAccessType(request.getAction().name());
+ expectedRangerRequest.setUser(request.getIdentity());
+
+ // no result processor should be provided used non-direct access
+ when(rangerBasePlugin.isAccessAllowed(
+ argThat(new RangerAccessRequestMatcher(expectedRangerRequest)))
+ ).thenReturn(allowedResult);
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ assertEquals(AuthorizationResult.approved().getResult(), result.getResult());
+ }
+
+ @Test
+ public void testResourceNotFound() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ final String systemResource = "/system";
+ final RequestAction action = RequestAction.WRITE;
+ final String user = "admin";
+
+ // the incoming NiFi request to test
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new MockResource(systemResource, systemResource))
+ .action(action)
+ .identity(user)
+ .resourceContext(new HashMap<>())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerAuthorizer.RANGER_NIFI_REG_RESOURCE_NAME, systemResource);
+
+ final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl();
+ expectedRangerRequest.setResource(resource);
+ expectedRangerRequest.setAction(request.getAction().name());
+ expectedRangerRequest.setAccessType(request.getAction().name());
+ expectedRangerRequest.setUser(request.getIdentity());
+
+ // no result processor should be provided used non-direct access
+ when(rangerBasePlugin.isAccessAllowed(
+ argThat(new RangerAccessRequestMatcher(expectedRangerRequest)),
+ any(RangerAccessResultProcessor.class))
+ ).thenReturn(notAllowedResult);
+
+ // return false when checking if a policy exists for the resource
+ when(rangerBasePlugin.doesPolicyExist(systemResource, action)).thenReturn(false);
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ assertEquals(AuthorizationResult.resourceNotFound().getResult(), result.getResult());
+ }
+
+ @Test
+ public void testDenied() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ final String systemResource = "/system";
+ final RequestAction action = RequestAction.WRITE;
+ final String user = "admin";
+
+ // the incoming NiFi request to test
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new MockResource(systemResource, systemResource))
+ .action(action)
+ .identity(user)
+ .resourceContext(new HashMap<>())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerAuthorizer.RANGER_NIFI_REG_RESOURCE_NAME, systemResource);
+
+ final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl();
+ expectedRangerRequest.setResource(resource);
+ expectedRangerRequest.setAction(request.getAction().name());
+ expectedRangerRequest.setAccessType(request.getAction().name());
+ expectedRangerRequest.setUser(request.getIdentity());
+
+ // no result processor should be provided used non-direct access
+ when(rangerBasePlugin.isAccessAllowed(
+ argThat(new RangerAccessRequestMatcher(expectedRangerRequest)))
+ ).thenReturn(notAllowedResult);
+
+ // return true when checking if a policy exists for the resource
+ when(rangerBasePlugin.doesPolicyExist(systemResource, action)).thenReturn(true);
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ assertEquals(AuthorizationResult.denied().getResult(), result.getResult());
+ }
+
+ @Test
+ public void testRangerAdminApproved() {
+ runRangerAdminTest(RangerAuthorizer.RESOURCES_RESOURCE, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminDenied() {
+ runRangerAdminTest("/flow", AuthorizationResult.denied().getResult());
+ }
+
+ private void runRangerAdminTest(final String resourceIdentifier, final AuthorizationResult.Result expectedResult) {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ final String rangerAdminIdentity = "ranger-admin";
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP)))
+ .thenReturn(new StandardPropertyValue(rangerAdminIdentity));
+
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ final RequestAction action = RequestAction.WRITE;
+
+ // the incoming NiFi request to test
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new MockResource(resourceIdentifier, resourceIdentifier))
+ .action(action)
+ .identity(rangerAdminIdentity)
+ .resourceContext(new HashMap<>())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerAuthorizer.RANGER_NIFI_REG_RESOURCE_NAME, resourceIdentifier);
+
+ final RangerAccessRequestImpl expectedRangerRequest = new RangerAccessRequestImpl();
+ expectedRangerRequest.setResource(resource);
+ expectedRangerRequest.setAction(request.getAction().name());
+ expectedRangerRequest.setAccessType(request.getAction().name());
+ expectedRangerRequest.setUser(request.getIdentity());
+
+ // return true when checking if a policy exists for the resource
+ when(rangerBasePlugin.doesPolicyExist(resourceIdentifier, action)).thenReturn(true);
+
+ // a non-null result processor should be used for direct access
+ when(rangerBasePlugin.isAccessAllowed(
+ argThat(new RangerAccessRequestMatcher(expectedRangerRequest)))
+ ).thenReturn(notAllowedResult);
+
+ final AuthorizationResult result = authorizer.authorize(request);
+ assertEquals(expectedResult, result.getResult());
+ }
+
+ @Test
+ @Ignore
+ public void testIntegration() {
+ final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+ final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_SECURITY_PATH_PROP)))
+ .thenReturn(new StandardPropertyValue("src/test/resources/ranger/ranger-nifi-registry-security.xml"));
+
+ when(configurationContext.getProperty(eq(RangerAuthorizer.RANGER_AUDIT_PATH_PROP)))
+ .thenReturn(new StandardPropertyValue("src/test/resources/ranger/ranger-nifi-registry-audit.xml"));
+
+ Authorizer authorizer = new RangerAuthorizer();
+ try {
+ authorizer.initialize(initializationContext);
+ authorizer.onConfigured(configurationContext);
+
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new Resource() {
+ @Override
+ public String getIdentifier() {
+ return "/policies";
+ }
+
+ @Override
+ public String getName() {
+ return "/policies";
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "policies";
+ }
+ })
+ .action(RequestAction.WRITE)
+ .identity("admin")
+ .resourceContext(new HashMap<>())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+
+ final AuthorizationResult result = authorizer.authorize(request);
+
+ assertEquals(AuthorizationResult.denied().getResult(), result.getResult());
+
+ } finally {
+ authorizer.preDestruction();
+ }
+ }
+
+ /**
+ * Extend RangerAuthorizer to inject a mock base plugin for testing.
+ */
+ private static class MockRangerAuthorizer extends RangerAuthorizer {
+
+ RangerBasePluginWithPolicies mockRangerBasePlugin;
+
+ MockRangerAuthorizer(RangerBasePluginWithPolicies mockRangerBasePlugin) {
+ this.mockRangerBasePlugin = mockRangerBasePlugin;
+ }
+
+ @Override
+ protected RangerBasePluginWithPolicies createRangerBasePlugin(String serviceType, String appId) {
+ when(mockRangerBasePlugin.getAppId()).thenReturn(appId);
+ when(mockRangerBasePlugin.getServiceType()).thenReturn(serviceType);
+ return mockRangerBasePlugin;
+ }
+ }
+
+ /**
+ * Resource implementation for testing.
+ */
+ private static class MockResource implements Resource {
+
+ private final String identifier;
+ private final String name;
+
+ MockResource(String identifier, String name) {
+ this.identifier = identifier;
+ this.name = name;
+ }
+
+ @Override
+ public String getIdentifier() {
+ return identifier;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return name;
+ }
+ }
+
+ /**
+ * Custom Mockito matcher for RangerAccessRequest objects.
+ */
+ private static class RangerAccessRequestMatcher implements ArgumentMatcher<RangerAccessRequest> {
+
+ private final RangerAccessRequest request;
+
+ RangerAccessRequestMatcher(RangerAccessRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public boolean matches(RangerAccessRequest other) {
+ final boolean clientIpsMatch = (other.getClientIPAddress() == null && request.getClientIPAddress() == null)
+ || (other.getClientIPAddress() != null && request.getClientIPAddress() != null && other.getClientIPAddress().equals(request.getClientIPAddress()));
+
+ return other.getResource().equals(request.getResource())
+ && other.getAccessType().equals(request.getAccessType())
+ && other.getAction().equals(request.getAction())
+ && other.getUser().equals(request.getUser())
+ && clientIpsMatch;
+ }
+ }
+
+ @Test
+ public void testNonConfigurableFingerPrint() {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), mock(UserGroupProvider.class), configurationContext);
+
+ Assert.assertEquals(EMPTY_FINGERPRINT, authorizer.getFingerprint());
+ }
+
+ @Test
+ public void testConfigurableEmptyFingerPrint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+ when(userGroupProvider.getFingerprint()).thenReturn("");
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ Assert.assertEquals(EMPTY_FINGERPRINT, authorizer.getFingerprint());
+ }
+
+ @Test
+ public void testConfigurableFingerPrint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+ when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ Assert.assertEquals(NON_EMPTY_FINGERPRINT, authorizer.getFingerprint());
+ }
+
+ @Test
+ public void testInheritEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.inheritFingerprint(EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+ }
+
+ @Test(expected = AuthorizationAccessException.class)
+ public void testInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.inheritFingerprint("not a valid fingerprint");
+ }
+
+ @Test
+ public void testInheritNonEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT);
+ }
+
+ @Test
+ public void testCheckInheritEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.checkInheritability(EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+ }
+
+ @Test(expected = AuthorizationAccessException.class)
+ public void testCheckInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.checkInheritability("not a valid fingerprint");
+ }
+
+ @Test
+ public void testCheckInheritNonEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT);
+ }
+
+ @Test(expected = UninheritableAuthorizationsException.class)
+ public void testCheckInheritNonConfigurableUserGroupProvider() {
+ final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ authorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
+ }
+
+}