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>"
+            + "&lt;tenants&gt;"
+            + "&lt;user identifier=\"user-id-1\" identity=\"user-1\"&gt;&lt;/user&gt;"
+            + "&lt;group identifier=\"group-id-1\" name=\"group-1\"&gt;"
+            + "&lt;groupUser identifier=\"user-id-1\"&gt;&lt;/groupUser&gt;"
+            + "&lt;/group&gt;"
+            + "&lt;/tenants&gt;"
+            + "</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);
+    }
+
+}