NIFIREG-186: Adding Ranger authorizer

- Ranger Authorizer is deployed as Registry extension.
- Added /config REST endpoint to expose Registry configuration for UI to
determine if user, group and policies can be edited.
- Added 'include-ranger' maven build profile and refactored project
structure to control ranger extension build.
- Added README.md to illustrate how to use this extension.
- Added default configuration files.
- Remove javax.ws.rs package in jersey-bundle jar to avoid rs version conflict.
- Added example conf to audit to Kerberized Solr.
- Updated hadoop version to 3.0.0
- Added ExtensionCloseable to use extension class loader when
  configuring authorizer. Without this, Hadoop Configuration class uses
  WebApp class loader that is set to current thread context class loader
  which does not have extension classes.
- Refactored anonymous inner classes at AuthorizerFactory to expose
  underlying authorizer instance, to use its extension class loader.
- Confirmed NiFi Registry can:
  - download policies from Kerbelized Ranger
  - send audit logs to Kerbelized Solr
  - send audit logs to Kerbelized HDFS
- Refactored project structures and updated L&N.

This closes #131.

Signed-off-by: Kevin Doran <kdoran@apache.org>
diff --git a/nifi-registry-assembly/pom.xml b/nifi-registry-assembly/pom.xml
index 2ea11b4..3378ace 100644
--- a/nifi-registry-assembly/pom.xml
+++ b/nifi-registry-assembly/pom.xml
@@ -379,5 +379,43 @@
                 </plugins>
             </build>
         </profile>
+        <profile>
+            <id>include-ranger</id>
+            <activation>
+                <activeByDefault>false</activeByDefault>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.nifi.registry</groupId>
+                    <artifactId>nifi-registry-ranger-assembly</artifactId>
+                    <version>0.3.0-SNAPSHOT</version>
+                    <classifier>bin</classifier>
+                    <scope>runtime</scope>
+                    <type>zip</type>
+                </dependency>
+            </dependencies>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-dependency-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>unpack-ranger-extensions</id>
+                                <goals>
+                                    <goal>unpack-dependencies</goal>
+                                </goals>
+                                <phase>generate-resources</phase>
+                                <configuration>
+                                    <outputDirectory>${project.build.directory}/ext/ranger</outputDirectory>
+                                    <includeGroupIds>org.apache.nifi.registry</includeGroupIds>
+                                    <includeArtifactIds>nifi-registry-ranger-assembly</includeArtifactIds>
+                                    <excludeTransitive>false</excludeTransitive>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
     </profiles>
 </project>
\ No newline at end of file
diff --git a/nifi-registry-assembly/src/main/assembly/dependencies.xml b/nifi-registry-assembly/src/main/assembly/dependencies.xml
index 45e5fc2..c41cc26 100644
--- a/nifi-registry-assembly/src/main/assembly/dependencies.xml
+++ b/nifi-registry-assembly/src/main/assembly/dependencies.xml
@@ -66,6 +66,7 @@
                 <exclude>nifi-registry-bootstrap</exclude>
                 <exclude>nifi-registry-utils</exclude>
                 <exclude>nifi-registry-docs</exclude>
+                <exclude>nifi-registry-ranger-assembly</exclude>
             </excludes>
         </dependencySet>
         
@@ -154,4 +155,12 @@
         </file>
     </files>
 
+    <fileSets>
+        <fileSet>
+            <!-- Extensions are extracted by maven-dependency-plugin defined in pom.xml -->
+            <directory>${project.build.directory}/ext</directory>
+            <outputDirectory>ext</outputDirectory>
+        </fileSet>
+    </fileSets>
+
 </assembly>
diff --git a/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/RegistryConfiguration.java b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/RegistryConfiguration.java
new file mode 100644
index 0000000..6c16a68
--- /dev/null
+++ b/nifi-registry-data-model/src/main/java/org/apache/nifi/registry/RegistryConfiguration.java
@@ -0,0 +1,78 @@
+/*
+ * 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;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@ApiModel(value = "registryConfiguration")
+public class RegistryConfiguration {
+
+    private Boolean supportsManagedAuthorizer;
+    private Boolean supportsConfigurableAuthorizer;
+    private Boolean supportsConfigurableUsersAndGroups;
+
+    /**
+     * @return whether this NiFi Registry supports a managed authorizer. Managed authorizers can visualize users, groups,
+     * and policies in the UI. This value is read only
+     */
+    @ApiModelProperty(
+            value = "Whether this NiFi Registry supports a managed authorizer. Managed authorizers can visualize users, groups, and policies in the UI.",
+            readOnly = true
+    )
+    public Boolean getSupportsManagedAuthorizer() {
+        return supportsManagedAuthorizer;
+    }
+
+    public void setSupportsManagedAuthorizer(Boolean supportsManagedAuthorizer) {
+        this.supportsManagedAuthorizer = supportsManagedAuthorizer;
+    }
+
+    /**
+     * @return whether this NiFi Registry supports configurable users and groups. This value is read only
+     */
+    @ApiModelProperty(
+            value = "Whether this NiFi Registry supports configurable users and groups.",
+            readOnly = true
+    )
+    public Boolean getSupportsConfigurableUsersAndGroups() {
+        return supportsConfigurableUsersAndGroups;
+    }
+
+    public void setSupportsConfigurableUsersAndGroups(Boolean supportsConfigurableUsersAndGroups) {
+        this.supportsConfigurableUsersAndGroups = supportsConfigurableUsersAndGroups;
+    }
+
+    /**
+     * @return whether this NiFi Registry supports a configurable authorizer. This value is read only
+     */
+    @ApiModelProperty(
+            value = "Whether this NiFi Registry supports a configurable authorizer.",
+            readOnly = true
+    )
+    public Boolean getSupportsConfigurableAuthorizer() {
+        return supportsConfigurableAuthorizer;
+    }
+
+    public void setSupportsConfigurableAuthorizer(Boolean supportsConfigurableAuthorizer) {
+        this.supportsConfigurableAuthorizer = supportsConfigurableAuthorizer;
+    }
+
+}
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE
new file mode 100644
index 0000000..f051fe2
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE
@@ -0,0 +1,445 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.
+
+The binary distribution of this product bundles 'Slf4j' which is available under an MIT license.
+
+    Copyright (c) 2004-2017 QOS.ch
+    All rights reserved.
+
+    Permission is hereby granted, free  of charge, to any person obtaining
+    a  copy  of this  software  and  associated  documentation files  (the
+    "Software"), to  deal in  the Software without  restriction, including
+    without limitation  the rights to  use, copy, modify,  merge, publish,
+    distribute,  sublicense, and/or sell  copies of  the Software,  and to
+    permit persons to whom the Software  is furnished to do so, subject to
+    the following conditions:
+
+    The  above  copyright  notice  and  this permission  notice  shall  be
+    included in all copies or substantial portions of the Software.
+
+    THE  SOFTWARE IS  PROVIDED  "AS  IS", WITHOUT  WARRANTY  OF ANY  KIND,
+    EXPRESS OR  IMPLIED, INCLUDING  BUT NOT LIMITED  TO THE  WARRANTIES OF
+    MERCHANTABILITY,    FITNESS    FOR    A   PARTICULAR    PURPOSE    AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+    OF CONTRACT, TORT OR OTHERWISE,  ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+The binary distribution of this product bundles 'jopt-simple' which is available under an MIT license.
+
+    Copyright (c) 2004-2016 Paul R. Holser, Jr.
+
+    Permission is hereby granted, free of charge, to any person obtaining
+    a copy of this software and associated documentation files (the
+    "Software"), to deal in the Software without restriction, including
+    without limitation the rights to use, copy, modify, merge, publish,
+    distribute, sublicense, and/or sell copies of the Software, and to
+    permit persons to whom the Software is furnished to do so, subject to
+    the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+The binary distribution of this product bundles 'ParaNamer' which is available under a BSD license.
+
+    Portions copyright (c) 2006-2018 Paul Hammant & ThoughtWorks Inc
+    Portions copyright (c) 2000-2007 INRIA, France Telecom
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+    1. Redistributions of source code must retain the above copyright
+       notice, this list of conditions and the following disclaimer.
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+    3. Neither the name of the copyright holders nor the names of its
+       contributors may be used to endorse or promote products derived from
+       this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+    THE POSSIBILITY OF SUCH DAMAGE.
+
+The binary distribution of this product bundles 'JSch' which is available under a BSD license.
+
+    Copyright (c) 2002-2015 Atsuhiko Yamanaka, JCraft,Inc.
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are met:
+
+      1. Redistributions of source code must retain the above copyright notice,
+         this list of conditions and the following disclaimer.
+
+      2. Redistributions in binary form must reproduce the above copyright
+         notice, this list of conditions and the following disclaimer in
+         the documentation and/or other materials provided with the distribution.
+
+      3. The names of the authors may not be used to endorse or promote products
+         derived from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
+    INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
+    INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
+    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
+    OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+    EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+The binary distribution of this product bundles 'JLine Bundle' which is available under a BSD 3-Clause license.
+
+    Copyright (c) 2002-2006, Marc Prud'hommeaux <mwp1@cornell.edu>
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or
+    without modification, are permitted provided that the following
+    conditions are met:
+
+    Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+    Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with
+    the distribution.
+
+    Neither the name of JLine nor the names of its contributors
+    may be used to endorse or promote products derived from this
+    software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
+    BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
+    AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+    EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+    FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+    OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+
+The binary distribution of this product bundles 'Protocol Buffers' which is available under a BSD 3-Clause license.
+
+    Copyright 2008 Google Inc.  All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+        * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+        * Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with the
+    distribution.
+        * Neither the name of Google Inc. nor the names of its
+    contributors may be used to endorse or promote products derived from
+    this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+    Code generated by the Protocol Buffer compiler is owned by the owner
+    of the input file used when generating it.  This code is not
+    standalone and requires a support library to be linked with it.  This
+    support library is itself covered by the above license.
+
+The binary distribution of this product bundles 'Scala' which is available under a BSD 3-Clause license.
+
+    Copyright (c) 2002-  EPFL
+    Copyright (c) 2011-  Lightbend, Inc.
+
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without modification,
+    are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of the EPFL nor the names of its contributors
+      may be used to endorse or promote products derived from this software
+      without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+This product bundles 'RE2/J' which is available under a Go license.
+
+    This is a work derived from Russ Cox's RE2 in Go, whose license
+    http://golang.org/LICENSE is as follows:
+
+    Copyright (c) 2009 The Go Authors. All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions are
+    met:
+
+       * Redistributions of source code must retain the above copyright
+         notice, this list of conditions and the following disclaimer.
+
+       * Redistributions in binary form must reproduce the above copyright
+         notice, this list of conditions and the following disclaimer in
+         the documentation and/or other materials provided with the
+         distribution.
+
+       * Neither the name of Google Inc. nor the names of its contributors
+         may be used to endorse or promote products derived from this
+         software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE
new file mode 100644
index 0000000..56c03f6
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE
@@ -0,0 +1,460 @@
+nifi-registry-ranger-extension
+Copyright 2018 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (http://www.apache.org/).
+
+******************
+Apache Software License v2
+******************
+
+  (ASLv2) Apache Avro
+    The following NOTICE information applies:
+      Apache Avro
+      Copyright 2009-2017 The Apache Software Foundation
+
+  (ASLv2) Apache Commons Collections
+    The following NOTICE information applies:
+      Apache Commons Collections
+      Copyright 2001-2013 The Apache Software Foundation
+
+  (ASLv2) Apache Commons Compress
+    The following NOTICE information applies:
+      Apache Commons Compress
+      Copyright 2002-2017 The Apache Software Foundation
+
+      The files in the package org.apache.commons.compress.archivers.sevenz
+      were derived from the LZMA SDK, version 9.20 (C/ and CPP/7zip/),
+      which has been placed in the public domain:
+
+      "LZMA SDK is placed in the public domain." (http://www.7-zip.org/sdk.html)
+
+  (ASLv2) Apache Commons Codec
+    The following NOTICE information applies:
+      Apache Commons Codec
+      Copyright 2002-2014 The Apache Software Foundation
+
+      src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java
+      contains test data from http://aspell.net/test/orig/batch0.tab.
+      Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org)
+
+      ===============================================================================
+
+      The content of package org.apache.commons.codec.language.bm has been translated
+      from the original php source code available at http://stevemorse.org/phoneticinfo.htm
+      with permission from the original authors.
+      Original source copyright:
+      Copyright (c) 2008 Alexander Beider & Stephen P. Morse.
+
+  (ASLv2) Apache Commons CLI
+    The following NOTICE information applies:
+      Apache Commons CLI
+      Copyright 2001-2009 The Apache Software Foundation
+
+  (ASLv2) Apache Commons Configuration
+    The following NOTICE information applies:
+      Apache Commons Configuration
+      Copyright 2001-2008 The Apache Software Foundation
+
+  (ASLv2) Apache Jakarta HttpClient
+    The following NOTICE information applies:
+      Apache Jakarta HttpClient
+      Copyright 1999-2007 The Apache Software Foundation
+
+  (ASLv2) Apache Commons IO
+    The following NOTICE information applies:
+      Apache Commons IO
+      Copyright 2002-2016 The Apache Software Foundation
+
+  (ASLv2) Apache Commons Lang
+    The following NOTICE information applies:
+      Apache Commons Lang
+      Copyright 2001-2015 The Apache Software Foundation
+
+      This product includes software from the Spring Framework,
+      under the Apache License 2.0 (see: StringUtils.containsWhitespace())
+
+  (ASLv2) Apache Commons Logging
+    The following NOTICE information applies:
+      Apache Commons Logging
+      Copyright 2003-2014 The Apache Software Foundation
+
+  (ASLv2) Apache Commons Math
+    The following NOTICE information applies:
+      Apache Commons Math
+      Copyright 2001-2012 The Apache Software Foundation
+
+      This product includes software developed by
+      The Apache Software Foundation (http://www.apache.org/).
+
+      ===============================================================================
+
+      The BracketFinder (package org.apache.commons.math3.optimization.univariate)
+      and PowellOptimizer (package org.apache.commons.math3.optimization.general)
+      classes are based on the Python code in module "optimize.py" (version 0.5)
+      developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/)
+      Copyright © 2003-2009 SciPy Developers.
+      ===============================================================================
+
+      The LinearConstraint, LinearObjectiveFunction, LinearOptimizer,
+      RelationShip, SimplexSolver and SimplexTableau classes in package
+      org.apache.commons.math3.optimization.linear include software developed by
+      Benjamin McCann (http://www.benmccann.com) and distributed with
+      the following copyright: Copyright 2009 Google Inc.
+      ===============================================================================
+
+      This product includes software developed by the
+      University of Chicago, as Operator of Argonne National
+      Laboratory.
+      The LevenbergMarquardtOptimizer class in package
+      org.apache.commons.math3.optimization.general includes software
+      translated from the lmder, lmpar and qrsolv Fortran routines
+      from the Minpack package
+      Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+      ===============================================================================
+
+      The GraggBulirschStoerIntegrator class in package
+      org.apache.commons.math3.ode.nonstiff includes software translated
+      from the odex Fortran routine developed by E. Hairer and G. Wanner.
+      Original source copyright:
+      Copyright (c) 2004, Ernst Hairer
+      ===============================================================================
+
+      The EigenDecompositionImpl class in package
+      org.apache.commons.math3.linear includes software translated
+      from some LAPACK Fortran routines.  Original source copyright:
+      Copyright (c) 1992-2008 The University of Tennessee.  All rights reserved.
+      ===============================================================================
+
+      The MersenneTwister class in package org.apache.commons.math3.random
+      includes software translated from the 2002-01-26 version of
+      the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji
+      Nishimura. Original source copyright:
+      Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+      All rights reserved
+      ===============================================================================
+
+      The LocalizedFormatsTest class in the unit tests is an adapted version of
+      the OrekitMessagesTest class from the orekit library distributed under the
+      terms of the Apache 2 licence. Original source copyright:
+      Copyright 2010 CS Systèmes d'Information
+      ===============================================================================
+
+      The HermiteInterpolator class and its corresponding test have been imported from
+      the orekit library distributed under the terms of the Apache 2 licence. Original
+      source copyright:
+      Copyright 2010-2012 CS Systèmes d'Information
+      ===============================================================================
+
+      The creation of the package "o.a.c.m.analysis.integration.gauss" was inspired
+      by an original code donated by Sébastien Brisard.
+      ===============================================================================
+
+  (ASLv2) Apache Commons Net
+    The following NOTICE information applies:
+      Apache Commons Net
+      Copyright 2001-2013 The Apache Software Foundation
+
+  (ASLv2) Apache Curator
+    The following NOTICE information applies:
+      Curator Framework
+      Copyright 2011-2014 The Apache Software Foundation
+
+      Curator Client
+      Copyright 2011-2014 The Apache Software Foundation
+
+      Curator Recipes
+      Copyright 2011-2014 The Apache Software Foundation
+
+  (ASLv2) Apache HttpComponents
+    The following NOTICE information applies:
+      Apache HttpClient
+      Copyright 1999-2015 The Apache Software Foundation
+
+      Apache HttpCore
+      Copyright 2005-2015 The Apache Software Foundation
+
+      Apache HttpMime
+      Copyright 1999-2013 The Apache Software Foundation
+
+      This project contains annotations derived from JCIP-ANNOTATIONS
+      Copyright (c) 2005 Brian Goetz and Tim Peierls. See http://www.jcip.net
+
+  (ASLv2) Apache Ranger
+    The following NOTICE information applies:
+      Apache Ranger Credential Builder
+      Copyright 2014-2016 The Apache Software Foundation
+
+      Apache Ranger Plugins Audit
+      Copyright 2014-2016 The Apache Software Foundation
+
+      Apache Ranger Plugins Common
+      Copyright 2014-2016 The Apache Software Foundation
+
+      Apache Ranger Plugins Cred
+      Copyright 2014-2016 The Apache Software Foundation
+
+  (ASLv2) Google GSON
+    The following NOTICE information applies:
+      Copyright 2008 Google Inc.
+
+  (ASLv2) Guava
+    The following NOTICE information applies:
+      Guava
+      Copyright 2015 The Guava Authors
+
+  (ASLv2) Apache Hadoop
+    The following NOTICE information applies:
+       Apache Hadoop
+       Copyright 2014 The Apache Software Foundation.
+
+  (ASLv2) HTrace Core
+    The following NOTICE information applies:
+      In addition, this product includes software dependencies. See
+      the accompanying LICENSE.txt for a listing of dependencies
+      that are NOT Apache licensed (with pointers to their licensing)
+
+      Apache HTrace includes an Apache Thrift connector to Zipkin. Zipkin
+      is a distributed tracing system that is Apache 2.0 Licensed.
+      Copyright 2012 Twitter, Inc.
+
+  (ASLv2) Jackson JSON processor
+    The following NOTICE information applies:
+      # Jackson JSON processor
+
+      Jackson is a high-performance, Free/Open Source JSON processing library.
+      It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has
+      been in development since 2007.
+      It is currently developed by a community of developers, as well as supported
+      commercially by FasterXML.com.
+
+      ## Licensing
+
+      Jackson core and extension components may licensed under different licenses.
+      To find the details that apply to this artifact see the accompanying LICENSE file.
+      For more information, including possible other licensing options, contact
+      FasterXML.com (http://fasterxml.com).
+
+      ## Credits
+
+      A list of contributors may be found from CREDITS file, which is included
+      in some artifacts (usually source distributions); but is always available
+      from the source code management (SCM) system project uses.
+
+  (ASLv2) Jettison
+    The following NOTICE information applies:
+      Copyright 2006 Envoi Solutions LLC
+
+  (ASLv2) Jetty
+    The following NOTICE information applies:
+       Jetty Web Container
+       Copyright 1995-2017 Mort Bay Consulting Pty Ltd.
+
+  (ASLv2) Apache Kafka
+    The following NOTICE information applies:
+       Apache Kafka
+       Copyright 2012 The Apache Software Foundation.
+
+       scala-library is BSD-like licensed software (http://www.scala-lang.org/license.html)
+
+  (ASLv2) Apache log4j
+    The following NOTICE information applies:
+      Apache log4j
+      Copyright 2007 The Apache Software Foundation
+
+  (ASLv2) Apache Solr
+    The following NOTICE information applies:
+      Apache Solrj
+      Copyright 2006-2014 The Apache Software Foundation
+
+  (ASLv2) Apache ZooKeeper
+    The following NOTICE information applies:
+      Apache ZooKeeper
+      Copyright 2009-2012 The Apache Software Foundation
+
+  (ASLv2) The Netty Project
+    The following NOTICE information applies:
+      The Netty Project
+      Copyright 2011 The Netty Project
+
+  (ASLv2) Snappy Java
+    The following NOTICE information applies:
+      This product includes software developed by Google
+       Snappy: http://code.google.com/p/snappy/ (New BSD License)
+
+      This product includes software developed by Apache
+       PureJavaCrc32C from apache-hadoop-common http://hadoop.apache.org/
+       (Apache 2.0 license)
+
+      This library containd statically linked libstdc++. This inclusion is allowed by
+      "GCC RUntime Library Exception"
+      http://gcc.gnu.org/onlinedocs/libstdc++/manual/license.html
+
+  (ASLv2) Woodstox Core ASL
+    The following NOTICE information applies:
+      This product currently only contains code developed by authors
+      of specific components, as identified by the source code files.
+
+      Since product implements StAX API, it has dependencies to StAX API
+      classes.
+
+  (ASLv2) Yammer Metrics
+    The following NOTICE information applies:
+      Metrics
+      Copyright 2010-2012 Coda Hale and Yammer, Inc.
+
+      This product includes software developed by Coda Hale and Yammer, Inc.
+
+      This product includes code derived from the JSR-166 project (ThreadLocalRandom), which was released
+      with the following comments:
+
+          Written by Doug Lea with assistance from members of JCP JSR-166
+          Expert Group and released to the public domain, as explained at
+          http://creativecommons.org/publicdomain/zero/1.0/
+
+  (ASLv2) ZkClient
+    The following NOTICE information applies:
+      ZkClient
+      Copyright 2009 Stefan Groschupf
+
+  (ASLv2) Rome
+    The following NOTICE information applies:
+      Rome Copyright Notices
+      Copyright 2004 Sun Microsystems, Inc.
+      Copyright 2011 The ROME Team
+
+  (ASLv2) Swagger Core library
+    The following NOTICE information applies:
+      Copyright 2016 SmartBear Software
+
+  (ASLv2) json-smart
+    The following NOTICE information applies:
+      Copyright 2011 JSON-SMART authors
+
+  (ASLv2) Apache Commons BeanUtils
+    The following NOTICE information applies:
+      Apache Commons BeanUtils
+      Copyright 2000-2008 The Apache Software Foundation
+
+  (ASLv2) Apache Kerby
+    The following NOTICE information applies:
+      Apache Kerby
+      Copyright 2003-2018 The Apache Software Foundation
+
+  (ASLv2) Nimbus JOSE + JWT
+    The following NOTICE information applies:
+      Nimbus JOSE + JWT
+      Copyright 2012 - 2018, Connect2id Ltd.
+
+  (ASLv2) OkHttp
+    The following NOTICE information applies:
+      OkHttp
+      Copyright (C) 2014 Square, Inc.
+
+  (ASLv2) Okio
+    The following NOTICE information applies:
+      Okio
+      Copyright (C) 2014 Square, Inc.
+
+  (ASLv2) JCIP Annotations Under Apache License
+    The following NOTICE information applies:
+      JCIP Annotations Under Apache License
+      Copyright 2013 Stephen Connolly.
+
+************************
+Common Development and Distribution License 1.0
+************************
+
+The following binary components are provided under the Common Development and Distribution License 1.0.  See project link for details.
+
+    (CDDL 1.0) JavaBeans Activation Framework (JAF) (javax.activation:activation:jar:1.1 - http://java.sun.com/products/javabeans/jaf/index.jsp)
+    (CDDL 1.0) (GPL3) Streaming API For XML (javax.xml.stream:stax-api:jar:1.0-2 - no url provided)
+
+************************
+Common Development and Distribution License 1.1
+************************
+
+The following binary components are provided under the Common Development and Distribution License 1.1. See project link for details.
+
+    (CDDL 1.1) (GPL2 w/ CPE) jersey-bundle (com.sun.jersey:jersey-bundle:jar:1.19.3 - https://jersey.java.net/jersey-bundle/)
+    (CDDL 1.1) (GPL2 w/ CPE) jersey-server (com.sun.jersey:jersey-server:jar:1.19 - https://jersey.java.net/jersey-server/)
+    (CDDL 1.1) (GPL2 w/ CPE) JavaServer Pages(TM) API (javax.servlet.jsp:javax.servlet.jsp-api:jar:2.1 - http://jsp.java.net)
+    (CDDL 1.1) (GPL2 w/ CPE) Java Servlet API  (javax.servlet:javax.servlet-api:jar:2.5 - http://servlet-spec.java.net)
+    (CDDL 1.1) (GPL2 w/ CPE) javax.ws.rs-api (javax.ws.rs:javax.ws.rs-api:jar:2.1 - http://jax-rs-spec.java.net)
+    (CDDL 1.1) (GPL2 w/ CPE) JavaMail API (compat) (javax.mail:mail:jar:1.4.7 - https://java.net/projects/javamail/pages/Home)
+    (CDDL 1.1) (GPL2 w/ CPE) Java Architecture For XML Binding (javax.xml.bind:jaxb-api:jar:2.2.2 - https://jaxb.dev.java.net/)
+    (CDDL 1.1) (GPL2 w/ CPE) Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:jar:2.2.3-1 - http://jaxb.java.net/)
+
+************************
+Eclipse Public License 1.0
+************************
+
+The following binary components are provided under the Eclipse Public License 1.0.  See project link for details.
+
+  (EPL 1.0) Eclipse Link (org.eclipse.persistence:eclipselink:2.5.2 - http://www.eclipse.org/eclipselink/)
+  (EPL 1.0) Common Service Data Objects (org.eclipse.persistence:commonj.sdo:2.1.1 - http://www.eclipse.org/eclipselink/)
+  (EPL 1.0) Java Persistence API (org.eclipse.persistence:javax.persistence:2.1.0 - http://www.eclipse.org/eclipselink/)
+
+************************
+The MIT License
+************************
+
+The following binary components are provided under the MIT License.  See project link for details.
+
+  (MIT License) Simple Logging Facade for Java (SLF4J)
+    The following NOTICE information applies:
+      Copyright (c) 2004-2017 QOS.ch
+      All rights reserved.
+      https://www.slf4j.org/
+
+  (MIT License) JOpt Simple
+      Copyright (c) 2004-2016 Paul R. Holser, Jr.
+      http://jopt-simple.github.io/jopt-simple/
+
+************************
+BSD License
+************************
+
+The following binary components are provided under the BSD License.  See project link for details.
+
+  (BSD) Paranamer
+    The following NOTICE information applies:
+      Portions copyright (c) 2006-2018 Paul Hammant & ThoughtWorks Inc
+      Portions copyright (c) 2000-2007 INRIA, France Telecom
+      All rights reserved.
+      https://github.com/paul-hammant/paranamer
+
+  (BSD) JSch
+    The following NOTICE information applies:
+      Copyright (c) 2002-2015 Atsuhiko Yamanaka, JCraft,Inc.
+      All rights reserved.
+      http://www.jcraft.com/jsch/
+
+  (BSD 3-Clause) JLine Bundle
+    The following NOTICE information applies:
+      Copyright (c) 2002-2007, Marc Prud'hommeaux. All rights reserved.
+      https://github.com/jline/jline1
+
+  (BSD 3-Clause) Protocol Buffers
+    The following NOTICE information applies:
+      Copyright 2008 Google Inc.  All rights reserved.
+      https://github.com/google/protobuf/tree/master/java
+
+  (BSD 3-Clause) Scala
+    The following NOTICE information applies:
+      Copyright (c) 2002-  EPFL
+      Copyright (c) 2011-  Lightbend, Inc.
+
+      All rights reserved.
+      https://www.scala-lang.org/
+
+************************
+Go License
+************************
+
+The following binary components are provided under the Go License.  See project link for details.
+
+  (Go) RE2/J
+    The following NOTICE information applies:
+      Copyright (c) 2009 The Go Authors. All rights reserved.
+      https://github.com/google/re2j
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md
new file mode 100644
index 0000000..ad6c4aa
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md
@@ -0,0 +1,131 @@
+<!--
+  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.
+-->
+# NiFi Registry Ranger extension
+
+This extension provides `org.apache.nifi.registry.ranger.RangerAuthorizer` class for NiFi Registry to authorize user requests by access policies defined at [Apache Ranger](https://ranger.apache.org/).
+
+## Prerequisites
+
+* Apache Ranger 1.2.0 or later is needed.
+
+## How to install
+
+### Enable Ranger extension at NiFi Registry build
+
+In order to enable Ranger extension when you build NiFi Registry, specify `include-ranger` profile with a maven install command:
+
+```
+cd nifi-registry
+mvn clean install -Pinclude-ranger
+```
+
+Then the extension will be installed at `${NIFI_REG_HOME}/ext/ranger` directory.
+
+### Add Ranger extension to existing NiFi Registry
+
+Alternatively, you can add Ranger extension to an existing NiFi Registry.
+To do so, build the extension with the following command:
+
+```
+cd nifi-registry
+mvn clean install -f nifi-registry-extensions/nifi-registry-ranger
+```
+
+The extension zip will be created as `nifi-registry-extensions/nifi-registry-ranger-extension/target/nifi-registry-ranger-extension-xxx-bin.zip`.
+
+Unzip the file into arbitrary directory so that NiFi Registry can use, such as `${NIFI_REG_HOME}/ext/ranger`.
+For example:
+
+```
+mkdir -p ${NIFI_REG_HOME}/ext/ranger
+unzip -d ${NIFI_REG_HOME}/ext/ranger nifi-registry-extensions/nifi-registry-ranger-extension/target/nifi-registry-ranger-extension-xxx-bin.zip
+```
+
+## NiFi Registry Configuration
+
+In order to use this extension, following NiFi Registry files need to be configured.
+
+### nifi-registry.properties
+
+```
+# Specify Ranger extension dir
+nifi.registry.extension.dir.ranger=./ext/ranger/lib
+# Specify Ranger authorizer identifier, which is defined at authorizers.xml
+nifi.registry.security.authorizer=ranger-authorizer
+```
+
+### authorizers.xml
+
+Add following `authorizer` element:
+```
+    <authorizer>
+        <identifier>ranger-authorizer</identifier>
+        <class>org.apache.nifi.registry.ranger.RangerAuthorizer</class>
+        <property name="Ranger Service Type">nifi-registry</property>
+
+        <property name="User Group Provider">file-user-group-provider</property>
+
+        <!-- Specify Ranger service name to use -->
+        <property name="Ranger Application Id">nifi-registry-service-name</property>
+
+        <!--
+            Specify configuration file paths for Ranger plugin.
+            See the XML files bundled with this extension for further details.
+         -->
+        <property name="Ranger Security Config Path">./ext/ranger/conf/ranger-nifi-registry-security.xml</property>
+        <property name="Ranger Audit Config Path">./ext/ranger/conf/ranger-nifi-registry-audit.xml</property>
+
+        <!--
+            Specify user identity that is used by Ranger to access NiFi Registry.
+            This property is used by NiFi Registry for Ranger to get available NiFi Registry policy resource identifiers.
+            The configured user can access NiFi Registry /policies/resources REST endpoint regardless of configured access policies.
+            Ranger uses available policies for user input suggestion at Ranger policy editor UI.
+        -->
+        <property name="Ranger Admin Identity">ranger@NIFI</property>
+
+        <!--
+            Specify if target Ranger is Kerberized.
+            If set to true, NiFi Registry will use the principal and keytab defined at nifi-registry.properties:
+            - nifi.registry.kerberos.service.principal
+            - nifi.registry.kerberos.service.keytab.location
+
+            The specified credential is used to access Ranger API, and to write audit logs into HDFS (if enabled).
+
+            At Ranger side, the configured user needs to be added to 'policy.download.auth.users' property, see Ranger configuration section below.
+
+            Also, ranger-nifi-registry-security.xml needs additional "xasecure.add-hadoop-authorization = true" configuration.
+        -->
+        <property name="Ranger Kerberos Enabled">false</property>
+
+    </authorizer>
+```
+
+## Ranger Configuration
+
+At Ranger side, add a NiFi Registry service. NiFi Registry service has following configuration properties:
+
+- NiFi Registry URL: Specify corresponding NiFi Registry URL that will be managed by this Ranger service. E.g. `https://nifi-registry.example.com:18443/nifi-registry-api/policies/resources`
+- Authentication Type: Should be `SSL`. Ranger authenticates itself to NiFi Registry by X.509 client certificate in the configured Keystore.
+- Keystore: Specify a Keystore filepath to use for X.509 client certificate.
+- Keystore Type: Specify the type of Keystore. E.g. `JKS`
+- Keystore Password: Specify the password of Keystore.
+- Truststore: Specify a Truststore filepath to verify NiFi Registry server certificate.
+- Truststore Type: Specify the type of Truststore. E.g. `JKS`
+- Truststore Password: Specify the password of Truststore.
+- Add New Configurations:
+  - policy.download.auth.users: Required if Ranger is Kerberized.
+    Specify the NiFi Registry user to download policies,
+    which is configured by 'nifi.registry.kerberos.service.principal' at nifi-registry.properties,
+    when NiFi Registry Ranger authorizer is configured as 'Ranger Kerberos Enabled' to true.
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml
new file mode 100644
index 0000000..e34ef88
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml
@@ -0,0 +1,174 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<!--
+  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.
+-->
+<configuration>
+	<property>
+		<name>xasecure.audit.is.enabled</name>
+		<value>true</value>
+	</property>
+
+	<!-- DB audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.db</name>
+		<value>false</value>
+	</property>	
+	
+	<property>
+		<name>xasecure.audit.destination.db.jdbc.driver</name>
+		<value>com.mysql.jdbc.Driver</value>
+	</property>	
+	
+	<property>
+		<name>xasecure.audit.destination.db.jdbc.url</name>
+		<value>jdbc:mysql://localhost/ranger_audit</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.password</name>
+		<value>rangerlogger</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.user</name>
+		<value>rangerlogger</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.batch.filespool.dir</name>
+		<value>/tmp/audit/db/spool</value>
+	</property>
+
+
+	<!-- HDFS audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.hdfs</name>
+		<value>false</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.dir</name>
+		<value>hdfs://localhost:8020/ranger/audit</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.batch.filespool.dir</name>
+		<value>/tmp/audit/hdfs/spool</value>
+	</property>
+
+
+	<!--
+		NOTE: These HDFS related configurations can be specified from here, or putting core-site.xml and hdfs-site.xml under classpath.
+	<property>
+		<name>xasecure.audit.destination.hdfs.config.fs.hdfs.impl</name>
+		<value>org.apache.hadoop.hdfs.DistributedFileSystem</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.config.hadoop.security.authentication</name>
+		<value>kerberos</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.config.dfs.namenode.kerberos.principal</name>
+		<value>nn/_HOST@EXAMPLE.COM</value>
+	</property>
+    -->
+
+
+	<!-- Log4j audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.log4j</name>
+		<value>false</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.log4j.logger</name>
+		<value>ranger_audit_logger</value>
+	</property>
+
+	<!-- Solr audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.solr</name>
+		<value>true</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.solr.batch.filespool.dir</name>
+		<value>/tmp/audit/solr/spool</value>
+	</property>
+
+	<!--
+	  IMPORTANT: Solr destination can be specified by either HTTP URL or Zookeeper address.
+	  However, when the target Solr is Kerberized, use Zookeeper address.
+	  Because LBHttpSolrClient can not use following In-memory JAAS config as it overwrites JAAS config internally.
+	-->
+	<property>
+		<name>xasecure.audit.destination.solr.urls</name>
+		<!-- by HTTP URL
+		<value>http://localhost:6083/solr/ranger_audits</value>
+		-->
+		<!-- by Zookeeper address, recommended -->
+		<value>localhost:2181/solr</value>
+	</property>
+
+	<!--
+	  If Solr is Kerberized, following in-memory JAAS properties are also needed to authenticate NiFi Registry as a Solr client.
+
+	  Also, solr-security.json should be configured to allow this NiFi Registry user (specified by the principal)
+	  to write audits to 'ranger_audits' Solr collection. See Solr documentation for how to configure solr-security.json.
+	  https://lucene.apache.org/solr/guide/6_6/authentication-and-authorization-plugins.html
+
+      In case Ranger uses infra-solr resides in the same cluster managed by Ambari, you can configure required solr-security.json from:
+      Ambari -> Infra Solr -> Config -> Advanced -> Advanced infra-solr-security-json -> Ranger audit service users
+      E.g. {default_ranger_audit_users},nifi-registry
+	-->
+	<!-- Also, solr-security.json Ranger audit service users -->
+	<property>
+		<name>xasecure.audit.destination.solr.force.use.inmemory.jaas.config</name>
+		<value>true</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.option.useKeyTab</name>
+		<value>true</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.option.storeKey</name>
+		<value>false</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.option.serviceName</name>
+		<value>solr</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.option.principal</name>
+		<value>nifi-registry@EXAMPLE.COM</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.option.keyTab</name>
+		<value>/etc/security/keytabs/nifi-registry.keytab</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.loginModuleName</name>
+		<value>com.sun.security.auth.module.Krb5LoginModule</value>
+	</property>
+	<property>
+		<name>xasecure.audit.jaas.Client.loginModuleControlFlag</name>
+		<value>required</value>
+	</property>
+
+</configuration>
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml
new file mode 100644
index 0000000..f271d6d
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<!--
+  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.
+-->
+<configuration>
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.url</name>
+		<value>http://localhost:6080</value>
+		<description>
+			URL to Ranger Admin
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.service.name</name>
+		<value>nifi-registry-service-name</value>
+		<description>
+			Name of the Ranger service containing policies for this NiFi Registry instance
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.source.impl</name>
+		<value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
+		<description>
+			Class to retrieve policies from the source
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.ssl.config.file</name>
+		<value>ranger-policymgr-ssl.xml</value>
+		<description>
+			Path to the file containing SSL details to contact Ranger Admin
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.pollIntervalMs</name>
+		<value>30000</value>
+		<description>
+			How often to poll for changes in policies?
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.cache.dir</name>
+		<value>/tmp</value>
+		<description>
+			Directory where Ranger policies are cached after successful retrieval from the source
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.client.connection.timeoutMs</name>
+		<value>120000</value>
+		<description>
+			RangerRestClient Connection Timeout in Milli Seconds
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.client.read.timeoutMs</name>
+		<value>30000</value>
+		<description>
+			RangerRestClient read Timeout in Milli Seconds
+		</description>
+	</property>
+
+	<property>
+		<name>xasecure.add-hadoop-authorization</name>
+		<value>true</value>
+		<description>
+			Enable SPNEGO authentication using principal and keytab to download policies from Ranger
+		</description>
+	</property>
+
+</configuration>
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml
new file mode 100644
index 0000000..003840d
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml
@@ -0,0 +1,63 @@
+<?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-assembly</artifactId>
+    <packaging>pom</packaging>
+    <description>Apache Ranger extension for Apache NiFi Registry</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi.registry</groupId>
+            <artifactId>nifi-registry-ranger-plugin</artifactId>
+            <version>0.3.0-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <configuration>
+                    <attach>true</attach>
+                </configuration>
+                <executions>
+                    <execution>
+                        <id>assembly-ranger-extension</id>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <phase>package</phase>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>src/main/assembly/extension.xml</descriptor>
+                            </descriptors>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml
new file mode 100644
index 0000000..c6413f3
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<assembly>
+    <id>bin</id>
+    <formats>
+        <format>zip</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <dependencySets>
+        <dependencySet>
+            <scope>runtime</scope>
+            <useProjectArtifact>true</useProjectArtifact>
+            <outputDirectory>lib</outputDirectory>
+            <directoryMode>0770</directoryMode>
+            <fileMode>0660</fileMode>
+        </dependencySet>
+    </dependencySets>
+
+    <files>
+        <file>
+            <source>./README.md</source>
+            <outputDirectory>./</outputDirectory>
+            <destName>README.md</destName>
+            <fileMode>0644</fileMode>
+            <filtered>true</filtered>
+        </file>
+        <file>
+            <source>./LICENSE</source>
+            <outputDirectory>./</outputDirectory>
+            <destName>LICENSE</destName>
+            <fileMode>0644</fileMode>
+            <filtered>true</filtered>
+        </file>
+        <file>
+            <source>./NOTICE</source>
+            <outputDirectory>./</outputDirectory>
+            <destName>NOTICE</destName>
+            <fileMode>0644</fileMode>
+            <filtered>true</filtered>
+        </file>
+    </files>
+
+    <fileSets>
+        <fileSet>
+            <directory>conf</directory>
+            <outputDirectory>conf</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml
new file mode 100644
index 0000000..f81bbf1
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml
@@ -0,0 +1,74 @@
+<?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>
+
+    <description>This module removes javax.ws.rs package from jersey-bundle-1.19.3.jar which is used by ranger-plugins-common.jar in order to address javax.ws.rs version mismatch between jersey-bundle-1.19.3.jar and NiFi Registry. NiFi Registry uses javax.ws.rs version 2.1. Without doing this, NiFi Registry encounters java.lang.LinkageError: ClassCastException: attempting to castjar:file:nifi-registry-xxx/work/jetty/nifi-registry-web-api-xxx.war/webapp/WEB-INF/lib/javax.ws.rs-api-2.1.jar!/javax/ws/rs/ext/RuntimeDelegate.classtojar:file:/home/koji/nifi-registry-xxx/./ext/ranger/lib/jersey-bundle-1.19.3.jar!/javax/ws/rs/ext/RuntimeDelegate.class</description>
+
+    <artifactId>nifi-registry-ranger-jersey-bundle</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.jersey</groupId>
+            <artifactId>jersey-bundle</artifactId>
+            <version>1.19.3</version>
+
+            <exclusions>
+                <exclusion>
+                    <groupId>javax.ws.rs</groupId>
+                    <artifactId>jsr311-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-shade-plugin</artifactId>
+                <version>2.4.3</version>
+                <executions>
+                    <execution>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>shade</goal>
+                        </goals>
+                        <configuration>
+                            <createDependencyReducedPom>false</createDependencyReducedPom>
+                            <filters>
+                                <filter>
+                                    <artifact>com.sun.jersey:jersey-bundle</artifact>
+                                    <excludes>
+                                        <exclude>javax/ws/rs/**</exclude>
+                                    </excludes>
+                                </filter>
+                            </filters>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
\ No newline at end of file
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
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
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;
+        }
+    }
+
+}
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
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);
+    }
+
+}
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerBasePluginWithPolicies.java b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerBasePluginWithPolicies.java
new file mode 100644
index 0000000..78c346a
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerBasePluginWithPolicies.java
@@ -0,0 +1,544 @@
+/*
+ * 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.nifi.registry.security.authorization.AccessPolicy;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+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.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProvider;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class TestRangerBasePluginWithPolicies {
+
+    @Test
+    public void testPoliciesWithoutUserGroupProvider() {
+        final String user1 = "user-1";
+        final String group1 = "group-1";
+
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ")).collect(Collectors.toList()));
+        policy1Item.setUsers(Stream.of(user1).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final String resourceIdentifier2 = "/resource-2";
+        RangerPolicy.RangerPolicyResource resource2 = new RangerPolicy.RangerPolicyResource(resourceIdentifier2);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy2Resources = new HashMap<>();
+        policy2Resources.put(resourceIdentifier2, resource2);
+
+        final RangerPolicy.RangerPolicyItem policy2Item = new RangerPolicy.RangerPolicyItem();
+        policy2Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ"), new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+        policy2Item.setGroups(Stream.of(group1).collect(Collectors.toList()));
+
+        final RangerPolicy policy2 = new RangerPolicy();
+        policy2.setResources(policy2Resources);
+        policy2.setPolicyItems(Stream.of(policy2Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+        policies.add(policy2);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the two ranger policies converted into 3 nifi-registry access policies
+        final Set<AccessPolicy> accessPolicies = pluginWithPolicies.getAccessPolicies();
+        assertEquals(3, accessPolicies.size());
+
+        // resource 1 -> read but no write
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.READ));
+
+        // read
+        final AccessPolicy readResource1 = pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.READ);
+        assertNotNull(readResource1);
+        assertTrue(accessPolicies.contains(readResource1));
+        assertTrue(readResource1.equals(pluginWithPolicies.getAccessPolicy(readResource1.getIdentifier())));
+        assertEquals(1, readResource1.getUsers().size());
+        assertTrue(readResource1.getUsers().contains(new User.Builder().identifierGenerateFromSeed(user1).identity(user1).build().getIdentifier()));
+        assertTrue(readResource1.getGroups().isEmpty());
+
+        // but no write
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+
+        // resource 2 -> read and write
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier2, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier2, RequestAction.READ));
+
+        // read
+        final AccessPolicy readResource2 = pluginWithPolicies.getAccessPolicy(resourceIdentifier2, RequestAction.READ);
+        assertNotNull(readResource2);
+        assertTrue(accessPolicies.contains(readResource2));
+        assertTrue(readResource2.equals(pluginWithPolicies.getAccessPolicy(readResource2.getIdentifier())));
+        assertTrue(readResource2.getUsers().isEmpty());
+        assertEquals(1, readResource2.getGroups().size());
+        assertTrue(readResource2.getGroups().contains(new Group.Builder().identifierGenerateFromSeed(group1).name(group1).build().getIdentifier()));
+
+        // and write
+        final AccessPolicy writeResource2 = pluginWithPolicies.getAccessPolicy(resourceIdentifier2, RequestAction.READ);
+        assertNotNull(writeResource2);
+        assertTrue(accessPolicies.contains(writeResource2));
+        assertTrue(writeResource2.equals(pluginWithPolicies.getAccessPolicy(writeResource2.getIdentifier())));
+        assertTrue(writeResource2.getUsers().isEmpty());
+        assertEquals(1, writeResource2.getGroups().size());
+        assertTrue(writeResource2.getGroups().contains(new Group.Builder().identifierGenerateFromSeed(group1).name(group1).build().getIdentifier()));
+
+        // resource 3 -> no read or write
+        assertFalse(pluginWithPolicies.doesPolicyExist("resource-3", RequestAction.WRITE));
+        assertFalse(pluginWithPolicies.doesPolicyExist("resource-3", RequestAction.READ));
+
+        // no read or write
+        assertNull(pluginWithPolicies.getAccessPolicy("resource-3", RequestAction.WRITE));
+        assertNull(pluginWithPolicies.getAccessPolicy("resource-3", RequestAction.READ));
+    }
+
+    @Test
+    public void testNoPolicies() {
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+
+        assertFalse(pluginWithPolicies.doesPolicyExist("non-existent-resource", RequestAction.READ));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy("non-existent-identifier"));
+        assertNull(pluginWithPolicies.getAccessPolicy("non-existent-resource", RequestAction.READ));
+    }
+
+    @Test
+    public void testDisabledPolicy() {
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ")).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setIsEnabled(false);
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the policy was skipped
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.READ));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.READ));
+    }
+
+    @Test
+    public void testMissingResourceValue() {
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource();
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the policy was skipped
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testWildcardResourceValue() {
+        final String resourceIdentifier1 = "*";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the policy was skipped
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testExcludesPolicy() {
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+        resource1.setIsExcludes(true);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the policy was skipped
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testRecursivePolicy() {
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+        resource1.setIsRecursive(true);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the policy was skipped
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.getAccessPolicies().isEmpty());
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testDelegateAdmin() {
+        final String user1 = "user-1";
+
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ"), new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+        policy1Item.setUsers(Stream.of(user1).collect(Collectors.toList()));
+        policy1Item.setDelegateAdmin(true);
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry");
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        assertEquals(4, pluginWithPolicies.getAccessPolicies().size());
+        assertNotNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.READ));
+        assertNotNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+        assertNotNull(pluginWithPolicies.getAccessPolicy("/policies" + resourceIdentifier1, RequestAction.READ));
+        assertNotNull(pluginWithPolicies.getAccessPolicy("/policies" + resourceIdentifier1, RequestAction.WRITE));
+    }
+
+    @Test
+    public void testPoliciesWithUserGroupProvider() {
+        final String user1 = "user-1"; // unknown according to user group provider
+        final String user2 = "user-2"; // known according to user group provider
+        final String group1 = "group-1"; // unknown according to user group provider
+        final String group2 = "group-2"; // known according to user group provider
+
+        final UserGroupProvider userGroupProvider = new UserGroupProvider() {
+            @Override
+            public Set<User> getUsers() throws AuthorizationAccessException {
+                return Stream.of(new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build()).collect(Collectors.toSet());
+            }
+
+            @Override
+            public User getUser(String identifier) throws AuthorizationAccessException {
+                final User u2 = new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build();
+                if (u2.getIdentifier().equals(identifier)) {
+                    return u2;
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                if (user2.equals(identity)) {
+                    return new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build();
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            public Set<Group> getGroups() throws AuthorizationAccessException {
+                return Stream.of(new Group.Builder().identifierGenerateFromSeed(group2).name(group2).build()).collect(Collectors.toSet());
+            }
+
+            @Override
+            public Group getGroup(String identifier) throws AuthorizationAccessException {
+                final Group g2 = new Group.Builder().identifierGenerateFromSeed(group2).name(group2).build();
+                if (g2.getIdentifier().equals(identifier)) {
+                    return g2;
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                if (user2.equals(identity)) {
+                    return new UserAndGroups() {
+                        @Override
+                        public User getUser() {
+                            return new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build();
+                        }
+
+                        @Override
+                        public Set<Group> getGroups() {
+                            return Collections.EMPTY_SET;
+                        }
+                    };
+                } else {
+                    return null;
+                }
+            }
+
+            @Override
+            public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+            }
+
+            @Override
+            public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+            }
+
+            @Override
+            public void preDestruction() throws SecurityProviderCreationException {
+            }
+        };
+
+        final String resourceIdentifier1 = "/resource-1";
+        RangerPolicy.RangerPolicyResource resource1 = new RangerPolicy.RangerPolicyResource(resourceIdentifier1);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy1Resources = new HashMap<>();
+        policy1Resources.put(resourceIdentifier1, resource1);
+
+        final RangerPolicy.RangerPolicyItem policy1Item = new RangerPolicy.RangerPolicyItem();
+        policy1Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ")).collect(Collectors.toList()));
+        policy1Item.setUsers(Stream.of(user1).collect(Collectors.toList()));
+        policy1Item.setGroups(Stream.of(group2).collect(Collectors.toList()));
+
+        final RangerPolicy policy1 = new RangerPolicy();
+        policy1.setResources(policy1Resources);
+        policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+        final String resourceIdentifier2 = "/resource-2";
+        RangerPolicy.RangerPolicyResource resource2 = new RangerPolicy.RangerPolicyResource(resourceIdentifier2);
+
+        final Map<String, RangerPolicy.RangerPolicyResource> policy2Resources = new HashMap<>();
+        policy2Resources.put(resourceIdentifier2, resource2);
+
+        final RangerPolicy.RangerPolicyItem policy2Item = new RangerPolicy.RangerPolicyItem();
+        policy2Item.setAccesses(Stream.of(new RangerPolicy.RangerPolicyItemAccess("READ"), new RangerPolicy.RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+        policy2Item.setUsers(Stream.of(user2).collect(Collectors.toList()));
+        policy2Item.setGroups(Stream.of(group1).collect(Collectors.toList()));
+
+        final RangerPolicy policy2 = new RangerPolicy();
+        policy2.setResources(policy2Resources);
+        policy2.setPolicyItems(Stream.of(policy2Item).collect(Collectors.toList()));
+
+        final List<RangerPolicy> policies = new ArrayList<>();
+        policies.add(policy1);
+        policies.add(policy2);
+
+        final RangerServiceDef serviceDef = new RangerServiceDef();
+        serviceDef.setName("nifi-registry");
+
+        final ServicePolicies servicePolicies = new ServicePolicies();
+        servicePolicies.setPolicies(policies);
+        servicePolicies.setServiceDef(serviceDef);
+
+        // set all the policies in the plugin
+        final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi-registry", "nifi-registry", userGroupProvider);
+        pluginWithPolicies.setPolicies(servicePolicies);
+
+        // ensure the two ranger policies converted into 3 nifi-registry access policies
+        final Set<AccessPolicy> accessPolicies = pluginWithPolicies.getAccessPolicies();
+        assertEquals(3, accessPolicies.size());
+
+        // resource 1 -> read but no write
+        assertFalse(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier1, RequestAction.READ));
+
+        // read
+        final AccessPolicy readResource1 = pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.READ);
+        assertNotNull(readResource1);
+        assertTrue(accessPolicies.contains(readResource1));
+        assertTrue(readResource1.equals(pluginWithPolicies.getAccessPolicy(readResource1.getIdentifier())));
+        assertTrue(readResource1.getUsers().isEmpty());
+        assertEquals(1, readResource1.getGroups().size());
+        assertTrue(readResource1.getGroups().contains(new Group.Builder().identifierGenerateFromSeed(group2).name(group2).build().getIdentifier()));
+
+        // but no write
+        assertNull(pluginWithPolicies.getAccessPolicy(resourceIdentifier1, RequestAction.WRITE));
+
+        // resource 2 -> read and write
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier2, RequestAction.WRITE));
+        assertTrue(pluginWithPolicies.doesPolicyExist(resourceIdentifier2, RequestAction.READ));
+
+        // read
+        final AccessPolicy readResource2 = pluginWithPolicies.getAccessPolicy(resourceIdentifier2, RequestAction.READ);
+        assertNotNull(readResource2);
+        assertTrue(accessPolicies.contains(readResource2));
+        assertTrue(readResource2.equals(pluginWithPolicies.getAccessPolicy(readResource2.getIdentifier())));
+        assertEquals(1, readResource2.getUsers().size());
+        assertTrue(readResource2.getUsers().contains(new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build().getIdentifier()));
+        assertTrue(readResource2.getGroups().isEmpty());
+
+        // and write
+        final AccessPolicy writeResource2 = pluginWithPolicies.getAccessPolicy(resourceIdentifier2, RequestAction.READ);
+        assertNotNull(writeResource2);
+        assertTrue(accessPolicies.contains(writeResource2));
+        assertTrue(writeResource2.equals(pluginWithPolicies.getAccessPolicy(writeResource2.getIdentifier())));
+        assertEquals(1, writeResource2.getUsers().size());
+        assertTrue(writeResource2.getUsers().contains(new User.Builder().identifierGenerateFromSeed(user2).identity(user2).build().getIdentifier()));
+        assertTrue(writeResource2.getGroups().isEmpty());
+    }
+}
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/krb5.conf b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/krb5.conf
new file mode 100644
index 0000000..0e3f142
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/krb5.conf
@@ -0,0 +1,25 @@
+# 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.
+
+[libdefaults]
+         default_realm = EXAMPLE.COM
+         dns_lookup_kdc = false
+         dns_lookup_realm = false
+
+[realms]
+         EXAMPLE.COM = {
+             kdc = kerberos.example.com
+             admin_server = kerberos.example.com
+         }
\ No newline at end of file
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml
new file mode 100644
index 0000000..d590a50
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<!--
+  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.
+-->
+<configuration>
+    <property>
+        <name>hadoop.security.authentication</name>
+        <value>simple</value>
+    </property>
+</configuration>
\ No newline at end of file
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml
new file mode 100644
index 0000000..3dbd576
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
+	<property>
+		<name>xasecure.audit.is.enabled</name>
+		<value>true</value>
+	</property>
+
+	<!-- DB audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.db</name>
+		<value>false</value>
+	</property>	
+	
+	<property>
+		<name>xasecure.audit.destination.db.jdbc.driver</name>
+		<value>com.mysql.jdbc.Driver</value>
+	</property>	
+	
+	<property>
+		<name>xasecure.audit.destination.db.jdbc.url</name>
+		<value>jdbc:mysql://localhost/ranger_audit</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.password</name>
+		<value>rangerlogger</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.user</name>
+		<value>rangerlogger</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.db.batch.filespool.dir</name>
+		<value>/tmp/audit/db/spool</value>
+	</property>
+
+
+	<!-- HDFS audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.hdfs</name>
+		<value>false</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.dir</name>
+		<value>hdfs://localhost:8020/ranger/audit</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.hdfs.batch.filespool.dir</name>
+		<value>/tmp/audit/hdfs/spool</value>
+	</property>
+
+
+	<!-- Log4j audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.log4j</name>
+		<value>false</value>
+	</property>	
+
+	<property>
+		<name>xasecure.audit.destination.log4j.logger</name>
+		<value>ranger_audit_logger</value>
+	</property>
+
+	<!-- Solr audit provider configuration -->
+	<property>
+		<name>xasecure.audit.destination.solr</name>
+		<value>true</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.solr.batch.filespool.dir</name>
+		<value>/tmp/audit/solr/spool</value>
+	</property>
+
+	<property>
+		<name>xasecure.audit.destination.solr.urls</name>
+		<value>http://localhost:6083/solr/ranger_audits</value>
+	</property>
+
+</configuration>
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml
new file mode 100644
index 0000000..ab55fba
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<!--
+  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.
+-->
+<configuration>
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.url</name>
+		<value>http://localhost:6080</value>
+		<description>
+			URL to Ranger Admin
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.service.name</name>
+		<value>nifi-registry</value>
+		<description>
+			Name of the Ranger service containing policies for this nifi instance
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.source.impl</name>
+		<value>org.apache.ranger.admin.client.RangerAdminRESTClient</value>
+		<description>
+			Class to retrieve policies from the source
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.ssl.config.file</name>
+		<value>ranger-policymgr-ssl.xml</value>
+		<description>
+			Path to the file containing SSL details to contact Ranger Admin
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.pollIntervalMs</name>
+		<value>30000</value>
+		<description>
+			How often to poll for changes in policies?
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.cache.dir</name>
+		<value>/tmp</value>
+		<description>
+			Directory where Ranger policies are cached after successful retrieval from the source
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.client.connection.timeoutMs</name>
+		<value>120000</value>
+		<description>
+			RangerRestClient Connection Timeout in Milli Seconds
+		</description>
+	</property>
+
+	<property>
+		<name>ranger.plugin.nifi-registry.policy.rest.client.read.timeoutMs</name>
+		<value>30000</value>
+		<description>
+			RangerRestClient read Timeout in Milli Seconds
+		</description>
+	</property>
+</configuration>
diff --git a/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
new file mode 100644
index 0000000..a6e0574
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
+<configuration xmlns:xi="http://www.w3.org/2001/XInclude">
+	<!--  The following properties are used for 2-way SSL client server validation -->
+	<property>
+		<name>xasecure.policymgr.clientssl.keystore</name>
+		<value></value>
+		<description> 
+			Java Keystore files 
+		</description>
+	</property>
+	<property>
+		<name>xasecure.policymgr.clientssl.keystore.password</name>
+		<value>none</value>
+		<description> 
+			password for keystore 
+		</description>
+	</property>
+	<property>
+		<name>xasecure.policymgr.clientssl.truststore</name>
+		<value></value>
+		<description> 
+			java truststore file
+		</description>
+	</property>
+	<property>
+		<name>xasecure.policymgr.clientssl.truststore.password</name>
+		<value>none</value>
+		<description> 
+			java  truststore password
+		</description>
+	</property>
+    <property>
+		<name>xasecure.policymgr.clientssl.keystore.credential.file</name>
+		<value></value>
+		<description> 
+			java  keystore credential file
+		</description>
+	</property>
+	<property>
+		<name>xasecure.policymgr.clientssl.truststore.credential.file</name>
+		<value></value>
+		<description> 
+			java  truststore credential file
+		</description>
+	</property>
+</configuration>
\ No newline at end of file
diff --git a/nifi-registry-extensions/nifi-registry-ranger/pom.xml b/nifi-registry-extensions/nifi-registry-ranger/pom.xml
new file mode 100644
index 0000000..1417637
--- /dev/null
+++ b/nifi-registry-extensions/nifi-registry-ranger/pom.xml
@@ -0,0 +1,35 @@
+<?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</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>nifi-registry-ranger-assembly</module>
+        <module>nifi-registry-ranger-jersey-bundle</module>
+        <module>nifi-registry-ranger-plugin</module>
+    </modules>
+
+</project>
\ No newline at end of file
diff --git a/nifi-registry-extensions/pom.xml b/nifi-registry-extensions/pom.xml
new file mode 100644
index 0000000..47f8196
--- /dev/null
+++ b/nifi-registry-extensions/pom.xml
@@ -0,0 +1,34 @@
+<?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</artifactId>
+        <groupId>org.apache.nifi.registry</groupId>
+        <version>0.3.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-registry-extensions</artifactId>
+    <packaging>pom</packaging>
+
+    <modules>
+        <module>nifi-registry-ranger</module>
+    </modules>
+
+
+</project>
\ No newline at end of file
diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionCloseable.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionCloseable.java
new file mode 100644
index 0000000..b24f950
--- /dev/null
+++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/extension/ExtensionCloseable.java
@@ -0,0 +1,49 @@
+/*
+ * 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.extension;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+public class ExtensionCloseable implements Closeable {
+    private final ClassLoader toSet;
+
+    private ExtensionCloseable(ClassLoader toSet) {
+        this.toSet = toSet;
+    }
+
+    public static ExtensionCloseable withComponentClassLoader(final ExtensionManager manager, final Class componentClass) {
+
+        final ClassLoader current = Thread.currentThread().getContextClassLoader();
+        final ExtensionCloseable closeable = new ExtensionCloseable(current);
+
+        ClassLoader componentClassLoader = manager.getExtensionClassLoader(componentClass.getName());
+        if (componentClassLoader == null) {
+            componentClassLoader = componentClass.getClassLoader();
+        }
+
+        Thread.currentThread().setContextClassLoader(componentClassLoader);
+        return closeable;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (toSet != null) {
+            Thread.currentThread().setContextClassLoader(toSet);
+        }
+    }
+}
diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerCapabilityDetection.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerCapabilityDetection.java
index 5252c7f..0652583 100644
--- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerCapabilityDetection.java
+++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerCapabilityDetection.java
@@ -16,15 +16,6 @@
  */
 package org.apache.nifi.registry.security.authorization;
 
-import org.apache.nifi.registry.security.authorization.AccessPolicy;
-import org.apache.nifi.registry.security.authorization.AccessPolicyProvider;
-import org.apache.nifi.registry.security.authorization.Authorizer;
-import org.apache.nifi.registry.security.authorization.ConfigurableAccessPolicyProvider;
-import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
-import org.apache.nifi.registry.security.authorization.Group;
-import org.apache.nifi.registry.security.authorization.ManagedAuthorizer;
-import org.apache.nifi.registry.security.authorization.User;
-
 public final class AuthorizerCapabilityDetection {
 
     public static boolean isManagedAuthorizer(final Authorizer authorizer) {
diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
index 3094309..43862fe 100644
--- a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
+++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/AuthorizerFactory.java
@@ -17,6 +17,7 @@
 package org.apache.nifi.registry.security.authorization;
 
 import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.extension.ExtensionCloseable;
 import org.apache.nifi.registry.extension.ExtensionManager;
 import org.apache.nifi.registry.properties.NiFiRegistryProperties;
 import org.apache.nifi.registry.properties.SensitivePropertyProtectionException;
@@ -193,7 +194,12 @@
                         // configure each authorizer
                         for (final org.apache.nifi.registry.security.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) {
                             final Authorizer instance = authorizers.get(provider.getIdentifier());
-                            instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
+                            final Class authorizerClass = instance instanceof WrappedAuthorizer
+                                    ? ((WrappedAuthorizer) instance).getBaseAuthorizer().getClass()
+                                    : instance.getClass();
+                            try (ExtensionCloseable extCloseable = ExtensionCloseable.withComponentClassLoader(extensionManager, authorizerClass)) {
+                                instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
+                            }
                         }
 
                         // get the authorizer instance
@@ -458,351 +464,380 @@
         };
     }
 
+    private interface WrappedAuthorizer {
+        Authorizer getBaseAuthorizer();
+    }
+
+    private static class ManagedAuthorizerWrapper implements ManagedAuthorizer, WrappedAuthorizer {
+        private final ManagedAuthorizer baseManagedAuthorizer;
+
+        public ManagedAuthorizerWrapper(ManagedAuthorizer baseManagedAuthorizer) {
+            this.baseManagedAuthorizer = baseManagedAuthorizer;
+        }
+
+        @Override
+        public Authorizer getBaseAuthorizer() {
+            return baseManagedAuthorizer;
+        }
+
+        @Override
+        public String getFingerprint() throws AuthorizationAccessException {
+            return baseManagedAuthorizer.getFingerprint();
+        }
+
+        @Override
+        public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+            baseManagedAuthorizer.inheritFingerprint(fingerprint);
+        }
+
+        @Override
+        public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+            baseManagedAuthorizer.checkInheritability(proposedFingerprint);
+        }
+
+        @Override
+        public AccessPolicyProvider getAccessPolicyProvider() {
+            final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
+            if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
+                return new ConfigurableAccessPolicyProvider() {
+                    @Override
+                    public String getFingerprint() throws AuthorizationAccessException {
+                        return baseConfigurableAccessPolicyProvider.getFingerprint();
+                    }
+
+                    @Override
+                    public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                        baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
+                    }
+
+                    @Override
+                    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                        baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
+                    }
+
+                    @Override
+                    public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                        if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) {
+                            throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
+                        }
+                        return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
+                    }
+
+                    @Override
+                    public boolean isConfigurable(AccessPolicy accessPolicy) {
+                        return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
+                    }
+
+                    @Override
+                    public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                        if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
+                            throw new IllegalArgumentException("The specified access policy is not support modification.");
+                        }
+                        return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
+                    }
+
+                    @Override
+                    public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+                        if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
+                            throw new IllegalArgumentException("The specified access policy is not support modification.");
+                        }
+                        return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
+                    }
+
+                    @Override
+                    public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
+                        if (!baseConfigurableAccessPolicyProvider.isConfigurable(baseConfigurableAccessPolicyProvider.getAccessPolicy(accessPolicyIdentifier))) {
+                            throw new IllegalArgumentException("The specified access policy is not support modification.");
+                        }
+                        return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
+                    }
+
+                    @Override
+                    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicies();
+                    }
+
+                    @Override
+                    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
+                    }
+
+                    @Override
+                    public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+                        return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
+                    }
+
+                    @Override
+                    public UserGroupProvider getUserGroupProvider() {
+                        final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider();
+                        if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
+                            final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
+                            return new ConfigurableUserGroupProvider() {
+                                @Override
+                                public String getFingerprint() throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getFingerprint();
+                                }
+
+                                @Override
+                                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+                                    baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
+                                }
+
+                                @Override
+                                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+                                    baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
+                                }
+
+                                @Override
+                                public User addUser(User user) throws AuthorizationAccessException {
+                                    if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                                        throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
+                                    }
+                                    return baseConfigurableUserGroupProvider.addUser(user);
+                                }
+
+                                @Override
+                                public boolean isConfigurable(User user) {
+                                    return baseConfigurableUserGroupProvider.isConfigurable(user);
+                                }
+
+                                @Override
+                                public User updateUser(User user) throws AuthorizationAccessException {
+                                    if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                                        throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
+                                    }
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
+                                        throw new IllegalArgumentException("The specified user does not support modification.");
+                                    }
+                                    return baseConfigurableUserGroupProvider.updateUser(user);
+                                }
+
+                                @Override
+                                public User deleteUser(User user) throws AuthorizationAccessException {
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
+                                        throw new IllegalArgumentException("The specified user does not support modification.");
+                                    }
+                                    return baseConfigurableUserGroupProvider.deleteUser(user);
+                                }
+
+                                @Override
+                                public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getUser(userIdentifier))) {
+                                        throw new IllegalArgumentException("The specified user does not support modification.");
+                                    }
+                                    return baseConfigurableUserGroupProvider.deleteUser(userIdentifier);
+                                }
+
+                                @Override
+                                public Group addGroup(Group group) throws AuthorizationAccessException {
+                                    if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
+                                        throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
+                                    }
+                                    if (!allGroupUsersExist(baseUserGroupProvider, group)) {
+                                        throw new IllegalStateException(String.format("Cannot create group '%s' with users that don't exist.", group.getName()));
+                                    }
+                                    return baseConfigurableUserGroupProvider.addGroup(group);
+                                }
+
+                                @Override
+                                public boolean isConfigurable(Group group) {
+                                    return baseConfigurableUserGroupProvider.isConfigurable(group);
+                                }
+
+                                @Override
+                                public Group updateGroup(Group group) throws AuthorizationAccessException {
+                                    if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
+                                        throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
+                                    }
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
+                                        throw new IllegalArgumentException("The specified group does not support modification.");
+                                    }
+                                    if (!allGroupUsersExist(baseUserGroupProvider, group)) {
+                                        throw new IllegalStateException(String.format("Cannot update group '%s' to add users that don't exist.", group.getName()));
+                                    }
+                                    return baseConfigurableUserGroupProvider.updateGroup(group);
+                                }
+
+                                @Override
+                                public Group deleteGroup(Group group) throws AuthorizationAccessException {
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
+                                        throw new IllegalArgumentException("The specified group does not support modification.");
+                                    }
+                                    return baseConfigurableUserGroupProvider.deleteGroup(group);
+                                }
+
+                                @Override
+                                public Group deleteGroup(String groupId) throws AuthorizationAccessException {
+                                    if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getGroup(groupId))) {
+                                        throw new IllegalArgumentException("The specified group does not support modification.");
+                                    }
+                                    return baseConfigurableUserGroupProvider.deleteGroup(groupId);
+                                }
+
+                                @Override
+                                public Set<User> getUsers() throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getUsers();
+                                }
+
+                                @Override
+                                public User getUser(String identifier) throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getUser(identifier);
+                                }
+
+                                @Override
+                                public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
+                                }
+
+                                @Override
+                                public Set<Group> getGroups() throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getGroups();
+                                }
+
+                                @Override
+                                public Group getGroup(String identifier) throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getGroup(identifier);
+                                }
+
+                                @Override
+                                public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
+                                    return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
+                                }
+
+                                @Override
+                                public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+                                    baseConfigurableUserGroupProvider.initialize(initializationContext);
+                                }
+
+                                @Override
+                                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+                                    baseConfigurableUserGroupProvider.onConfigured(configurationContext);
+                                }
+
+                                @Override
+                                public void preDestruction() throws SecurityProviderDestructionException {
+                                    baseConfigurableUserGroupProvider.preDestruction();
+                                }
+                            };
+                        } else {
+                            return baseUserGroupProvider;
+                        }
+                    }
+
+                    @Override
+                    public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+                        baseConfigurableAccessPolicyProvider.initialize(initializationContext);
+                    }
+
+                    @Override
+                    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+                        baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
+                    }
+
+                    @Override
+                    public void preDestruction() throws SecurityProviderDestructionException {
+                        baseConfigurableAccessPolicyProvider.preDestruction();
+                    }
+                };
+            } else {
+                return baseAccessPolicyProvider;
+            }
+        }
+
+        @Override
+        public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+            final AuthorizationResult result = baseManagedAuthorizer.authorize(request);
+
+            // audit the authorization request
+            audit(baseManagedAuthorizer, request, result);
+
+            return result;
+        }
+
+        @Override
+        public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException {
+            baseManagedAuthorizer.initialize(initializationContext);
+        }
+
+        @Override
+        public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+            baseManagedAuthorizer.onConfigured(configurationContext);
+
+            final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
+            final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
+
+            // ensure that only one policy per resource-action exists
+            for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
+                if (policyExists(accessPolicyProvider, accessPolicy)) {
+                    throw new SecurityProviderCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
+                }
+            }
+
+            // ensure that only one group exists per identity
+            for (User user : userGroupProvider.getUsers()) {
+                if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) {
+                    throw new SecurityProviderCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
+                }
+            }
+
+            // ensure that only one group exists per identity
+            for (Group group : userGroupProvider.getGroups()) {
+                if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) {
+                    throw new SecurityProviderCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
+                }
+            }
+        }
+
+        @Override
+        public void preDestruction() throws SecurityProviderDestructionException {
+            baseManagedAuthorizer.preDestruction();
+        }
+    }
+
+    private static class AuthorizerWrapper implements Authorizer, WrappedAuthorizer {
+        private final Authorizer baseAuthorizer;
+
+        public AuthorizerWrapper(Authorizer baseAuthorizer) {
+            this.baseAuthorizer = baseAuthorizer;
+        }
+
+        @Override
+        public Authorizer getBaseAuthorizer() {
+            return baseAuthorizer;
+        }
+
+        @Override
+        public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+            final AuthorizationResult result = baseAuthorizer.authorize(request);
+
+            // audit the authorization request
+            audit(baseAuthorizer, request, result);
+
+            return result;
+        }
+
+        @Override
+        public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException {
+            baseAuthorizer.initialize(initializationContext);
+        }
+
+        @Override
+        public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+            baseAuthorizer.onConfigured(configurationContext);
+        }
+
+        @Override
+        public void preDestruction() throws SecurityProviderDestructionException {
+            baseAuthorizer.preDestruction();
+        }
+    }
+
     private static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) {
         if (baseAuthorizer instanceof ManagedAuthorizer) {
-            final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
-            return new ManagedAuthorizer() {
-                @Override
-                public String getFingerprint() throws AuthorizationAccessException {
-                    return baseManagedAuthorizer.getFingerprint();
-                }
-
-                @Override
-                public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
-                    baseManagedAuthorizer.inheritFingerprint(fingerprint);
-                }
-
-                @Override
-                public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
-                    baseManagedAuthorizer.checkInheritability(proposedFingerprint);
-                }
-
-                @Override
-                public AccessPolicyProvider getAccessPolicyProvider() {
-                    final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
-                    if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
-                        final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
-                        return new ConfigurableAccessPolicyProvider() {
-                            @Override
-                            public String getFingerprint() throws AuthorizationAccessException {
-                                return baseConfigurableAccessPolicyProvider.getFingerprint();
-                            }
-
-                            @Override
-                            public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
-                                baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
-                            }
-
-                            @Override
-                            public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
-                                baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
-                            }
-
-                            @Override
-                            public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                                if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) {
-                                    throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
-                                }
-                                return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
-                            }
-
-                            @Override
-                            public boolean isConfigurable(AccessPolicy accessPolicy) {
-                                return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
-                            }
-
-                            @Override
-                            public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                                if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
-                                    throw new IllegalArgumentException("The specified access policy is not support modification.");
-                                }
-                                return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
-                            }
-
-                            @Override
-                            public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-                                if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
-                                    throw new IllegalArgumentException("The specified access policy is not support modification.");
-                                }
-                                return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
-                            }
-
-                            @Override
-                            public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException {
-                                if (!baseConfigurableAccessPolicyProvider.isConfigurable(baseConfigurableAccessPolicyProvider.getAccessPolicy(accessPolicyIdentifier))) {
-                                    throw new IllegalArgumentException("The specified access policy is not support modification.");
-                                }
-                                return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
-                            }
-
-                            @Override
-                            public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-                                return baseConfigurableAccessPolicyProvider.getAccessPolicies();
-                            }
-
-                            @Override
-                            public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
-                                return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
-                            }
-
-                            @Override
-                            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
-                                return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
-                            }
-
-                            @Override
-                            public UserGroupProvider getUserGroupProvider() {
-                                final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider();
-                                if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
-                                    final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
-                                    return new ConfigurableUserGroupProvider() {
-                                        @Override
-                                        public String getFingerprint() throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getFingerprint();
-                                        }
-
-                                        @Override
-                                        public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
-                                            baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
-                                        }
-
-                                        @Override
-                                        public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
-                                            baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
-                                        }
-
-                                        @Override
-                                        public User addUser(User user) throws AuthorizationAccessException {
-                                            if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
-                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
-                                            }
-                                            return baseConfigurableUserGroupProvider.addUser(user);
-                                        }
-
-                                        @Override
-                                        public boolean isConfigurable(User user) {
-                                            return baseConfigurableUserGroupProvider.isConfigurable(user);
-                                        }
-
-                                        @Override
-                                        public User updateUser(User user) throws AuthorizationAccessException {
-                                            if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
-                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
-                                            }
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
-                                                throw new IllegalArgumentException("The specified user does not support modification.");
-                                            }
-                                            return baseConfigurableUserGroupProvider.updateUser(user);
-                                        }
-
-                                        @Override
-                                        public User deleteUser(User user) throws AuthorizationAccessException {
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
-                                                throw new IllegalArgumentException("The specified user does not support modification.");
-                                            }
-                                            return baseConfigurableUserGroupProvider.deleteUser(user);
-                                        }
-
-                                        @Override
-                                        public User deleteUser(String userIdentifier) throws AuthorizationAccessException {
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getUser(userIdentifier))) {
-                                                throw new IllegalArgumentException("The specified user does not support modification.");
-                                            }
-                                            return baseConfigurableUserGroupProvider.deleteUser(userIdentifier);
-                                        }
-
-                                        @Override
-                                        public Group addGroup(Group group) throws AuthorizationAccessException {
-                                            if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
-                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
-                                            }
-                                            if (!allGroupUsersExist(baseUserGroupProvider, group)) {
-                                                throw new IllegalStateException(String.format("Cannot create group '%s' with users that don't exist.", group.getName()));
-                                            }
-                                            return baseConfigurableUserGroupProvider.addGroup(group);
-                                        }
-
-                                        @Override
-                                        public boolean isConfigurable(Group group) {
-                                            return baseConfigurableUserGroupProvider.isConfigurable(group);
-                                        }
-
-                                        @Override
-                                        public Group updateGroup(Group group) throws AuthorizationAccessException {
-                                            if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
-                                                throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
-                                            }
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
-                                                throw new IllegalArgumentException("The specified group does not support modification.");
-                                            }
-                                            if (!allGroupUsersExist(baseUserGroupProvider, group)) {
-                                                throw new IllegalStateException(String.format("Cannot update group '%s' to add users that don't exist.", group.getName()));
-                                            }
-                                            return baseConfigurableUserGroupProvider.updateGroup(group);
-                                        }
-
-                                        @Override
-                                        public Group deleteGroup(Group group) throws AuthorizationAccessException {
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
-                                                throw new IllegalArgumentException("The specified group does not support modification.");
-                                            }
-                                            return baseConfigurableUserGroupProvider.deleteGroup(group);
-                                        }
-
-                                        @Override
-                                        public Group deleteGroup(String groupId) throws AuthorizationAccessException {
-                                            if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getGroup(groupId))) {
-                                                throw new IllegalArgumentException("The specified group does not support modification.");
-                                            }
-                                            return baseConfigurableUserGroupProvider.deleteGroup(groupId);
-                                        }
-
-                                        @Override
-                                        public Set<User> getUsers() throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getUsers();
-                                        }
-
-                                        @Override
-                                        public User getUser(String identifier) throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getUser(identifier);
-                                        }
-
-                                        @Override
-                                        public User getUserByIdentity(String identity) throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
-                                        }
-
-                                        @Override
-                                        public Set<Group> getGroups() throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getGroups();
-                                        }
-
-                                        @Override
-                                        public Group getGroup(String identifier) throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getGroup(identifier);
-                                        }
-
-                                        @Override
-                                        public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
-                                            return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
-                                        }
-
-                                        @Override
-                                        public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
-                                            baseConfigurableUserGroupProvider.initialize(initializationContext);
-                                        }
-
-                                        @Override
-                                        public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
-                                            baseConfigurableUserGroupProvider.onConfigured(configurationContext);
-                                        }
-
-                                        @Override
-                                        public void preDestruction() throws SecurityProviderDestructionException {
-                                            baseConfigurableUserGroupProvider.preDestruction();
-                                        }
-                                    };
-                                } else {
-                                    return baseUserGroupProvider;
-                                }
-                            }
-
-                            @Override
-                            public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
-                                baseConfigurableAccessPolicyProvider.initialize(initializationContext);
-                            }
-
-                            @Override
-                            public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
-                                baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
-                            }
-
-                            @Override
-                            public void preDestruction() throws SecurityProviderDestructionException {
-                                baseConfigurableAccessPolicyProvider.preDestruction();
-                            }
-                        };
-                    } else {
-                        return baseAccessPolicyProvider;
-                    }
-                }
-
-                @Override
-                public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
-                    final AuthorizationResult result = baseAuthorizer.authorize(request);
-
-                    // audit the authorization request
-                    audit(baseAuthorizer, request, result);
-
-                    return result;
-                }
-
-                @Override
-                public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException {
-                    baseManagedAuthorizer.initialize(initializationContext);
-                }
-
-                @Override
-                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
-                    baseManagedAuthorizer.onConfigured(configurationContext);
-
-                    final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
-                    final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
-
-                    // ensure that only one policy per resource-action exists
-                    for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
-                        if (policyExists(accessPolicyProvider, accessPolicy)) {
-                            throw new SecurityProviderCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
-                        }
-                    }
-
-                    // ensure that only one group exists per identity
-                    for (User user : userGroupProvider.getUsers()) {
-                        if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) {
-                            throw new SecurityProviderCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
-                        }
-                    }
-
-                    // ensure that only one group exists per identity
-                    for (Group group : userGroupProvider.getGroups()) {
-                        if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) {
-                            throw new SecurityProviderCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
-                        }
-                    }
-                }
-
-                @Override
-                public void preDestruction() throws SecurityProviderDestructionException {
-                    baseManagedAuthorizer.preDestruction();
-                }
-            };
+            return new ManagedAuthorizerWrapper((ManagedAuthorizer) baseAuthorizer);
         } else {
-            return new Authorizer() {
-                @Override
-                public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
-                    final AuthorizationResult result = baseAuthorizer.authorize(request);
-
-                    // audit the authorization request
-                    audit(baseAuthorizer, request, result);
-
-                    return result;
-                }
-
-                @Override
-                public void initialize(AuthorizerInitializationContext initializationContext) throws SecurityProviderCreationException {
-                    baseAuthorizer.initialize(initializationContext);
-                }
-
-                @Override
-                public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
-                    baseAuthorizer.onConfigured(configurationContext);
-                }
-
-                @Override
-                public void preDestruction() throws SecurityProviderDestructionException {
-                    baseAuthorizer.preDestruction();
-                }
-            };
+            return new AuthorizerWrapper(baseAuthorizer);
         }
     }
 
diff --git a/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java b/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
index 62caf2c..1dcb0f7 100644
--- a/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
+++ b/nifi-registry-properties/src/main/java/org/apache/nifi/registry/properties/NiFiRegistryProperties.java
@@ -76,6 +76,8 @@
     public static final String KERBEROS_SPNEGO_PRINCIPAL = "nifi.registry.kerberos.spnego.principal";
     public static final String KERBEROS_SPNEGO_KEYTAB_LOCATION = "nifi.registry.kerberos.spnego.keytab.location";
     public static final String KERBEROS_SPNEGO_AUTHENTICATION_EXPIRATION = "nifi.registry.kerberos.spnego.authentication.expiration";
+    public static final String KERBEROS_SERVICE_PRINCIPAL = "nifi.registry.kerberos.service.principal";
+    public static final String KERBEROS_SERVICE_KEYTAB_LOCATION = "nifi.registry.kerberos.service.keytab.location";
 
     // Defaults
     public static final String DEFAULT_WEB_WORKING_DIR = "./work/jetty";
@@ -230,6 +232,14 @@
         return !StringUtils.isBlank(getKerberosSpnegoPrincipal()) && !StringUtils.isBlank(getKerberosSpnegoKeytabLocation());
     }
 
+    public String getKerberosServicePrincipal() {
+        return getPropertyAsTrimmedString(KERBEROS_SERVICE_PRINCIPAL);
+    }
+
+    public String getKerberosServiceKeytabLocation() {
+        return getPropertyAsTrimmedString(KERBEROS_SERVICE_KEYTAB_LOCATION);
+    }
+
     public Set<String> getExtensionsDirs() {
         final Set<String> extensionDirs = new HashSet<>();
         stringPropertyNames().stream().filter(key -> key.startsWith(EXTENSION_DIR_PREFIX)).forEach(key -> extensionDirs.add(getProperty(key)));
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
index ef0f97e..a5ab5ef 100644
--- a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/NiFiRegistryResourceConfig.java
@@ -20,6 +20,7 @@
 import org.apache.nifi.registry.web.api.AccessResource;
 import org.apache.nifi.registry.web.api.BucketFlowResource;
 import org.apache.nifi.registry.web.api.BucketResource;
+import org.apache.nifi.registry.web.api.ConfigResource;
 import org.apache.nifi.registry.web.api.FlowResource;
 import org.apache.nifi.registry.web.api.ItemResource;
 import org.apache.nifi.registry.web.api.TenantResource;
@@ -59,6 +60,7 @@
         register(FlowResource.class);
         register(ItemResource.class);
         register(TenantResource.class);
+        register(ConfigResource.class);
 
         // include bean validation errors in response
         property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
diff --git a/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ConfigResource.java b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ConfigResource.java
new file mode 100644
index 0000000..a600a11
--- /dev/null
+++ b/nifi-registry-web-api/src/main/java/org/apache/nifi/registry/web/api/ConfigResource.java
@@ -0,0 +1,108 @@
+/*
+ * 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.web.api;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiResponse;
+import io.swagger.annotations.ApiResponses;
+import io.swagger.annotations.Authorization;
+import io.swagger.annotations.Extension;
+import io.swagger.annotations.ExtensionProperty;
+import org.apache.nifi.registry.RegistryConfiguration;
+import org.apache.nifi.registry.event.EventService;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.AuthorizerCapabilityDetection;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.resource.Authorizable;
+import org.apache.nifi.registry.service.AuthorizationService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+@Component
+@Path("/config")
+@Api(
+        value = "config",
+        description = "Retrieves the configuration for this NiFi Registry.",
+        authorizations = { @Authorization("Authorization") }
+)
+public class ConfigResource extends AuthorizableApplicationResource {
+
+    @Autowired
+    public ConfigResource(
+            final AuthorizationService authorizationService,
+            final EventService eventService) {
+        super(authorizationService, eventService);
+    }
+
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @ApiOperation(
+            value = "Gets NiFi Registry configurations",
+            response = RegistryConfiguration.class,
+            extensions = {
+                    @Extension(name = "access-policy", properties = {
+                            @ExtensionProperty(name = "action", value = "read"),
+                            @ExtensionProperty(name = "resource", value = "/policies,/tenants") })
+            }
+    )
+    @ApiResponses({
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401),
+            @ApiResponse(code = 401, message = HttpStatusMessages.MESSAGE_401) })
+    public Response getConfiguration() {
+
+        final RegistryConfiguration config = new RegistryConfiguration();
+
+        boolean hasAnyConfigurationAccess = false;
+        AccessDeniedException lastAccessDeniedException = null;
+        final Authorizer authorizer = authorizationService.getAuthorizer();
+        try {
+            final Authorizable policyAuthorizer = authorizableLookup.getPoliciesAuthorizable();
+            authorizationService.authorize(policyAuthorizer, RequestAction.READ);
+            config.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer));
+            config.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer));
+            hasAnyConfigurationAccess = true;
+        } catch (AccessDeniedException e) {
+            lastAccessDeniedException = e;
+        }
+
+        try {
+            authorizationService.authorize(authorizableLookup.getTenantsAuthorizable(), RequestAction.READ);
+            config.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer));
+            hasAnyConfigurationAccess = true;
+        } catch (AccessDeniedException e) {
+            lastAccessDeniedException = e;
+        }
+
+        if (!hasAnyConfigurationAccess) {
+            // If the user doesn't have access to any configuration, then throw the exception.
+            // Otherwise, return what they can access.
+            throw lastAccessDeniedException;
+        }
+
+        return Response.status(Response.Status.OK).entity(config).build();
+    }
+}
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
index 1e524b1..7062f6b 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.html
@@ -24,7 +24,7 @@
                       (add)="nfRegistryService.usersSearchAdd($event)"
                       (remove)="nfRegistryService.usersSearchRemove($event)"></td-chips>
             <div matTooltip="{{(!nfRegistryService.currentUser.resourcePermissions.tenants.canWrite) ? 'You do not have permission. Please contact your system administrator.': ''}}">
-                <button id="add-user-button" class="push-top-sm push-right-sm" color="fds-secondary" mat-raised-button (click)="addUser()" [disabled]="!nfRegistryService.currentUser.resourcePermissions.tenants.canWrite">
+                <button id="add-user-button" class="push-top-sm push-right-sm" color="fds-secondary" mat-raised-button (click)="addUser()" [disabled]="!canEditUsers()">
                     Add User
                 </button>
             </div>
@@ -36,13 +36,13 @@
         <mat-menu class="fds-primary-dropdown-button-menu" #userActionMenu="matMenu" [overlapTrigger]="false">
             <div matTooltip="{{nfRegistryService.disableMultiDeleteAction ? 'Please deselect any non-configurable users or groups to enable multi-delete.': ''}}">
                 <button mat-menu-item
-                    [disabled]="((nfRegistryService.getSelectedGroups().length === 0) && (nfRegistryService.getSelectedUsers().length === 0)) || nfRegistryService.disableMultiDeleteAction"
+                    [disabled]="((nfRegistryService.getSelectedGroups().length === 0) && (nfRegistryService.getSelectedUsers().length === 0)) || nfRegistryService.disableMultiDeleteAction || !canEditUsers()"
                     (click)="nfRegistryService.deleteSelectedUsersAndGroups()">
                     <span>Delete</span>
                 </button>
             </div>
             <div matTooltip="{{(nfRegistryService.getSelectedGroups().length > 0) ? 'Only users can be added when creating a new group. Please deselect any groups to enable.': (!nfRegistryService.currentUser.resourcePermissions.tenants.canWrite) ? 'You do not have permission. Please contact your system administrator.': ''}}">
-                <button mat-menu-item [disabled]="(nfRegistryService.getSelectedGroups().length > 0) || !nfRegistryService.currentUser.resourcePermissions.tenants.canWrite"
+                <button mat-menu-item [disabled]="(nfRegistryService.getSelectedGroups().length > 0) || !canEditUsers()"
                     (click)="createNewGroup()">
                     <span>Create new group</span>
                 </button>
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
index 552628f..08def45 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/nf-registry-users-administration.js
@@ -115,6 +115,15 @@
         this.dialog.open(NfRegistryCreateNewGroup, {
             disableClose: true
         });
+    },
+
+    /**
+     * Determine if users can be edited.
+     * @returns {boolean}
+     */
+    canEditUsers: function () {
+        return this.nfRegistryService.currentUser.resourcePermissions.tenants.canWrite
+                && this.nfRegistryService.registry.config.supportsConfigurableUsersAndGroups;
     }
 };
 
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
index 9927e49..8317ee2 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.html
@@ -46,7 +46,7 @@
                    class="pad-left-sm fa fa-question-circle-o help-icon"></i>
             </span>
             </div>
-            <mat-checkbox [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+            <mat-checkbox [disabled]="!canEditSpecialPrivileges()"
                           [checked]="nfRegistryService.group.resourcePermissions.buckets.canRead && nfRegistryService.group.resourcePermissions.buckets.canWrite && nfRegistryService.group.resourcePermissions.buckets.canDelete"
                           (change)="toggleGroupManageBucketsPrivileges($event)">
             <span class="description">Can manage buckets<i
@@ -55,25 +55,25 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.buckets.canRead"
                               (change)="toggleGroupManageBucketsPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.buckets.canWrite"
                               (change)="toggleGroupManageBucketsPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.buckets.canDelete"
                               (change)="toggleGroupManageBucketsPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
-            <mat-checkbox [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+            <mat-checkbox [disabled]="!canEditSpecialPrivileges()"
                           [checked]="nfRegistryService.group.resourcePermissions.tenants.canRead && nfRegistryService.group.resourcePermissions.tenants.canWrite && nfRegistryService.group.resourcePermissions.tenants.canDelete"
                           (change)="toggleGroupManageTenantsPrivileges($event)">
             <span class="description">Can manage users<i
@@ -82,25 +82,25 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.tenants.canRead"
                               (change)="toggleGroupManageTenantsPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.tenants.canWrite"
                               (change)="toggleGroupManageTenantsPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.tenants.canDelete"
                               (change)="toggleGroupManageTenantsPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
-            <mat-checkbox [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+            <mat-checkbox [disabled]="!canEditSpecialPrivileges()"
                           [checked]="nfRegistryService.group.resourcePermissions.policies.canRead && nfRegistryService.group.resourcePermissions.policies.canWrite && nfRegistryService.group.resourcePermissions.policies.canDelete"
                           (change)="toggleGroupManagePoliciesPrivileges($event)">
             <span class="description">Can manage policies<i
@@ -109,25 +109,25 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.policies.canRead"
                               (change)="toggleGroupManagePoliciesPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.policies.canWrite"
                               (change)="toggleGroupManagePoliciesPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.group.resourcePermissions.policies.canDelete"
                               (change)="toggleGroupManagePoliciesPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
-            <mat-checkbox [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+            <mat-checkbox [disabled]="!canEditSpecialPrivileges()"
                           [checked]="nfRegistryService.group.resourcePermissions.proxy.canWrite"
                           (change)="toggleGroupManageProxyPrivileges($event)">
             <span class="description">Can proxy user requests<i
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.js
index 7e48d79..94f99a5 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.js
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-group/nf-registry-manage-group.js
@@ -577,6 +577,15 @@
                 });
             }
         });
+    },
+
+    /**
+     * Determine if 'Special Privileges' can be edited.
+     * @returns {boolean}
+     */
+    canEditSpecialPrivileges: function() {
+        return this.nfRegistryService.currentUser.resourcePermissions.policies.canWrite
+                && this.nfRegistryService.registry.config.supportsConfigurableAuthorizer;
     }
 };
 
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
index f44a7cb..e1fb346 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.html
@@ -49,7 +49,7 @@
             </span>
             </div>
             <mat-checkbox
-                    [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                    [disabled]="!canEditSpecialPrivileges()"
                     [checked]="nfRegistryService.user.resourcePermissions.buckets.canRead && nfRegistryService.user.resourcePermissions.buckets.canWrite && nfRegistryService.user.resourcePermissions.buckets.canDelete"
                     (change)="toggleUserManageBucketsPrivileges($event)">
             <span class="description">Can manage buckets<i
@@ -58,26 +58,26 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.buckets.canRead"
                               (change)="toggleUserManageBucketsPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.buckets.canWrite"
                               (change)="toggleUserManageBucketsPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.buckets.canDelete"
                               (change)="toggleUserManageBucketsPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
             <mat-checkbox
-                    [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                    [disabled]="!canEditSpecialPrivileges()"
                     [checked]="nfRegistryService.user.resourcePermissions.tenants.canRead && nfRegistryService.user.resourcePermissions.tenants.canWrite && nfRegistryService.user.resourcePermissions.tenants.canDelete"
                     (change)="toggleUserManageTenantsPrivileges($event)">
             <span class="description">Can manage users<i
@@ -86,26 +86,26 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.tenants.canRead"
                               (change)="toggleUserManageTenantsPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.tenants.canWrite"
                               (change)="toggleUserManageTenantsPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.tenants.canDelete"
                               (change)="toggleUserManageTenantsPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
             <mat-checkbox
-                    [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                    [disabled]="!canEditSpecialPrivileges()"
                     [checked]="nfRegistryService.user.resourcePermissions.policies.canRead && nfRegistryService.user.resourcePermissions.policies.canWrite && nfRegistryService.user.resourcePermissions.policies.canDelete"
                     (change)="toggleUserManagePoliciesPrivileges($event)">
             <span class="description">Can manage policies<i
@@ -114,26 +114,26 @@
             </mat-checkbox>
             <div flex fxLayout="row" fxLayoutAlign="space-around center">
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.policies.canRead"
                               (change)="toggleUserManagePoliciesPrivileges($event, 'read')">
                     <span class="description">Read</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.policies.canWrite"
                               (change)="toggleUserManagePoliciesPrivileges($event, 'write')">
                     <span class="description">Write</span>
                 </mat-checkbox>
                 <mat-checkbox class="pad-left-md"
-                              [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                              [disabled]="!canEditSpecialPrivileges()"
                               [(checked)]="nfRegistryService.user.resourcePermissions.policies.canDelete"
                               (change)="toggleUserManagePoliciesPrivileges($event, 'delete')">
                     <span class="description">Delete</span>
                 </mat-checkbox>
             </div>
             <mat-checkbox
-                    [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite || (nfRegistryService.currentUser.identity === nfRegistryService.user.identity)"
+                    [disabled]="!canEditSpecialPrivileges()"
                     [checked]="nfRegistryService.user.resourcePermissions.proxy.canWrite"
                     (change)="toggleUserManageProxyPrivileges($event)">
             <span class="description">Can proxy user requests<i
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.js b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.js
index aa2b0fd..e7595ad 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.js
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/users/sidenav/manage-user/nf-registry-manage-user.js
@@ -599,7 +599,18 @@
             }
         });
         return disabled;
+    },
+
+    /**
+     * Determine if 'Special Privileges' can be edited.
+     * @returns {boolean}
+     */
+    canEditSpecialPrivileges: function() {
+        return this.nfRegistryService.currentUser.resourcePermissions.policies.canWrite
+                && !(this.nfRegistryService.currentUser.identity === this.nfRegistryService.user.identity)
+                && this.nfRegistryService.registry.config.supportsConfigurableAuthorizer;
     }
+
 };
 
 NfRegistryManageUser.annotations = [
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
index 7776655..3728fce 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.html
@@ -45,7 +45,7 @@
             <div flex fxLayout="row" fxLayoutAlign="space-between center">
                 <span class="md-card-title">Policies ({{userIdentitiesWithPolicies.length + groupIdentitiesWithPolicies.length}})</span>
                 <button color="fds-secondary"
-                        [disabled]="nfRegistryService.currentUser.anonymous || !nfRegistryService.currentUser.resourcePermissions.policies.canWrite || !nfRegistryService.currentUser.resourcePermissions.tenants.canRead"
+                        [disabled]="nfRegistryService.currentUser.anonymous || !canEditBucketPolicies() || !nfRegistryService.currentUser.resourcePermissions.tenants.canRead"
                         mat-raised-button
                         (click)="addPolicy()">
                     New Policy
@@ -79,12 +79,12 @@
                     </div>
                     <div class="td-data-table-cell">
                         <button (click)="editPolicy(row);row.checked = !row.checked;"
-                                [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                                [disabled]="!canEditBucketPolicies()"
                                 matTooltip="'Edit user policies of this bucket'" mat-icon-button color="accent">
                             <i class="fa fa-pencil" aria-hidden="true"></i>
                         </button>
                         <button (click)="removePolicyFromBucket(row);row.checked = !row.checked;"
-                                [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite"
+                                [disabled]="!canEditBucketPolicies()"
                                 matTooltip="'Remove group policies from bucket'" mat-icon-button color="accent">
                             <i class="fa fa-trash" aria-hidden="true"></i>
                         </button>
@@ -103,12 +103,12 @@
                     <div class="td-data-table-cell">
                         <button (click)="editPolicy(row);row.checked = !row.checked;"
                                 matTooltip="'Edit user policies of this bucket'" mat-icon-button color="accent"
-                                [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite">
+                                [disabled]="!canEditBucketPolicies()">
                             <i class="fa fa-pencil" aria-hidden="true"></i>
                         </button>
                         <button (click)="removePolicyFromBucket(row);row.checked = !row.checked;"
                                 matTooltip="'Remove user policies from this bucket'" mat-icon-button color="accent"
-                                [disabled]="!nfRegistryService.currentUser.resourcePermissions.policies.canWrite">
+                                [disabled]="!canEditBucketPolicies()">
                             <i class="fa fa-trash" aria-hidden="true"></i>
                         </button>
                     </div>
diff --git a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
index bc6391e..023b9d4 100644
--- a/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
+++ b/nifi-registry-web-ui/src/main/webapp/components/administration/workflow/sidenav/manage-bucket/nf-registry-manage-bucket.js
@@ -430,6 +430,15 @@
                 });
             }
         });
+    },
+
+    /**
+     * Determine if bucket policies can be edited.
+     * @returns {boolean}
+     */
+    canEditBucketPolicies: function () {
+        return this.nfRegistryService.currentUser.resourcePermissions.policies.canWrite
+                && this.nfRegistryService.registry.config.supportsConfigurableAuthorizer;
     }
 };
 
diff --git a/nifi-registry-web-ui/src/main/webapp/nf-registry.js b/nifi-registry-web-ui/src/main/webapp/nf-registry.js
index edf840c..bf80f2b 100644
--- a/nifi-registry-web-ui/src/main/webapp/nf-registry.js
+++ b/nifi-registry-web-ui/src/main/webapp/nf-registry.js
@@ -53,6 +53,10 @@
     ngOnInit: function () {
         var self = this;
         this.nfRegistryService.sidenav = this.sidenav; //ngCore.ViewChild
+
+        this.nfRegistryApi.getRegistryConfig().subscribe(function (registryConfig) {
+            self.nfRegistryService.registry.config = registryConfig;
+        });
     },
 
     /**
diff --git a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
index 7db2253..e722f30 100644
--- a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
+++ b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.api.js
@@ -756,7 +756,25 @@
                     }
                 });
             });
+    },
+
+    /**
+     * Get NiFi Registry configuration resource.
+     *
+     * @returns {*}
+     */
+    getRegistryConfig: function (action, resource) {
+        var self = this;
+        return this.http.get('/nifi-registry-api/config')
+            .map(function (response) {
+                return response;
+            })
+            .catch(function (error) {
+                // If failed, return an empty object.
+                return rxjs.Observable.of({});
+            });
     }
+
 };
 
 NfRegistryApi.parameters = [
diff --git a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
index da71cdd..6e8510a 100644
--- a/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
+++ b/nifi-registry-web-ui/src/main/webapp/services/nf-registry.service.js
@@ -37,8 +37,11 @@
 function NfRegistryService(nfRegistryApi, nfStorage, tdDataTableService, router, fdsDialogService, fdsSnackBarService) {
     var self = this;
     this.registry = {
-        name: "NiFi Registry"
+        name: "NiFi Registry",
+        // Config is updated later by calling the /config API.
+        config: {}
     };
+
     this.documentation = {
         link: 'nifi-registry-docs/documentation'
     }
diff --git a/pom.xml b/pom.xml
index ac6f2c4..29d7c7d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -49,6 +49,7 @@
         <module>nifi-registry-assembly</module>
 	    <module>nifi-registry-client</module>
         <module>nifi-registry-docker</module>
+        <module>nifi-registry-extensions</module>
     </modules>
     <url>https://nifi.apache.org/registry.html</url>
     <organization>