diff --git a/nifi-assembly/pom.xml b/nifi-assembly/pom.xml
index 718bce7d1578..2f2034345de7 100644
--- a/nifi-assembly/pom.xml
+++ b/nifi-assembly/pom.xml
@@ -1119,6 +1119,57 @@ language governing permissions and limitations under the License. -->
+
+ include-ranger
+
+ false
+
+ allProfiles
+
+
+
+
+
+ maven-assembly-plugin
+
+
+ make shared resource
+
+ single
+
+ package
+
+ nifi-${project.version}
+ false
+
+ 0775
+ 0775
+ 0664
+
+
+ src/main/assembly/ranger.xml
+
+ posix
+
+
+
+
+
+
+
+
+ org.apache.nifi
+ nifi-ranger-nar
+ 2.10.0.1
+ nar
+
+
+ org.apache.nifi
+ nifi-ranger-resources
+ 2.10.0.1
+
+
+
include-asn1
diff --git a/nifi-assembly/src/main/assembly/ranger.xml b/nifi-assembly/src/main/assembly/ranger.xml
new file mode 100644
index 000000000000..e395cc532a66
--- /dev/null
+++ b/nifi-assembly/src/main/assembly/ranger.xml
@@ -0,0 +1,92 @@
+
+
+
+ bin
+
+ dir
+ zip
+ tar.gz
+
+ true
+ nifi-${project.version}
+
+
+ src/main/assembly/common.xml
+
+
+
+
+
+ runtime
+ false
+ lib
+ 0770
+ 0660
+ true
+
+ *:nifi-bootstrap-utils
+ *:nifi-bootstrap
+ *:nifi-property-protection-api
+ *:nifi-property-protection-factory
+ *:nifi-resources
+ *:nifi-docs
+ org.apache.nifi:nifi-ranger-resources:jar
+
+
+
+ org.glassfish.jaxb:jaxb-runtime
+ jakarta.xml.bind:jakarta.xml.bind-api
+ javax.annotation:javax.annotation-api
+
+
+
+
+ runtime
+ false
+ ext/ranger/install/lib/
+ 0770
+ 0660
+ true
+
+ org.apache.nifi:nifi-ranger-resources:jar
+ org.slf4j:slf4j-api
+ org.slf4j:jcl-over-slf4j
+ org.apache.commons:commons-lang3
+ com.google.guava:guava
+
+
+
+
+ runtime
+ false
+ ext/ranger/
+ 0770
+ 0770
+ false
+
+ org.apache.nifi:nifi-ranger-resources:jar
+
+ true
+
+ true
+
+ scripts/
+
+
+
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/pom.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/pom.xml
new file mode 100644
index 000000000000..f4e066ebc553
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/pom.xml
@@ -0,0 +1,35 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.nifi
+ nifi-ranger-bundle
+ 2.10.0.1
+
+
+ nifi-ranger-nar
+ nar
+
+
+
+ org.apache.nifi
+ nifi-ranger-plugin
+ 2.10.0.1
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/LICENSE b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/LICENSE
new file mode 100644
index 000000000000..2bb247e447b9
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,404 @@
+
+ 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.
+
+APACHE NIFI SUBCOMPONENTS:
+
+The Apache NiFi project contains subcomponents with separate copyright
+notices and license terms. Your use of the source code for the these
+subcomponents is subject to the terms and conditions of the following
+licenses.
+
+ The binary distribution of this product bundles 'JOpt Simple' under an MIT
+ style license.
+
+ Copyright (c) 2009 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 'JCraft Jsch' which is available
+ under a BSD style 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 'Protocol Buffers - Google's data interchange format'
+ which is available under a BSD style license.
+
+ Copyright 2008 Google Inc. All rights reserved.
+ http://code.google.com/p/protobuf/
+
+ 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.
+
+ The binary distribution of this product bundles 'Woodstox StAX 2 API' which is
+ "licensed under standard BSD license"
+
+ The binary distribution of this product bundles 'Azure SDK for Java' which is available under an MIT license.
+
+ Copyright (c) 2015 Microsoft Azure
+
+ 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 'Bouncy Castle' which is available under
+ the Bouncy Castle License (an MIT style license).
+
+ Copyright (c) 2000-2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
+
+ 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 'dnsjava' which is available under a BSD 3-Clause license.
+
+ Copyright (c) 1998-2019, Brian Wellington
+ Copyright (c) 2005 VeriSign. All rights reserved.
+ Copyright (c) 2019-2023, dnsjava 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:
+
+ 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 holder 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 HOLDER 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 'zstd-jni', which bundles
+ the 'Zstandard' (Zstd) library, available under a BSD license.
+
+ BSD License
+
+ For Zstandard software
+
+ Copyright (c) 2016-present, Facebook, 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 Facebook 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 HOLDER 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-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/NOTICE b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/NOTICE
new file mode 100644
index 000000000000..97c6efd4481f
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-nar/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,386 @@
+nifi-ranger-nar
+Copyright 2014-2024 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 Commons EL
+ The following NOTICE information applies:
+ Apache Commons EL
+ Copyright 1999-2007 The Apache Software Foundation
+
+ EL-8 patch - Copyright 2004-2007 Jamie Taylor
+ http://issues.apache.org/jira/browse/EL-8
+
+ (ASLv2) Apache Directory Server
+ The following NOTICE information applies:
+ ApacheDS Protocol Kerberos Codec
+ Copyright 2003-2013 The Apache Software Foundation
+
+ ApacheDS I18n
+ Copyright 2003-2013 The Apache Software Foundation
+
+ Apache Directory API ASN.1 API
+ Copyright 2003-2013 The Apache Software Foundation
+
+ Apache Directory LDAP API Utilities
+ Copyright 2003-2013 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 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) 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) Jets3t
+ The following NOTICE information applies:
+
+ This product includes software developed by:
+
+ The Apache Software Foundation (http://www.apache.org/).
+
+ The ExoLab Project (http://www.exolab.org/)
+
+ Sun Microsystems (http://www.sun.com/)
+
+ Codehaus (http://castor.codehaus.org)
+
+ Tatu Saloranta (http://wiki.fasterxml.com/TatuSaloranta)
+
+ (ASLv2) Jetty
+ The following NOTICE information applies:
+ Jetty Web Container
+ Copyright 1995-2019 Mort Bay Consulting Pty Ltd.
+
+ (ASLv2) Apache Kafka
+ The following NOTICE information applies:
+ Apache Kafka
+ Copyright 2012 The Apache Software Foundation.
+
+ (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) Amazon Web Services SDK
+ The following NOTICE information applies:
+ Copyright 2010-2014 Amazon.com, Inc. or its affiliates. All Rights Reserved.
+
+ This product includes software developed by
+ Amazon Technologies, Inc (http://www.amazon.com/).
+
+ **********************
+ THIRD PARTY COMPONENTS
+ **********************
+ This software includes third party software subject to the following copyrights:
+ - XML parsing and utility functions from JetS3t - Copyright 2006-2009 James Murty.
+ - PKCS#1 PEM encoded private key parsing and utility functions from oauth.googlecode.com - Copyright 1998-2010 AOL Inc.
+
+************************
+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) Old JAXB Runtime (com.sun.xml.bind:jaxb-impl:jar:2.2.3-1 - http://jaxb.java.net/)
+ (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) jersey-bundle (com.sun.jersey:jersey-bundle:jar:1.17 - https://jersey.java.net/jersey-bundle/)
+ (CDDL 1.1) (GPL2 w/ CPE) jersey-core (com.sun.jersey:jersey-core:jar:1.19 - https://jersey.java.net/jersey-core/)
+ (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) jersey-json (com.sun.jersey:jersey-json:jar:1.19 - https://jersey.java.net/jersey-json/)
+ (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)
+
+************************
+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/)
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/pom.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/pom.xml
new file mode 100644
index 000000000000..19ec9358fbb9
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/pom.xml
@@ -0,0 +1,404 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.nifi
+ nifi-ranger-bundle
+ 2.10.0.1
+
+
+ nifi-ranger-plugin
+ jar
+
+
+ org.apache.solr
+ solr-solrj
+
+
+ org.eclipse.jetty
+ jetty-client
+
+
+ org.eclipse.jetty
+ jetty-http
+
+
+ org.eclipse.jetty
+ jetty-io
+
+
+ org.eclipse.jetty
+ jetty-util
+
+
+ org.eclipse.jetty
+ jetty-alpn-client
+
+
+ org.eclipse.jetty
+ jetty-util-ajax
+
+
+ org.apache.nifi
+ nifi-api
+
+
+ org.apache.nifi
+ nifi-framework-api
+
+
+ org.apache.nifi
+ nifi-properties
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ org.apache.nifi
+ nifi-deprecation-log
+
+
+ org.apache.ranger
+ ranger-plugins-common
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+ commons-logging
+ commons-logging
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ org.apache.ranger
+ ranger-plugins-audit
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+ com.amazonaws
+ aws-java-sdk-logs
+
+
+
+
+ org.apache.ranger
+ credentialbuilder
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ org.slf4j
+ slf4j-api
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ com.github.stephenc.findbugs
+ findbugs-annotations
+ 1.3.9-1
+
+
+
+ org.apache.hadoop
+ hadoop-client
+ ${hadoop.version}
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${hadoop.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ org.eclipse.jetty
+ jetty-webapp
+
+
+ org.eclipse.jetty
+ jetty-servlet
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ org.apache.hadoop
+ hadoop-auth
+
+
+ org.apache.nifi
+ nifi-xml-processing
+ 2.10.0.1
+
+
+ org.apache.nifi
+ nifi-mock
+ 2.10.0.1
+ test
+
+
+
+
+
+
+ disable-ranger-tests
+
+
+ aarch64
+
+
+
+ true
+
+
+
+
+ include-hadoop-aws
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-aws
+ ${hadoop.version}
+
+
+
+
+
+ include-hadoop-azure
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-azure
+ ${hadoop.version}
+
+
+ com.google.guava
+ guava
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+ org.apache.hadoop
+ hadoop-azure-datalake
+ ${hadoop.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+
+
+
+ include-hadoop-cloud-storage
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-cloud-storage
+ ${hadoop.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+
+ include-hadoop-ozone
+
+ false
+
+
+
+ org.apache.ozone
+ ozone-client
+ ${ozone.version}
+
+
+ commons-logging
+ commons-logging
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+
+
+ org.apache.ozone
+ ozone-filesystem
+ ${ozone.version}
+
+
+
+
+
+ include-hadoop-gcp
+
+ false
+
+
+
+ com.google.cloud.bigdataoss
+ gcs-connector
+ hadoop3-${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ util
+ ${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ util-hadoop
+ hadoop3-${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ gcsio
+ ${gcs.version}
+
+
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizer.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizer.java
new file mode 100644
index 000000000000..2feef8295596
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizer.java
@@ -0,0 +1,208 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.AccessPolicyProvider;
+import org.apache.nifi.authorization.AccessPolicyProviderInitializationContext;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.authorization.ManagedAuthorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProviderLookup;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.xml.processing.ProcessingException;
+import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
+import org.apache.nifi.xml.processing.transform.StandardTransformProvider;
+import org.apache.nifi.xml.processing.transform.TransformProvider;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+public class ManagedRangerAuthorizer extends RangerNiFiAuthorizer implements ManagedAuthorizer {
+ private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
+
+ private UserGroupProviderLookup userGroupProviderLookup;
+ private UserGroupProvider userGroupProvider;
+ private RangerBasePluginWithPolicies nifiPlugin;
+
+ @Override
+ public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+ userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
+
+ super.initialize(initializationContext);
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+ final String userGroupProviderKey = configurationContext.getProperty("User Group Provider").getValue();
+ userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey);
+
+ // ensure the desired access policy provider has a user group provider
+ if (userGroupProvider == null) {
+ throw new AuthorizerCreationException(String.format("Unable to locate configured User Group Provider: %s", userGroupProviderKey));
+ }
+
+ super.onConfigured(configurationContext);
+ }
+
+ @Override
+ protected RangerBasePluginWithPolicies createRangerBasePlugin(final String serviceType, final String appId) {
+ // override the method for creating the ranger base plugin so a user group provider can be specified
+ nifiPlugin = new RangerBasePluginWithPolicies(serviceType, appId, userGroupProvider);
+ return nifiPlugin;
+ }
+
+ @Override
+ public AccessPolicyProvider getAccessPolicyProvider() {
+ return new AccessPolicyProvider() {
+ @Override
+ public Set getAccessPolicies() throws AuthorizationAccessException {
+ return nifiPlugin.getAccessPolicies();
+ }
+
+ @Override
+ public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+ return nifiPlugin.getAccessPolicy(identifier);
+ }
+
+ @Override
+ public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
+ return nifiPlugin.getAccessPolicy(resourceIdentifier, action);
+ }
+
+ @Override
+ public UserGroupProvider getUserGroupProvider() {
+ return userGroupProvider;
+ }
+
+ @Override
+ public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void preDestruction() throws AuthorizerDestructionException {
+ }
+ };
+ }
+
+ @Override
+ public String getFingerprint() throws AuthorizationAccessException {
+ final StringWriter out = new StringWriter();
+ try {
+ // create the document
+ final StandardDocumentProvider documentProvider = new StandardDocumentProvider();
+ final Document document = documentProvider.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 TransformProvider transformProvider = new StandardTransformProvider();
+ transformProvider.transform(new DOMSource(document), new StreamResult(out));
+ } catch (final ProcessingException e) {
+ throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+ }
+
+ return out.toString();
+ }
+
+ @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 forciblyInheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
+ final String userGroupFingerprint = parseFingerprint(fingerprint);
+
+ if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+ ((ConfigurableUserGroupProvider) userGroupProvider).forciblyInheritFingerprint(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.");
+ }
+ }
+ }
+
+ private String parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
+ final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+
+ try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+ final StandardDocumentProvider documentProvider = new StandardDocumentProvider();
+ final Document document = documentProvider.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 (final ProcessingException | IOException e) {
+ throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+ }
+ }
+}
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerBasePluginWithPolicies.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerBasePluginWithPolicies.java
new file mode 100644
index 000000000000..9839c81866b3
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerBasePluginWithPolicies.java
@@ -0,0 +1,293 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.util.StringUtils;
+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 policy domain model.
+ */
+public class RangerBasePluginWithPolicies extends RangerBasePlugin {
+
+ private static final Logger logger = LoggerFactory.getLogger(RangerBasePluginWithPolicies.class);
+
+ private static final String WILDCARD_ASTERISK = "*";
+
+ private UserGroupProvider userGroupProvider;
+ private AtomicReference 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 ManagedRangerAuthorizer
+ }
+
+ @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 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 policiesByIdentifier = new HashMap<>();
+ final Map> policiesByResource = new HashMap<>();
+
+ logger.info("Converting Ranger ServicePolicies model into NiFi policy model for viewing purposes in NiFi 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 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 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 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 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 policiesByIdentifier;
+ private final Map> policiesByResource;
+ private final Set allPolicies;
+
+ private PolicyLookup() {
+ this(null, null);
+ }
+
+ private PolicyLookup(final Map policiesByIdentifier, final Map> policiesByResource) {
+ if (policiesByIdentifier == null) {
+ allPolicies = Collections.EMPTY_SET;
+ } else {
+ allPolicies = Collections.unmodifiableSet(new HashSet<>(policiesByIdentifier.values()));
+ }
+
+ this.policiesByIdentifier = policiesByIdentifier;
+ this.policiesByResource = policiesByResource;
+ }
+
+ private Set 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 policiesForResource = policiesByResource.get(resourceIdentifier);
+
+ if (policiesForResource != null) {
+ return policiesForResource.get(action);
+ }
+
+ return null;
+ }
+ }
+
+}
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerNiFiAuthorizer.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerNiFiAuthorizer.java
new file mode 100644
index 000000000000..1fed01efc631
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/java/org/apache/nifi/ranger/authorization/RangerNiFiAuthorizer.java
@@ -0,0 +1,309 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.nifi.authorization.AuthorizationAuditor;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.authorization.UserContextKeys;
+import org.apache.nifi.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.ranger.audit.model.AuthzAuditEvent;
+import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+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 java.io.File;
+import java.net.MalformedURLException;
+import java.text.NumberFormat;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Authorizer implementation that uses Apache Ranger to make authorization decisions.
+ */
+public class RangerNiFiAuthorizer implements Authorizer, AuthorizationAuditor {
+ private static final Logger logger = LoggerFactory.getLogger(RangerNiFiAuthorizer.class);
+
+ 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_SERVICE_TYPE_PROP = "Ranger Service Type";
+ static final String RANGER_APP_ID_PROP = "Ranger Application Id";
+ static final String RANGER_ADMIN_IDENTITY_PROP_PREFIX = "Ranger Admin Identity";
+ static final Pattern RANGER_ADMIN_IDENTITY_PATTERN = Pattern.compile(RANGER_ADMIN_IDENTITY_PROP_PREFIX + "\\s?\\S*");
+
+ static final String RANGER_NIFI_RESOURCE_NAME = "nifi-resource";
+ static final String DEFAULT_SERVICE_TYPE = "nifi";
+ static final String DEFAULT_APP_ID = "nifi";
+ static final String RESOURCES_RESOURCE = "/resources";
+ static final String HADOOP_SECURITY_AUTHENTICATION = "hadoop.security.authentication";
+ static final String KERBEROS_AUTHENTICATION = "kerberos";
+
+ private final Map resultLookup = new WeakHashMap<>();
+
+ private volatile RangerBasePluginWithPolicies nifiPlugin = null;
+ private volatile RangerDefaultAuditHandler defaultAuditHandler = null;
+ private volatile Set rangerAdminIdentity = null;
+ private volatile boolean rangerKerberosEnabled = false;
+ private volatile NiFiProperties nifiProperties;
+ private final NumberFormat numberFormat = NumberFormat.getInstance();
+
+ @Override
+ public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+ try {
+ if (nifiPlugin == null) {
+ logger.info("RangerNiFiAuthorizer(): initializing base plugin");
+
+ final String serviceType = getConfigValue(configurationContext, RANGER_SERVICE_TYPE_PROP, DEFAULT_SERVICE_TYPE);
+ final String appId = getConfigValue(configurationContext, RANGER_APP_ID_PROP, DEFAULT_APP_ID);
+
+ nifiPlugin = createRangerBasePlugin(serviceType, appId);
+
+ final RangerPluginConfig pluginConfig = nifiPlugin.getConfig();
+
+ final PropertyValue securityConfigValue = configurationContext.getProperty(RANGER_SECURITY_PATH_PROP);
+ addRequiredResource(RANGER_SECURITY_PATH_PROP, securityConfigValue, pluginConfig);
+
+ final PropertyValue auditConfigValue = configurationContext.getProperty(RANGER_AUDIT_PATH_PROP);
+ addRequiredResource(RANGER_AUDIT_PATH_PROP, auditConfigValue, pluginConfig);
+
+ final String rangerKerberosEnabledValue = getConfigValue(configurationContext, RANGER_KERBEROS_ENABLED_PROP, Boolean.FALSE.toString());
+ rangerKerberosEnabled = rangerKerberosEnabledValue.equals(Boolean.TRUE.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 principal and keytab, RangerAdminRESTClient will use Ranger's MiscUtil which
+ // will grab UserGroupInformation.getLoginUser() and call ugi.checkTGTAndReloginFromKeytab();
+ final String nifiPrincipal = nifiProperties.getKerberosServicePrincipal();
+ final String nifiKeytab = nifiProperties.getKerberosServiceKeytabLocation();
+
+ if (StringUtils.isBlank(nifiPrincipal) || StringUtils.isBlank(nifiKeytab)) {
+ throw new AuthorizerCreationException("Principal and Keytab must be provided when Kerberos is enabled");
+ }
+
+ UserGroupInformation.loginUserFromKeytab(nifiPrincipal.trim(), nifiKeytab.trim());
+ }
+
+ nifiPlugin.init();
+
+ defaultAuditHandler = new RangerDefaultAuditHandler();
+ rangerAdminIdentity = getConfigValues(configurationContext, RANGER_ADMIN_IDENTITY_PATTERN, null);
+
+ } else {
+ logger.info("RangerNiFiAuthorizer(): base plugin already initialized");
+ }
+ } catch (Throwable t) {
+ throw new AuthorizerCreationException("Error creating RangerBasePlugin", t);
+ }
+ }
+
+ protected RangerBasePluginWithPolicies createRangerBasePlugin(final String serviceType, final String appId) {
+ return new RangerBasePluginWithPolicies(serviceType, appId);
+ }
+
+ @Override
+ public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
+ final String identity = request.getIdentity();
+ final Set userGroups = request.getGroups();
+ final String resourceIdentifier = request.getResource().getIdentifier();
+
+ // if a ranger admin identity was provided, and it contains the identity making the request,
+ // and the request is to retrieve the resources, then allow it through
+ if (rangerAdminIdentity != null && rangerAdminIdentity.contains(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_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 long authStart = System.nanoTime();
+ final RangerAccessResult result = nifiPlugin.isAccessAllowed(rangerRequest);
+ final long authNanos = System.nanoTime() - authStart;
+ logger.debug("Performed authorization against Ranger for Resource ID {}, Identity {} in {} nanos", resourceIdentifier, identity, numberFormat.format(authNanos));
+
+ // 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 = nifiPlugin.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 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_RESOURCE_NAME);
+ event.setResourcePath(request.getRequestedResource().getIdentifier());
+
+ final long start = System.nanoTime();
+ defaultAuditHandler.logAuthzAudit(event);
+ final long nanos = System.nanoTime() - start;
+ logger.debug("Logged authorization audits to Ranger in {} nanos", numberFormat.format(nanos));
+ }
+ }
+
+ @Override
+ public void preDestruction() throws AuthorizerDestructionException {
+ if (nifiPlugin != null) {
+ try {
+ nifiPlugin.cleanup();
+ nifiPlugin = null;
+ } catch (Throwable t) {
+ throw new AuthorizerDestructionException("Error cleaning up RangerBasePlugin", t);
+ }
+ }
+ }
+
+ @AuthorizerContext
+ public void setNiFiProperties(final NiFiProperties properties) {
+ this.nifiProperties = 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
+ * @param configuration the RangerConfiguration instance to add the resource to
+ */
+ private void addRequiredResource(final String name, final PropertyValue resourceValue, final RangerConfiguration configuration) {
+ if (resourceValue == null || StringUtils.isBlank(resourceValue.getValue())) {
+ throw new AuthorizerCreationException(name + " must be specified.");
+ }
+
+ final File resourceFile = new File(resourceValue.getValue());
+ if (!resourceFile.exists() || !resourceFile.canRead()) {
+ throw new AuthorizerCreationException(resourceValue + " does not exist, or can not be read");
+ }
+
+ try {
+ configuration.addResource(resourceFile.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new AuthorizerCreationException("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;
+ }
+
+ private Set getConfigValues(final AuthorizerConfigurationContext context, final Pattern namePattern, final String defaultValue) {
+ final Set configValues = new HashSet<>();
+
+ for (Map.Entry entry : context.getProperties().entrySet()) {
+ Matcher matcher = namePattern.matcher(entry.getKey());
+ if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+ configValues.add(entry.getValue());
+ }
+ }
+
+ if (configValues.isEmpty() && (defaultValue != null)) {
+ configValues.add(defaultValue);
+ }
+
+ return configValues;
+ }
+}
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
new file mode 100755
index 000000000000..34d87976e8c1
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
@@ -0,0 +1,16 @@
+# 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.ranger.authorization.RangerNiFiAuthorizer
+org.apache.nifi.ranger.authorization.ManagedRangerAuthorizer
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizerTest.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizerTest.java
new file mode 100644
index 000000000000..7f8c17ce9058
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/ManagedRangerAuthorizerTest.java
@@ -0,0 +1,227 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProviderLookup;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.util.MockPropertyValue;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.io.File;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+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 ManagedRangerAuthorizerTest {
+
+ private static final String TENANT_FINGERPRINT =
+ ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
+
+ private static final String EMPTY_FINGERPRINT = ""
+ + ""
+ + ""
+ + "";
+
+ private static final String NON_EMPTY_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 final String serviceType = "nifi";
+ private final String appId = "nifiAppId";
+
+ @BeforeEach
+ public void setup() {
+ // 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(RangerNiFiAuthorizer.HADOOP_SECURITY_AUTHENTICATION, "simple");
+ UserGroupInformation.setConfiguration(securityConf);
+
+ assertFalse(UserGroupInformation.isSecurityEnabled());
+ }
+
+ @Test
+ public void testNonConfigurableFingerPrint() {
+ final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertEquals(EMPTY_FINGERPRINT, managedRangerAuthorizer.getFingerprint());
+ }
+
+ @Test
+ public void testConfigurableEmptyFingerPrint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+ when(userGroupProvider.getFingerprint()).thenReturn("");
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertEquals(EMPTY_FINGERPRINT, managedRangerAuthorizer.getFingerprint());
+ }
+
+ @Test
+ public void testConfigurableFingerPrint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+ when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertEquals(NON_EMPTY_FINGERPRINT, managedRangerAuthorizer.getFingerprint());
+ }
+
+ @Test
+ public void testInheritEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ managedRangerAuthorizer.inheritFingerprint(EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+ }
+
+ @Test
+ public void testInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertThrows(AuthorizationAccessException.class, () -> managedRangerAuthorizer.inheritFingerprint("not a valid fingerprint"));
+ }
+
+ @Test
+ public void testInheritNonEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ managedRangerAuthorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT);
+ }
+
+ @Test
+ public void testCheckInheritEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ managedRangerAuthorizer.checkInheritability(EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+ }
+
+ @Test
+ public void testCheckInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertThrows(AuthorizationAccessException.class, () -> managedRangerAuthorizer.checkInheritability("not a valid fingerprint"));
+ }
+
+ @Test
+ public void testCheckInheritNonEmptyFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ managedRangerAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
+
+ verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT);
+ }
+
+ @Test
+ public void testCheckInheritNonConfigurableUserGroupProvider() {
+ final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+ final ManagedRangerAuthorizer managedRangerAuthorizer = getStandardManagedAuthorizer(userGroupProvider);
+ assertThrows(UninheritableAuthorizationsException.class, () -> managedRangerAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT));
+ }
+
+ private ManagedRangerAuthorizer getStandardManagedAuthorizer(final UserGroupProvider userGroupProvider) {
+ final RangerBasePluginWithPolicies rangerBasePlugin = Mockito.mock(RangerBasePluginWithPolicies.class);
+
+ final RangerPluginConfig pluginConfig = new RangerPluginConfig(serviceType, null, appId, null, null, null);
+ when(rangerBasePlugin.getConfig()).thenReturn(pluginConfig);
+
+ final ManagedRangerAuthorizer managedAuthorizer = new MockManagedRangerAuthorizer(rangerBasePlugin);
+
+ final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+ when(configurationContext.getProperty(eq("User Group Provider"))).thenReturn(new MockPropertyValue("user-group-provider", null));
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SECURITY_PATH_PROP))).thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-security.xml"));
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_AUDIT_PATH_PROP))).thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-audit.xml"));
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_APP_ID_PROP))).thenReturn(new MockPropertyValue(appId));
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SERVICE_TYPE_PROP))).thenReturn(new MockPropertyValue(serviceType));
+
+ final UserGroupProviderLookup userGroupProviderLookup = mock(UserGroupProviderLookup.class);
+ when(userGroupProviderLookup.getUserGroupProvider("user-group-provider")).thenReturn(userGroupProvider);
+
+ final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+ when(initializationContext.getUserGroupProviderLookup()).thenReturn(userGroupProviderLookup);
+
+ managedAuthorizer.initialize(initializationContext);
+ managedAuthorizer.onConfigured(configurationContext);
+
+ return managedAuthorizer;
+ }
+
+ /**
+ * Extend ManagedRangerAuthorizer to inject a mock base plugin for testing.
+ */
+ private static class MockManagedRangerAuthorizer extends ManagedRangerAuthorizer {
+
+ RangerBasePluginWithPolicies mockRangerBasePlugin;
+
+ public MockManagedRangerAuthorizer(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;
+ }
+ }
+}
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerBasePluginWithPolicies.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerBasePluginWithPolicies.java
new file mode 100644
index 000000000000..833fd0fc9c6f
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerBasePluginWithPolicies.java
@@ -0,0 +1,550 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.UserAndGroups;
+import org.apache.nifi.authorization.UserGroupProvider;
+import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.ranger.plugin.model.RangerPolicy;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemAccess;
+import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyResource;
+import org.apache.ranger.plugin.model.RangerServiceDef;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.junit.jupiter.api.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.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TestRangerBasePluginWithPolicies {
+
+ @Test
+ public void testPoliciesWithoutUserGroupProvider() {
+ final String user1 = "user-1";
+ final String group1 = "group-1";
+
+ final String resourceIdentifier1 = "/resource-1";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new 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";
+ RangerPolicyResource resource2 = new RangerPolicyResource(resourceIdentifier2);
+
+ final Map policy2Resources = new HashMap<>();
+ policy2Resources.put(resourceIdentifier2, resource2);
+
+ final RangerPolicyItem policy2Item = new RangerPolicyItem();
+ policy2Item.setAccesses(Stream.of(new RangerPolicyItemAccess("READ"), new 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 policies = new ArrayList<>();
+ policies.add(policy1);
+ policies.add(policy2);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ pluginWithPolicies.setPolicies(servicePolicies);
+
+ // ensure the two ranger policies converted into 3 nifi access policies
+ final Set 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));
+ assertEquals(readResource1, 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));
+ assertEquals(readResource2, 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));
+ assertEquals(writeResource2, 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", "nifi");
+
+ 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";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new 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 policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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";
+ RangerPolicyResource resource1 = new RangerPolicyResource();
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+ final RangerPolicy policy1 = new RangerPolicy();
+ policy1.setResources(policy1Resources);
+ policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+ final List policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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 = "*";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+ final RangerPolicy policy1 = new RangerPolicy();
+ policy1.setResources(policy1Resources);
+ policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+ final List policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+ resource1.setIsExcludes(true);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+ final RangerPolicy policy1 = new RangerPolicy();
+ policy1.setResources(policy1Resources);
+ policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+ final List policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+ resource1.setIsRecursive(true);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new RangerPolicyItemAccess("WRITE")).collect(Collectors.toList()));
+
+ final RangerPolicy policy1 = new RangerPolicy();
+ policy1.setResources(policy1Resources);
+ policy1.setPolicyItems(Stream.of(policy1Item).collect(Collectors.toList()));
+
+ final List policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new RangerPolicyItemAccess("READ"), new 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 policies = new ArrayList<>();
+ policies.add(policy1);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi");
+ 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 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 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 getGroups() {
+ return Collections.EMPTY_SET;
+ }
+ };
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+ }
+
+ @Override
+ public void preDestruction() throws AuthorizerDestructionException {
+ }
+ };
+
+ final String resourceIdentifier1 = "/resource-1";
+ RangerPolicyResource resource1 = new RangerPolicyResource(resourceIdentifier1);
+
+ final Map policy1Resources = new HashMap<>();
+ policy1Resources.put(resourceIdentifier1, resource1);
+
+ final RangerPolicyItem policy1Item = new RangerPolicyItem();
+ policy1Item.setAccesses(Stream.of(new 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";
+ RangerPolicyResource resource2 = new RangerPolicyResource(resourceIdentifier2);
+
+ final Map policy2Resources = new HashMap<>();
+ policy2Resources.put(resourceIdentifier2, resource2);
+
+ final RangerPolicyItem policy2Item = new RangerPolicyItem();
+ policy2Item.setAccesses(Stream.of(new RangerPolicyItemAccess("READ"), new 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 policies = new ArrayList<>();
+ policies.add(policy1);
+ policies.add(policy2);
+
+ final RangerServiceDef serviceDef = new RangerServiceDef();
+ serviceDef.setName("nifi");
+
+ final ServicePolicies servicePolicies = new ServicePolicies();
+ servicePolicies.setPolicies(policies);
+ servicePolicies.setServiceDef(serviceDef);
+
+ // set all the policies in the plugin
+ final RangerBasePluginWithPolicies pluginWithPolicies = new RangerBasePluginWithPolicies("nifi", "nifi", userGroupProvider);
+ pluginWithPolicies.setPolicies(servicePolicies);
+
+ // ensure the two ranger policies converted into 3 nifi access policies
+ final Set 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));
+ assertEquals(readResource1, 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));
+ assertEquals(readResource2, 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));
+ assertEquals(writeResource2, 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-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerNiFiAuthorizer.java b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerNiFiAuthorizer.java
new file mode 100644
index 000000000000..bcfd8ddd0fff
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/java/org/apache/nifi/ranger/authorization/TestRangerNiFiAuthorizer.java
@@ -0,0 +1,572 @@
+/*
+ * 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.ranger.authorization;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.UserContextKeys;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.util.MockPropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+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.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class TestRangerNiFiAuthorizer {
+
+ private MockRangerNiFiAuthorizer authorizer;
+ private RangerBasePluginWithPolicies rangerBasePlugin;
+ private AuthorizerConfigurationContext configurationContext;
+ private NiFiProperties nifiProperties;
+
+ private final String serviceType = "nifi";
+ private final String appId = "nifiAppId";
+
+ private RangerAccessResult allowedResult;
+ private RangerAccessResult notAllowedResult;
+
+ private Map authorizersXmlContent = null;
+
+ @BeforeEach
+ public void setup() {
+ // 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(RangerNiFiAuthorizer.HADOOP_SECURITY_AUTHENTICATION, "simple");
+ UserGroupInformation.setConfiguration(securityConf);
+
+ // initialize the content of authorizers.xml in case tests added further entries to it
+ authorizersXmlContent = new HashMap<>();
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_SECURITY_PATH_PROP, "src/test/resources/ranger/ranger-nifi-security.xml");
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_AUDIT_PATH_PROP, "src/test/resources/ranger/ranger-nifi-audit.xml");
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_APP_ID_PROP, appId);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_SERVICE_TYPE_PROP, serviceType);
+ configurationContext = createMockConfigContext();
+ rangerBasePlugin = Mockito.mock(RangerBasePluginWithPolicies.class);
+
+ final RangerPluginConfig pluginConfig = new RangerPluginConfig(serviceType, null, appId, null, null, null);
+ when(rangerBasePlugin.getConfig()).thenReturn(pluginConfig);
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.onConfigured(configurationContext);
+
+ assertFalse(UserGroupInformation.isSecurityEnabled());
+
+ allowedResult = Mockito.mock(RangerAccessResult.class);
+ when(allowedResult.getIsAllowed()).thenReturn(true);
+
+ notAllowedResult = Mockito.mock(RangerAccessResult.class);
+ when(notAllowedResult.getIsAllowed()).thenReturn(false);
+ }
+
+ private AuthorizerConfigurationContext createMockConfigContext() {
+ AuthorizerConfigurationContext configurationContext = Mockito.mock(AuthorizerConfigurationContext.class);
+
+ for (Map.Entry entry : authorizersXmlContent.entrySet()) {
+ when(configurationContext.getProperty(eq(entry.getKey())))
+ .thenReturn(new MockPropertyValue(entry.getValue()));
+ }
+
+ when(configurationContext.getProperties()).thenReturn(authorizersXmlContent);
+
+ return configurationContext;
+ }
+
+ @Test
+ public void testOnConfigured() {
+ verify(rangerBasePlugin, times(1)).init();
+
+ assertEquals(appId, authorizer.mockRangerBasePlugin.getAppId());
+ assertEquals(serviceType, authorizer.mockRangerBasePlugin.getServiceType());
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutKeytab() {
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new MockPropertyValue("true"));
+
+ nifiProperties = Mockito.mock(NiFiProperties.class);
+ when(nifiProperties.getKerberosServicePrincipal()).thenReturn("");
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.setNiFiProperties(nifiProperties);
+
+ assertThrows(AuthorizerCreationException.class, () -> authorizer.onConfigured(configurationContext));
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutPrincipal() {
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new MockPropertyValue("true"));
+
+ nifiProperties = Mockito.mock(NiFiProperties.class);
+ when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn("");
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.setNiFiProperties(nifiProperties);
+
+ assertThrows(AuthorizerCreationException.class, () -> authorizer.onConfigured(configurationContext));
+ }
+
+ @Test
+ public void testKerberosEnabledWithoutKeytabOrPrincipal() {
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new MockPropertyValue("true"));
+
+ nifiProperties = Mockito.mock(NiFiProperties.class);
+ when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn("");
+ when(nifiProperties.getKerberosServicePrincipal()).thenReturn("");
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.setNiFiProperties(nifiProperties);
+
+ assertThrows(AuthorizerCreationException.class, () -> authorizer.onConfigured(configurationContext));
+ }
+
+ @Test
+ public void testKerberosEnabled() {
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_KERBEROS_ENABLED_PROP)))
+ .thenReturn(new MockPropertyValue("true"));
+
+ nifiProperties = Mockito.mock(NiFiProperties.class);
+ when(nifiProperties.getKerberosServiceKeytabLocation()).thenReturn("test");
+ when(nifiProperties.getKerberosServicePrincipal()).thenReturn("test");
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.setNiFiProperties(nifiProperties);
+
+ assertThrows(AuthorizerCreationException.class, () -> authorizer.onConfigured(configurationContext));
+ }
+
+ @Test
+ public void testApprovedWithDirectAccess() {
+ final String systemResource = "/system";
+ final RequestAction action = RequestAction.WRITE;
+ final String user = "admin";
+ final String clientIp = "192.168.1.1";
+
+ final Map 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(RangerNiFiAuthorizer.RANGER_NIFI_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 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(RangerNiFiAuthorizer.RANGER_NIFI_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 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(RangerNiFiAuthorizer.RANGER_NIFI_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)),
+ isNotNull())
+ ).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 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(RangerNiFiAuthorizer.RANGER_NIFI_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() {
+ final String acceptableIdentity = "ranger-admin";
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity);
+
+ final String requestIdentity = "ranger-admin";
+ runRangerAdminTest(RangerNiFiAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminApprovedMultipleAcceptableIdentities() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin2";
+ runRangerAdminTest(RangerNiFiAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminApprovedMultipleAcceptableIdentities2() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin3";
+ runRangerAdminTest(RangerNiFiAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminDenied() {
+ final String acceptableIdentity = "ranger-admin";
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity);
+
+ final String requestIdentity = "ranger-admin";
+ runRangerAdminTest("/flow", requestIdentity, AuthorizationResult.denied().getResult());
+ }
+
+ @Test
+ public void testRangerAdminDeniedMultipleAcceptableIdentities() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerNiFiAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin4";
+ runRangerAdminTest(RangerNiFiAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.denied().getResult());
+ }
+
+ private void runRangerAdminTest(final String resourceIdentifier, final String requestIdentity, final AuthorizationResult.Result expectedResult) {
+ configurationContext = createMockConfigContext();
+
+ rangerBasePlugin = Mockito.mock(RangerBasePluginWithPolicies.class);
+
+ final RangerPluginConfig pluginConfig = new RangerPluginConfig(serviceType, null, appId, null, null, null);
+ when(rangerBasePlugin.getConfig()).thenReturn(pluginConfig);
+
+ authorizer = new MockRangerNiFiAuthorizer(rangerBasePlugin);
+ authorizer.onConfigured(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(requestIdentity)
+ .resourceContext(new HashMap<>())
+ .accessAttempt(true)
+ .anonymous(false)
+ .build();
+
+ // the expected Ranger resource and request that are created
+ final RangerAccessResourceImpl resource = new RangerAccessResourceImpl();
+ resource.setValue(RangerNiFiAuthorizer.RANGER_NIFI_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
+ @Disabled
+ public void testIntegration() {
+ final AuthorizerInitializationContext initializationContext = Mockito.mock(AuthorizerInitializationContext.class);
+ final AuthorizerConfigurationContext configurationContext = Mockito.mock(AuthorizerConfigurationContext.class);
+
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_SECURITY_PATH_PROP)))
+ .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-security.xml"));
+
+ when(configurationContext.getProperty(eq(RangerNiFiAuthorizer.RANGER_AUDIT_PATH_PROP)))
+ .thenReturn(new MockPropertyValue("src/test/resources/ranger/ranger-nifi-audit.xml"));
+
+ Authorizer authorizer = new RangerNiFiAuthorizer();
+ try {
+ authorizer.initialize(initializationContext);
+ authorizer.onConfigured(configurationContext);
+
+ final AuthorizationRequest request = new AuthorizationRequest.Builder()
+ .resource(new Resource() {
+ @Override
+ public String getIdentifier() {
+ return "/system";
+ }
+
+ @Override
+ public String getName() {
+ return "/system";
+ }
+
+ @Override
+ public String getSafeDescription() {
+ return "system";
+ }
+ })
+ .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 RangerNiFiAuthorizer to inject a mock base plugin for testing.
+ */
+ private static class MockRangerNiFiAuthorizer extends RangerNiFiAuthorizer {
+
+ RangerBasePluginWithPolicies mockRangerBasePlugin;
+
+ public MockRangerNiFiAuthorizer(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;
+
+ public 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 {
+
+ private final RangerAccessRequest request;
+
+ public RangerAccessRequestMatcher(RangerAccessRequest request) {
+ this.request = request;
+ }
+
+ @Override
+ public boolean matches(RangerAccessRequest argument) {
+ if (argument == null) {
+ return false;
+ }
+
+ final boolean clientIpsMatch = (argument.getClientIPAddress() == null && request.getClientIPAddress() == null)
+ || (argument.getClientIPAddress() != null && request.getClientIPAddress() != null && argument.getClientIPAddress().equals(request.getClientIPAddress()));
+
+ return argument.getResource().equals(request.getResource())
+ && argument.getAccessType().equals(request.getAccessType())
+ && argument.getAction().equals(request.getAction())
+ && argument.getUser().equals(request.getUser())
+ && clientIpsMatch;
+ }
+ }
+
+}
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/authorizers.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/authorizers.xml
new file mode 100644
index 000000000000..ef87a8c51cdf
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/authorizers.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ ranger-provider
+ org.apache.nifi.ranger.authorization.RangerNiFiAuthorizer
+ src/test/resources/ranger/ranger-nifi-audit.xml
+ src/test/resources/ranger/ranger-nifi-security.xml
+ nifi
+ nifi
+ CN=ranger-admin, OU=Apache Ranger, O=Apache, L=Santa Monica, ST=CA, C=US
+ false
+
+
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/krb5.conf b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/krb5.conf
new file mode 100644
index 000000000000..0e3f142a9b48
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-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-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/core-site.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/core-site.xml
new file mode 100644
index 000000000000..d590a5039c04
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/core-site.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ hadoop.security.authentication
+ simple
+
+
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-audit.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-audit.xml
new file mode 100644
index 000000000000..3dbd576334b9
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-audit.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+ xasecure.audit.is.enabled
+ true
+
+
+
+
+ xasecure.audit.destination.db
+ false
+
+
+
+ xasecure.audit.destination.db.jdbc.driver
+ com.mysql.jdbc.Driver
+
+
+
+ xasecure.audit.destination.db.jdbc.url
+ jdbc:mysql://localhost/ranger_audit
+
+
+
+ xasecure.audit.destination.db.password
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.user
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.batch.filespool.dir
+ /tmp/audit/db/spool
+
+
+
+
+
+ xasecure.audit.destination.hdfs
+ false
+
+
+
+ xasecure.audit.destination.hdfs.dir
+ hdfs://localhost:8020/ranger/audit
+
+
+
+ xasecure.audit.destination.hdfs.batch.filespool.dir
+ /tmp/audit/hdfs/spool
+
+
+
+
+
+ xasecure.audit.destination.log4j
+ false
+
+
+
+ xasecure.audit.destination.log4j.logger
+ ranger_audit_logger
+
+
+
+
+ xasecure.audit.destination.solr
+ true
+
+
+
+ xasecure.audit.destination.solr.batch.filespool.dir
+ /tmp/audit/solr/spool
+
+
+
+ xasecure.audit.destination.solr.urls
+ http://localhost:6083/solr/ranger_audits
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-security.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-security.xml
new file mode 100644
index 000000000000..b371dcc8436a
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-nifi-security.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+ ranger.plugin.nifi.policy.rest.url
+ http://localhost:6080
+
+ URL to Ranger Admin
+
+
+
+
+ ranger.plugin.nifi.service.name
+ nifi
+
+ Name of the Ranger service containing policies for this nifi instance
+
+
+
+
+ ranger.plugin.nifi.policy.source.impl
+ org.apache.ranger.admin.client.RangerAdminRESTClient
+
+ Class to retrieve policies from the source
+
+
+
+
+ ranger.plugin.nifi.policy.rest.ssl.config.file
+ ranger-policymgr-ssl.xml
+
+ Path to the file containing SSL details to contact Ranger Admin
+
+
+
+
+ ranger.plugin.nifi.policy.pollIntervalMs
+ 30000
+
+ How often to poll for changes in policies?
+
+
+
+
+ ranger.plugin.nifi.policy.cache.dir
+ /tmp
+
+ Directory where Ranger policies are cached after successful retrieval from the source
+
+
+
+
+ ranger.plugin.nifi.policy.rest.client.connection.timeoutMs
+ 120000
+
+ RangerRestClient Connection Timeout in Milli Seconds
+
+
+
+
+ ranger.plugin.nifi.policy.rest.client.read.timeoutMs
+ 30000
+
+ RangerRestClient read Timeout in Milli Seconds
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
new file mode 100644
index 000000000000..a6e05747a3d5
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ xasecure.policymgr.clientssl.keystore
+
+
+ Java Keystore files
+
+
+
+ xasecure.policymgr.clientssl.keystore.password
+ none
+
+ password for keystore
+
+
+
+ xasecure.policymgr.clientssl.truststore
+
+
+ java truststore file
+
+
+
+ xasecure.policymgr.clientssl.truststore.password
+ none
+
+ java truststore password
+
+
+
+ xasecure.policymgr.clientssl.keystore.credential.file
+
+
+ java keystore credential file
+
+
+
+ xasecure.policymgr.clientssl.truststore.credential.file
+
+
+ java truststore credential file
+
+
+
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/pom.xml b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/pom.xml
new file mode 100644
index 000000000000..22448804d547
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/pom.xml
@@ -0,0 +1,75 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.nifi
+ nifi-ranger-bundle
+ 2.10.0.1
+
+
+ nifi-ranger-resources
+ jar
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+ org.apache.ranger
+ credentialbuilder
+ ${ranger.version}
+
+
+ commons-configuration
+ commons-configuration
+
+
+ log4j
+ log4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.apache.commons
+ commons-configuration2
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/src/main/resources/scripts/ranger_credential_helper.py b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/src/main/resources/scripts/ranger_credential_helper.py
new file mode 100644
index 000000000000..bda3b89d9bfe
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/nifi-ranger-resources/src/main/resources/scripts/ranger_credential_helper.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python3
+# 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.
+
+import os
+import argparse
+import getpass
+import sys
+
+from subprocess import Popen, PIPE
+
+if os.getenv('JAVA_HOME') is None:
+ print("[W] ---------- JAVA_HOME environment property not defined, using java in path. ----------")
+ JAVA_BIN = 'java'
+else:
+ JAVA_BIN = os.path.join(os.getenv('JAVA_HOME'), 'bin', 'java')
+print(f"Using Java:{JAVA_BIN}")
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Manage Ranger JCEKS credential aliases.")
+ parser.add_argument("-l", "--libpath", dest="library_path",
+ help="Path to folder where credential libs are present")
+ parser.add_argument("-f", "--file", dest="jceks_file_path", help="Path to jceks file to use")
+ parser.add_argument("-k", "--key", dest="key", help="Key to use")
+ parser.add_argument("-c", "--create", dest="create", help="Add a new alias")
+ args = parser.parse_args()
+
+ value = ''
+ if args.create:
+ value = getpass.getpass("Value: ") if sys.stdin.isatty() else sys.stdin.readline().rstrip("\n")
+ getorcreate = 'create'
+ else:
+ getorcreate = 'get'
+ call_keystore(args.library_path, args.jceks_file_path, args.key, value, getorcreate)
+
+
+def call_keystore(libpath, filepath, aliasKey, aliasValue='', getorcreate='get'):
+ finalLibPath = libpath.replace('\\', '/').replace('//', '/')
+ finalFilePath = 'jceks://file/' + filepath.replace('\\', '/').replace('//', '/')
+
+ if getorcreate == 'create':
+ commandtorun = [JAVA_BIN, '-cp', finalLibPath, 'org.apache.ranger.credentialapi.buildks',
+ 'create', aliasKey, '-value', aliasValue, '-provider', finalFilePath]
+ p = Popen(commandtorun, stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True)
+ output, error = p.communicate()
+ if p.returncode == 0:
+ print(f"Alias {aliasKey} created successfully!")
+ else:
+ print(f"Error creating Alias!! Error: {error.strip()}")
+
+ elif getorcreate == 'get':
+ commandtorun = [JAVA_BIN, '-cp', finalLibPath, 'org.apache.ranger.credentialapi.buildks',
+ 'get', aliasKey, '-provider', finalFilePath]
+ p = Popen(commandtorun, stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True)
+ output, error = p.communicate()
+ if p.returncode == 0:
+ print(f"Alias : {aliasKey} Value : {output.strip()}")
+ else:
+ print(f"Error getting value!! Error: {error.strip()}")
+
+ else:
+ print('Invalid Arguments!!')
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/nifi-extension-bundles/nifi-ranger-bundle/pom.xml b/nifi-extension-bundles/nifi-ranger-bundle/pom.xml
new file mode 100644
index 000000000000..bfdae6a8a6bf
--- /dev/null
+++ b/nifi-extension-bundles/nifi-ranger-bundle/pom.xml
@@ -0,0 +1,136 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.nifi
+ nifi-extension-bundles
+ 2.10.0.1
+
+
+ nifi-ranger-bundle
+ pom
+
+
+ nifi-ranger-plugin
+ nifi-ranger-nar
+ nifi-ranger-resources
+
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-client
+ 9.4.58.v20250814
+
+
+ org.eclipse.jetty
+ jetty-http
+ 9.4.58.v20250814
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-alpn-client
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-util-ajax
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-alpn-java-client
+ ${ranger.jetty.version}
+
+
+
+ commons-beanutils
+ commons-beanutils
+ 1.9.4
+
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${hadoop.version}
+
+
+ org.apache.solr
+ solr-solrj
+ ${ranger.solr.version}
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ ${nimbus-jose-jwt.version}
+
+
+ org.apache.hadoop
+ hadoop-auth
+ ${hadoop.version}
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.codehaus.jettison
+ jettison
+ 1.5.4
+
+
+ org.apache.nifi
+ nifi-deprecation-log
+ 2.10.0.1
+
+
+ org.apache.nifi
+ nifi-framework-api
+ 2.10.0.1
+
+
+
+
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/GpfidstLoadConfig.java b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/GpfidstLoadConfig.java
index 7223443efe82..e2d3e48364bb 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/GpfidstLoadConfig.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/GpfidstLoadConfig.java
@@ -42,7 +42,9 @@ public long getGpfdistStreamBufferEnqueueTimeoutMs() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GpfidstLoadConfig that = (GpfidstLoadConfig) o;
return asyncContextTimeoutMs == that.asyncContextTimeoutMs
&& gpfdistSegmentStreamBufferSize == that.gpfdistSegmentStreamBufferSize
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorId.java b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorId.java
index 9fa6fff83cf3..3a57f612b347 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorId.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorId.java
@@ -40,7 +40,9 @@ public int getSegmentId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
RecordProcessorId that = (RecordProcessorId) o;
return segmentId == that.segmentId && Objects.equals(sinkId, that.sinkId) && Objects.equals(txId, that.txId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorLoadingResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorLoadingResult.java
index 2f7eca90d2e2..1629ae91b2d8 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorLoadingResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/metadata/RecordProcessorLoadingResult.java
@@ -49,7 +49,9 @@ public void incrementRecordBytes(long processedBytes) {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
RecordProcessorLoadingResult that = (RecordProcessorLoadingResult) o;
return recordCount == that.recordCount && recordBytes == that.recordBytes && Objects.equals(recordProcessorId, that.recordProcessorId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/service/LoadResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/service/LoadResult.java
index 2aaecc573214..6b5898686c6f 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/service/LoadResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-gpfdist-service-api/src/main/java/org/apache/nifi/gpfdist/service/LoadResult.java
@@ -53,7 +53,9 @@ public boolean isSuccess() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
LoadResult result = (LoadResult) o;
return Objects.equals(loadId, result.loadId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/StandardGpfdistService.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/StandardGpfdistService.java
index 056f91ed51ce..7f60b14c0169 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/StandardGpfdistService.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/StandardGpfdistService.java
@@ -281,6 +281,7 @@ public TransferDataQueryExecutor getCreateWriteExternalTableQueryExecutor() {
return createWriteExternalTableQueryExecutor;
}
+ @Override
public TransferDataQueryExecutor getInsertDataIntoTargetTableQueryExecutor() {
return insertDataIntoTargetTableQueryExecutor;
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/cluster/ClusterStateNodeIndexService.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/cluster/ClusterStateNodeIndexService.java
index def942ef63c1..73c572e018fc 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/cluster/ClusterStateNodeIndexService.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/cluster/ClusterStateNodeIndexService.java
@@ -62,7 +62,7 @@ public int getNodeIndex() throws Exception {
}
private Set getHosts(String hostsStr) {
- if (hostsStr == null || hostsStr.trim().isEmpty()) {
+ if (hostsStr == null || hostsStr.isEmpty()) {
throw new IllegalStateException("Cluster hosts state is empty when getting node index for " + hostName);
}
final Set hosts = Arrays.stream(hostsStr.split(","))
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/context/GpfdistContextId.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/context/GpfdistContextId.java
index 4a1925578660..0881dc86fc40 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/context/GpfdistContextId.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/context/GpfdistContextId.java
@@ -34,7 +34,9 @@ public String getId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GpfdistContextId contextId = (GpfdistContextId) o;
return Objects.equals(id, contextId.id);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/greengage/model/GreengageColumnDescription.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/greengage/model/GreengageColumnDescription.java
index 8c6c8fe14400..00b84a4a9caf 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/greengage/model/GreengageColumnDescription.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/greengage/model/GreengageColumnDescription.java
@@ -59,7 +59,9 @@ public boolean isNullable() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GreengageColumnDescription that = (GreengageColumnDescription) o;
return required == that.required && Objects.equals(columnName, that.columnName) && Objects.equals(dataType, that.dataType);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/context/WriteContext.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/context/WriteContext.java
index f4ac6dd13f36..e340c6058b52 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/context/WriteContext.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/context/WriteContext.java
@@ -35,8 +35,8 @@
import java.util.stream.Collectors;
public class WriteContext implements Context {
- private final ConcurrentHashMap externalTableLocksBySink = new ConcurrentHashMap<>();
- private final ConcurrentHashMap externalTableCreatedBySink = new ConcurrentHashMap<>();
+ private final Map externalTableLocksBySink = new ConcurrentHashMap<>();
+ private final Map externalTableCreatedBySink = new ConcurrentHashMap<>();
private final ContextId contextId;
private final Map metadataMap;
private final Map recordSinkMap;
@@ -134,6 +134,15 @@ public void close() {
}
}
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ WriteContext that = (WriteContext) o;
+ return Objects.equals(contextId, that.contextId);
+ }
+
@Override
public int hashCode() {
return Objects.hashCode(contextId);
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingResult.java
index 2eafcf686b58..608440ca2488 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingResult.java
@@ -26,9 +26,6 @@ public class LoadingResult {
private final AtomicReference error = new AtomicReference<>();
private final Map segmentResults = new ConcurrentHashMap<>();
- public LoadingResult() {
- }
-
public AtomicInteger getSegmentsCount() {
return segmentsCount;
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingSegmentResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingSegmentResult.java
index f5d2630824eb..0c18513cb54f 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingSegmentResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/metadata/LoadingSegmentResult.java
@@ -35,7 +35,9 @@ public int getSegmentId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
LoadingSegmentResult that = (LoadingSegmentResult) o;
return segmentId == that.segmentId && loadedRecords == that.loadedRecords && loadedBytes == that.loadedBytes;
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistRecordSink.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistRecordSink.java
index 8ac95ee97d93..906687832b5a 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistRecordSink.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistRecordSink.java
@@ -296,7 +296,9 @@ public int hashCode() {
@Override
public boolean equals(Object o) {
- if (!(o instanceof GpfdistRecordSink)) return false;
+ if (!(o instanceof GpfdistRecordSink)) {
+ return false;
+ }
return Objects.equals(sinkId, ((GpfdistRecordSink) o).sinkId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistSegmentStream.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistSegmentStream.java
index 0838eb209778..c54fae3c0653 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistSegmentStream.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/load/process/GpfdistSegmentStream.java
@@ -19,6 +19,7 @@
import org.apache.nifi.logging.ComponentLog;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -31,7 +32,7 @@ public class GpfdistSegmentStream {
private final RecordProcessorId recordProcessorId;
private final int avgGpfdistPacketSize;
private final int capacity;
- private final ArrayBlockingQueue queue;
+ private final BlockingQueue queue;
private final long enqueueTimeoutMs;
private final ComponentLog logger;
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/CsvFormatConfig.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/CsvFormatConfig.java
index 9c690f806cee..da865fe8c99e 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/CsvFormatConfig.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/CsvFormatConfig.java
@@ -35,9 +35,6 @@ public class CsvFormatConfig implements DataFormatConfig {
.setSkipHeaderRecord(true)
.build();
- public CsvFormatConfig() {
- }
-
public CSVFormat getCsvFormat() {
return GREENGAGE_CSV_FORMAT;
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/DefaultExternalTableFormat.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/DefaultExternalTableFormat.java
index 7fa20daa9d9e..19a95e091698 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/DefaultExternalTableFormat.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/metadata/DefaultExternalTableFormat.java
@@ -59,7 +59,9 @@ public DataFormat getDataFormat() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
DefaultExternalTableFormat that = (DefaultExternalTableFormat) o;
return Objects.equals(delimiter, that.delimiter)
&& Objects.equals(encoding, that.encoding)
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/deserialization/AbstractGreengageCSVRecordReader.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/deserialization/AbstractGreengageCSVRecordReader.java
index 89016b1f94a4..2aaa9cba6dcf 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/deserialization/AbstractGreengageCSVRecordReader.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/deserialization/AbstractGreengageCSVRecordReader.java
@@ -37,7 +37,7 @@
import static org.apache.nifi.gpfdist.service.util.GreengageColumnDataTypeConverter.parseArray;
import static org.apache.nifi.gpfdist.service.util.GreengageColumnDataTypeConverter.parseMap;
-abstract public class AbstractGreengageCSVRecordReader implements RecordReader {
+public abstract class AbstractGreengageCSVRecordReader implements RecordReader {
private final boolean trimDoubleQuote;
protected final String dateFormat;
protected final String timeFormat;
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/GreengageChunkId.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/GreengageChunkId.java
index fa01c8ba9b9a..c8bb17da9d0d 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/GreengageChunkId.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/GreengageChunkId.java
@@ -34,7 +34,9 @@ public int getSegmentId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GreengageChunkId that = (GreengageChunkId) o;
return segmentId == that.segmentId && Objects.equals(transactionId, that.transactionId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessingChunkId.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessingChunkId.java
index 2e1e340025f2..aeb1783427cc 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessingChunkId.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessingChunkId.java
@@ -40,7 +40,9 @@ public int getSegmentId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
ProcessingChunkId that = (ProcessingChunkId) o;
return segmentId == that.segmentId && Objects.equals(processorTaskId, that.processorTaskId) && Objects.equals(txId, that.txId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessorTaskResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessorTaskResult.java
index 0a54f9aa9574..b987f9f5e77f 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessorTaskResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/ProcessorTaskResult.java
@@ -79,7 +79,9 @@ public static ProcessorTaskResult aggregate(String processorTaskId,
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
ProcessorTaskResult that = (ProcessorTaskResult) o;
return Objects.equals(processorTaskId, that.processorTaskId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/SegmentResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/SegmentResult.java
index ac4567595f8d..b6f5f92f1823 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/SegmentResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/SegmentResult.java
@@ -34,7 +34,9 @@ public SegmentResult(int segmentId,
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
SegmentResult that = (SegmentResult) o;
return segmentId == that.segmentId && Objects.equals(transactions, that.transactions);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/TransactionResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/TransactionResult.java
index 4b06a65a14ba..d6bd9d969ca2 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/TransactionResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/TransactionResult.java
@@ -29,7 +29,9 @@ public TransactionResult(String txId, long recordsCount) {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
TransactionResult that = (TransactionResult) o;
return recordsCount == that.recordsCount && Objects.equals(txId, that.txId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/UnloadingResult.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/UnloadingResult.java
index 597dfc8c95d5..2e8d60240fce 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/UnloadingResult.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/dto/UnloadingResult.java
@@ -49,7 +49,9 @@ public void increment() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
UnloadingResult that = (UnloadingResult) o;
return Objects.equals(chunkId, that.chunkId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistChunkRequestProcessor.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistChunkRequestProcessor.java
index 4ba17d34fc16..d306fb6c5e01 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistChunkRequestProcessor.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistChunkRequestProcessor.java
@@ -74,7 +74,9 @@ public GreengageChunkId getChunkId() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GpfdistChunkRequestProcessor that = (GpfdistChunkRequestProcessor) o;
return Objects.equals(chunkId, that.chunkId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistCsvDataProcessor.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistCsvDataProcessor.java
index 0f7aa85a875e..9014f2eaf102 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistCsvDataProcessor.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistCsvDataProcessor.java
@@ -86,7 +86,9 @@ public UnloadingResult getResult() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GpfdistCsvDataProcessor that = (GpfdistCsvDataProcessor) o;
return Objects.equals(schema, that.schema)
&& Objects.equals(dataTypes, that.dataTypes)
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistRecordProcessingService.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistRecordProcessingService.java
index d39f3aef6482..34482bf4836c 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistRecordProcessingService.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/GpfdistRecordProcessingService.java
@@ -23,6 +23,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -32,7 +33,7 @@ public class GpfdistRecordProcessingService
implements RecordProcessingService {
private static final int MINIMUM_RECORDS_BUFFER_SIZE = 1000;
private final String processorTaskId;
- private final ArrayBlockingQueue queue;
+ private final BlockingQueue queue;
private final AtomicBoolean isStopped = new AtomicBoolean(false);
private final Map segmentDataProcessors = new ConcurrentHashMap<>();
private final ComponentLog logger;
@@ -129,7 +130,9 @@ public Collection getResult() {
@Override
public boolean equals(Object o) {
- if (o == null || getClass() != o.getClass()) return false;
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
GpfdistRecordProcessingService that = (GpfdistRecordProcessingService) o;
return Objects.equals(processorTaskId, that.processorTaskId);
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/ProcessorTaskManager.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/ProcessorTaskManager.java
index 707b92d4efc5..3bfdd27223e1 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/ProcessorTaskManager.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/unload/process/ProcessorTaskManager.java
@@ -15,13 +15,14 @@
import org.apache.nifi.logging.ComponentLog;
+import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ProcessorTaskManager {
private final int globalParallelFactor;
private final ComponentLog logger;
- private final ConcurrentLinkedQueue processorTasksQueue;
+ private final Queue processorTasksQueue;
public ProcessorTaskManager(int nodeIndex,
int nodeCount,
@@ -66,7 +67,7 @@ public void release(ProcessorTask task) {
}
}
- public ConcurrentLinkedQueue getProcessorTask() {
+ public Queue getProcessorTask() {
return processorTasksQueue;
}
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/util/GreengageUtil.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/util/GreengageUtil.java
index ba89a402d6b0..38da1ab56e9b 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/util/GreengageUtil.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/main/java/org/apache/nifi/gpfdist/service/util/GreengageUtil.java
@@ -54,7 +54,7 @@ public final class GreengageUtil {
SUPPORTED_MAX_VALUE_TYPES.stream()
.map(e -> e.name().toLowerCase())
.collect(Collectors.joining(", "));
- private final static Map TYPE_OID_TO_JDBC_TYPE_MAP = Map.ofEntries(
+ private static final Map TYPE_OID_TO_JDBC_TYPE_MAP = Map.ofEntries(
Map.entry(Oid.INT2, Types.SMALLINT),
Map.entry(Oid.INT4, Types.INTEGER),
Map.entry(Oid.OID, Types.BIGINT),
diff --git a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/test/java/org/apache/nifi/gpfdist/service/load/serialization/csv/CsvRecordSerializationServiceTest.java b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/test/java/org/apache/nifi/gpfdist/service/load/serialization/csv/CsvRecordSerializationServiceTest.java
index 123aed91ed4e..500b85a7fbbf 100644
--- a/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/test/java/org/apache/nifi/gpfdist/service/load/serialization/csv/CsvRecordSerializationServiceTest.java
+++ b/nifi-extension-bundles/nifi-standard-services/nifi-greengage-service-bundle/nifi-gpfdist-service/src/test/java/org/apache/nifi/gpfdist/service/load/serialization/csv/CsvRecordSerializationServiceTest.java
@@ -154,55 +154,56 @@ void testSerializeAllDataTypes() {
Timestamp timestampField = new Timestamp(1740027473375L);
Timestamp zonedTimestampField = new Timestamp(1740002273375L);
+ Map recordValues1 = Map.ofEntries(
+ Map.entry(fieldNames.get(0), 1),
+ Map.entry(fieldNames.get(1), 2478701872L),
+ Map.entry(fieldNames.get(2), false),
+ Map.entry(fieldNames.get(3), true),
+ Map.entry(fieldNames.get(4), new Byte[]{-48, 120}),
+ Map.entry(fieldNames.get(5), "tt"),
+ Map.entry(fieldNames.get(6), "c4ca4238a0"),
+ Map.entry(fieldNames.get(7), "edc8acddc2e9a0a6aec79ddd681c75ac"),
+ Map.entry(fieldNames.get(8), dateField),
+ Map.entry(fieldNames.get(9), 0.557235836982727),
+ Map.entry(fieldNames.get(10), 6.559277),
+ Map.entry(fieldNames.get(11), "{\"a\": \"b\"}"),
+ Map.entry(fieldNames.get(12), 45.51123),
+ Map.entry(fieldNames.get(13), 10.3),
+ Map.entry(fieldNames.get(14), 15),
+ Map.entry(fieldNames.get(15), 5000),
+ Map.entry(fieldNames.get(16), timeField),
+ Map.entry(fieldNames.get(17), timestampField),
+ Map.entry(fieldNames.get(18), zonedTimestampField),
+ Map.entry(fieldNames.get(19), "c2142fe5-e305-42ab-8b95-598567e9ea86"),
+ Map.entry(fieldNames.get(20), "{val, val}"),
+ Map.entry(fieldNames.get(21), "{ISBN-13=978-1449370000, weight=11.2 ounces, paperback=243, publisher=postgresqltutorial.com, language=English}"));
+
+ Map recordValues2 = new HashMap<>();
+ recordValues2.put(fieldNames.get(0), 1);
+ recordValues2.put(fieldNames.get(0), null);
+ recordValues2.put(fieldNames.get(2), null);
+ recordValues2.put(fieldNames.get(3), null);
+ recordValues2.put(fieldNames.get(4), null);
+ recordValues2.put(fieldNames.get(5), null);
+ recordValues2.put(fieldNames.get(6), null);
+ recordValues2.put(fieldNames.get(7), null);
+ recordValues2.put(fieldNames.get(8), null);
+ recordValues2.put(fieldNames.get(9), null);
+ recordValues2.put(fieldNames.get(10), null);
+ recordValues2.put(fieldNames.get(11), null);
+ recordValues2.put(fieldNames.get(12), null);
+ recordValues2.put(fieldNames.get(13), null);
+ recordValues2.put(fieldNames.get(14), null);
+ recordValues2.put(fieldNames.get(15), null);
+ recordValues2.put(fieldNames.get(16), null);
+ recordValues2.put(fieldNames.get(17), null);
+ recordValues2.put(fieldNames.get(18), null);
+ recordValues2.put(fieldNames.get(19), null);
+ recordValues2.put(fieldNames.get(20), null);
+ recordValues2.put(fieldNames.get(21), null);
List records = List.of(
- new MapRecord(recordSchema, new HashMap<>() {{
- put(fieldNames.get(0), 1);
- put(fieldNames.get(1), 2478701872L);
- put(fieldNames.get(2), false);
- put(fieldNames.get(3), true);
- put(fieldNames.get(4), new Byte[]{-48, 120});
- put(fieldNames.get(5), "tt");
- put(fieldNames.get(6), "c4ca4238a0");
- put(fieldNames.get(7), "edc8acddc2e9a0a6aec79ddd681c75ac");
- put(fieldNames.get(8), dateField);
- put(fieldNames.get(9), 0.557235836982727);
- put(fieldNames.get(10), 6.559277);
- put(fieldNames.get(11), "{\"a\": \"b\"}");
- put(fieldNames.get(12), 45.51123);
- put(fieldNames.get(13), 10.3);
- put(fieldNames.get(14), 15);
- put(fieldNames.get(15), 5000);
- put(fieldNames.get(16), timeField);
- put(fieldNames.get(17), timestampField);
- put(fieldNames.get(18), zonedTimestampField);
- put(fieldNames.get(19), "c2142fe5-e305-42ab-8b95-598567e9ea86");
- put(fieldNames.get(20), "{val, val}");
- put(fieldNames.get(21), "{ISBN-13=978-1449370000, weight=11.2 ounces, paperback=243, publisher=postgresqltutorial.com, language=English}");
- }}, true, false),
- new MapRecord(recordSchema, new HashMap<>() {{
- put(fieldNames.get(0), 1);
- put(fieldNames.get(0), null);
- put(fieldNames.get(2), null);
- put(fieldNames.get(3), null);
- put(fieldNames.get(4), null);
- put(fieldNames.get(5), null);
- put(fieldNames.get(6), null);
- put(fieldNames.get(7), null);
- put(fieldNames.get(8), null);
- put(fieldNames.get(9), null);
- put(fieldNames.get(10), null);
- put(fieldNames.get(11), null);
- put(fieldNames.get(12), null);
- put(fieldNames.get(13), null);
- put(fieldNames.get(14), null);
- put(fieldNames.get(15), null);
- put(fieldNames.get(16), null);
- put(fieldNames.get(17), null);
- put(fieldNames.get(18), null);
- put(fieldNames.get(19), null);
- put(fieldNames.get(20), null);
- put(fieldNames.get(21), null);
- }}, true, false));
+ new MapRecord(recordSchema, recordValues1, true, false),
+ new MapRecord(recordSchema, recordValues2, true, false));
serializationService = new CsvRecordSerializationService(recordSchema, columns, csvFormatConfig, logger);
records.forEach(r -> serializationService.append(r));
@@ -439,4 +440,4 @@ private String toString(Date date, String format) {
return DateTimeFormatter.ofPattern(format)
.format(zonedDateTime);
}
-}
\ No newline at end of file
+}
diff --git a/nifi-extension-bundles/pom.xml b/nifi-extension-bundles/pom.xml
index 6669ecd01951..a8444bc2ae76 100755
--- a/nifi-extension-bundles/pom.xml
+++ b/nifi-extension-bundles/pom.xml
@@ -64,6 +64,7 @@
nifi-cdc
nifi-parquet-bundle
nifi-py4j-extension-bundle
+ nifi-ranger-bundle
nifi-extension-utils
nifi-redis-bundle
nifi-network-bundle
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE
new file mode 100644
index 000000000000..3d6c113c3e80
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/LICENSE
@@ -0,0 +1,445 @@
+
+ Apache License
+ Version 2.0, January 2004
+ https://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
+ 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
+ https://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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE
new file mode 100644
index 000000000000..074c9a86ad4b
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/NOTICE
@@ -0,0 +1,449 @@
+nifi-registry-ranger-extension
+Copyright 2020 The Apache Software Foundation
+
+This product includes software developed at
+The Apache Software Foundation (https://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." (https://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 https://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 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 (https://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 (https://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 (https://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 https://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 (https://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 (https://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: https://code.google.com/p/snappy/ (New BSD License)
+
+ This product includes software developed by Apache
+ PureJavaCrc32C from apache-hadoop-common https://hadoop.apache.org/
+ (Apache 2.0 license)
+
+ This library containd statically linked libstdc++. This inclusion is allowed by
+ "GCC RUntime Library Exception"
+ https://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
+ https://creativecommons.org/publicdomain/zero/1.0/
+
+ (ASLv2) ZkClient
+ The following NOTICE information applies:
+ ZkClient
+ Copyright 2009 Stefan Groschupf
+
+ (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 - https://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 - https://jsp.java.net)
+ (CDDL 1.1) (GPL2 w/ CPE) Java Servlet API (javax.servlet:javax.servlet-api:jar:2.5 - https://servlet-spec.java.net)
+ (CDDL 1.1) (GPL2 w/ CPE) javax.ws.rs-api (javax.ws.rs:javax.ws.rs-api:jar:2.1 - https://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 - https://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 - https://www.eclipse.org/eclipselink/)
+ (EPL 1.0) Common Service Data Objects (org.eclipse.persistence:commonj.sdo:2.1.1 - https://www.eclipse.org/eclipselink/)
+ (EPL 1.0) Java Persistence API (org.eclipse.persistence:javax.persistence:2.1.0 - https://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.
+ https://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.
+ https://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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md
new file mode 100644
index 000000000000..52333f1f85fa
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/README.md
@@ -0,0 +1,131 @@
+
+# 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-assembly
+mvn clean install -Pinclude-ranger
+```
+
+The extension will be packaged in the NiFi distribution and will be available in the ${NIFI_HOME}/ext/ranger directory after extraction.
+
+### 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-assembly
+mvn clean install -f nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly
+```
+
+The extension zip will be created as `nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/target/nifi-registry-ranger-assembly-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/nifi-registry-ranger-assembly/target/nifi-registry-ranger-assembly-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:
+```
+
+ ranger-authorizer
+ org.apache.nifi.registry.ranger.RangerAuthorizer
+ nifi-registry
+
+ file-user-group-provider
+
+
+ nifi-registry-service-name
+
+
+ ./ext/ranger/conf/ranger-nifi-registry-security.xml
+ ./ext/ranger/conf/ranger-nifi-registry-audit.xml
+
+
+ ranger@NIFI
+
+
+ false
+
+
+```
+
+## 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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml
new file mode 100644
index 000000000000..e34ef8873d1d
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-audit.xml
@@ -0,0 +1,174 @@
+
+
+
+
+
+ xasecure.audit.is.enabled
+ true
+
+
+
+
+ xasecure.audit.destination.db
+ false
+
+
+
+ xasecure.audit.destination.db.jdbc.driver
+ com.mysql.jdbc.Driver
+
+
+
+ xasecure.audit.destination.db.jdbc.url
+ jdbc:mysql://localhost/ranger_audit
+
+
+
+ xasecure.audit.destination.db.password
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.user
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.batch.filespool.dir
+ /tmp/audit/db/spool
+
+
+
+
+
+ xasecure.audit.destination.hdfs
+ false
+
+
+
+ xasecure.audit.destination.hdfs.dir
+ hdfs://localhost:8020/ranger/audit
+
+
+
+ xasecure.audit.destination.hdfs.batch.filespool.dir
+ /tmp/audit/hdfs/spool
+
+
+
+
+
+
+
+
+ xasecure.audit.destination.log4j
+ false
+
+
+
+ xasecure.audit.destination.log4j.logger
+ ranger_audit_logger
+
+
+
+
+ xasecure.audit.destination.solr
+ true
+
+
+
+ xasecure.audit.destination.solr.batch.filespool.dir
+ /tmp/audit/solr/spool
+
+
+
+
+ xasecure.audit.destination.solr.urls
+
+
+ localhost:2181/solr
+
+
+
+
+
+ xasecure.audit.destination.solr.force.use.inmemory.jaas.config
+ true
+
+
+ xasecure.audit.jaas.Client.option.useKeyTab
+ true
+
+
+ xasecure.audit.jaas.Client.option.storeKey
+ false
+
+
+ xasecure.audit.jaas.Client.option.serviceName
+ solr
+
+
+ xasecure.audit.jaas.Client.option.principal
+ nifi-registry@EXAMPLE.COM
+
+
+ xasecure.audit.jaas.Client.option.keyTab
+ /etc/security/keytabs/nifi-registry.keytab
+
+
+ xasecure.audit.jaas.Client.loginModuleName
+ com.sun.security.auth.module.Krb5LoginModule
+
+
+ xasecure.audit.jaas.Client.loginModuleControlFlag
+ required
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml
new file mode 100644
index 000000000000..f271d6d8df3a
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/conf/ranger-nifi-registry-security.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.url
+ http://localhost:6080
+
+ URL to Ranger Admin
+
+
+
+
+ ranger.plugin.nifi-registry.service.name
+ nifi-registry-service-name
+
+ Name of the Ranger service containing policies for this NiFi Registry instance
+
+
+
+
+ ranger.plugin.nifi-registry.policy.source.impl
+ org.apache.ranger.admin.client.RangerAdminRESTClient
+
+ Class to retrieve policies from the source
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.ssl.config.file
+ ranger-policymgr-ssl.xml
+
+ Path to the file containing SSL details to contact Ranger Admin
+
+
+
+
+ ranger.plugin.nifi-registry.policy.pollIntervalMs
+ 30000
+
+ How often to poll for changes in policies?
+
+
+
+
+ ranger.plugin.nifi-registry.policy.cache.dir
+ /tmp
+
+ Directory where Ranger policies are cached after successful retrieval from the source
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.client.connection.timeoutMs
+ 120000
+
+ RangerRestClient Connection Timeout in Milli Seconds
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.client.read.timeoutMs
+ 30000
+
+ RangerRestClient read Timeout in Milli Seconds
+
+
+
+
+ xasecure.add-hadoop-authorization
+ true
+
+ Enable SPNEGO authentication using principal and keytab to download policies from Ranger
+
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml
new file mode 100644
index 000000000000..7568c8d8c135
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/pom.xml
@@ -0,0 +1,100 @@
+
+
+
+
+ nifi-registry-ranger
+ org.apache.nifi.registry
+ 2.10.0.1
+
+ 4.0.0
+
+ nifi-registry-ranger-assembly
+ pom
+ Apache Ranger extension for Apache NiFi Registry
+
+
+
+ org.apache.nifi.registry
+ nifi-registry-ranger-plugin
+ ${project.version}
+
+
+ org.slf4j
+ jcl-over-slf4j
+ runtime
+
+
+
+
+
+
+ maven-assembly-plugin
+
+ true
+
+
+
+ assembly-ranger-extension
+
+ single
+
+ package
+
+
+ src/main/assembly/extension.xml
+
+ zip
+
+
+
+
+
+
+
+
+
+ targz
+
+ false
+
+
+
+
+ maven-assembly-plugin
+
+ true
+
+
+
+ assembly-ranger-extension
+
+ single
+
+ package
+
+
+ src/main/assembly/extension.xml
+
+ tar.gz
+
+
+
+
+
+
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml
new file mode 100644
index 000000000000..859ca1158de0
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-assembly/src/main/assembly/extension.xml
@@ -0,0 +1,62 @@
+
+
+
+ bin
+
+ zip
+
+ false
+
+
+ runtime
+ false
+ lib
+ 0770
+ 0664
+
+
+
+
+
+ ./README.md
+ ./
+ README.md
+ 0644
+ true
+
+
+ ./LICENSE
+ ./
+ LICENSE
+ 0644
+ true
+
+
+ ./NOTICE
+ ./
+ NOTICE
+ 0644
+ true
+
+
+
+
+
+ conf
+ conf
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml
new file mode 100644
index 000000000000..54a3cb2729a2
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-jersey-bundle/pom.xml
@@ -0,0 +1,71 @@
+
+
+
+
+ nifi-registry-ranger
+ org.apache.nifi.registry
+ 2.10.0.1
+
+ 4.0.0
+
+ This module removes javax.ws.rs package from jersey-bundle.jar which is used by ranger-plugins-common.jar in order to address javax.ws.rs version mismatch between jersey-bundle.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.jar!/javax/ws/rs/ext/RuntimeDelegate.classtojar:file:/home/koji/nifi-registry-xxx/./ext/ranger/lib/jersey-bundle.jar!/javax/ws/rs/ext/RuntimeDelegate.class
+
+ nifi-registry-ranger-jersey-bundle
+
+
+
+ com.sun.jersey
+ jersey-bundle
+ 1.19.4
+
+
+
+ javax.ws.rs
+ jsr311-api
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.5.0
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ com.sun.jersey:jersey-bundle
+
+ javax/ws/rs/**
+
+
+
+
+
+
+
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml
new file mode 100644
index 000000000000..70ac6bc17612
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/pom.xml
@@ -0,0 +1,449 @@
+
+
+
+
+ nifi-registry-ranger
+ org.apache.nifi.registry
+ 2.10.0.1
+
+ 4.0.0
+
+ nifi-registry-ranger-plugin
+ jar
+
+
+
+ org.apache.nifi.registry
+ nifi-registry-data-model
+ 2.10.0.1
+
+
+
+ org.apache.nifi.registry
+ nifi-registry-security-api
+
+ provided
+
+
+ org.apache.nifi.registry
+ nifi-registry-properties
+
+ provided
+
+
+ org.apache.nifi
+ nifi-xml-processing
+ 2.10.0.1
+
+
+ org.apache.nifi.registry
+ nifi-registry-ranger-jersey-bundle
+ 2.10.0.1
+
+
+ com.sun.jersey
+ jersey-bundle
+
+
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+ 2.1.1
+
+
+ org.slf4j
+ log4j-over-slf4j
+
+
+ org.slf4j
+ jcl-over-slf4j
+
+
+ org.apache.nifi
+ nifi-deprecation-log
+ 2.10.0.1
+
+
+
+
+ org.apache.ranger
+ ranger-plugins-common
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+ ch.qos.logback
+ logback-classic
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
+ com.sun.jersey
+ jersey-bundle
+
+
+
+ com.sun.jersey
+ jersey-json
+
+
+
+
+ org.apache.ranger
+ ranger-plugins-audit
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+
+
+ org.apache.ranger
+ credentialbuilder
+ ${ranger.version}
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ org.slf4j
+ slf4j-api
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+ org.apache.hadoop
+ hadoop-client
+ ${hadoop.version}
+
+
+ org.apache.hadoop
+ hadoop-yarn-api
+
+
+ org.apache.hadoop
+ hadoop-yarn-client
+
+
+ org.apache.hadoop
+ hadoop-mapreduce-client
+
+
+ org.apache.hadoop
+ hadoop-mapreduce-client-core
+
+
+ org.apache.hadoop
+ hadoop-mapreduce-client-jobclient
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.apache.hadoop
+ hadoop-common
+ ${hadoop.version}
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+
+ javax.ws.rs
+ jsr311-api
+
+
+
+ com.sun.jersey
+ jersey-core
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+ ch.qos.logback
+ logback-classic
+
+
+
+
+ org.apache.hadoop
+ hadoop-auth
+ ${hadoop.version}
+
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ org.slf4j
+ slf4j-log4j12
+
+
+ log4j
+ log4j
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+ javax.mail
+ mail
+ 1.4.7
+
+
+ org.codehaus.jettison
+ jettison
+ 1.5.4
+
+
+
+
+
+
+ disable-ranger-tests
+
+
+ aarch64
+
+
+
+ true
+
+
+
+
+ include-hadoop-aws
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-aws
+ ${hadoop.version}
+
+
+
+
+
+ include-hadoop-azure
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-azure
+ ${hadoop.version}
+
+
+ com.google.guava
+ guava
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+ org.apache.hadoop
+ hadoop-azure-datalake
+ ${hadoop.version}
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+
+
+
+
+
+ include-hadoop-cloud-storage
+
+ false
+
+
+
+ org.apache.hadoop
+ hadoop-cloud-storage
+ ${hadoop.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+
+
+ include-hadoop-ozone
+
+ false
+
+
+
+ org.apache.ozone
+ ozone-client
+ ${ozone.version}
+
+
+ commons-logging
+ commons-logging
+
+
+ org.apache.logging.log4j
+ log4j-core
+
+
+ org.bouncycastle
+ bcprov-jdk15on
+
+
+ org.bouncycastle
+ bcpkix-jdk15on
+
+
+
+
+ org.bouncycastle
+ bcprov-jdk18on
+
+
+ org.bouncycastle
+ bcpkix-jdk18on
+
+
+ org.apache.ozone
+ ozone-filesystem
+ ${ozone.version}
+
+
+
+
+
+ include-hadoop-gcp
+
+ false
+
+
+
+ com.google.cloud.bigdataoss
+ gcs-connector
+ hadoop3-${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ util
+ ${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ util-hadoop
+ hadoop3-${gcs.version}
+
+
+ com.google.cloud.bigdataoss
+ gcsio
+ ${gcs.version}
+
+
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java b/nifi-registry/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 000000000000..8c021f1caa5f
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerAuthorizer.java
@@ -0,0 +1,452 @@
+/*
+ * 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.nifi.xml.processing.ProcessingException;
+import org.apache.nifi.xml.processing.parsers.DocumentProvider;
+import org.apache.nifi.xml.processing.parsers.StandardDocumentProvider;
+import org.apache.nifi.xml.processing.transform.StandardTransformProvider;
+import org.apache.nifi.xml.processing.transform.TransformProvider;
+import org.apache.ranger.audit.model.AuthzAuditEvent;
+import org.apache.ranger.authorization.hadoop.config.RangerConfiguration;
+import org.apache.ranger.authorization.hadoop.config.RangerPluginConfig;
+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 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.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.WeakHashMap;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/**
+ * 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 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_SERVICE_TYPE_PROP = "Ranger Service Type";
+ static final String RANGER_APP_ID_PROP = "Ranger Application Id";
+ static final String RANGER_ADMIN_IDENTITY_PROP_PREFIX = "Ranger Admin Identity";
+ static final Pattern RANGER_ADMIN_IDENTITY_PATTERN = Pattern.compile(RANGER_ADMIN_IDENTITY_PROP_PREFIX + "\\s?\\S*");
+
+ 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 resultLookup = new WeakHashMap<>();
+
+ private volatile RangerBasePluginWithPolicies rangerPlugin = null;
+ private volatile RangerDefaultAuditHandler defaultAuditHandler = null;
+ private volatile Set 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 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);
+
+ final RangerPluginConfig pluginConfig = rangerPlugin.getConfig();
+
+ final PropertyValue securityConfigValue = configurationContext.getProperty(RANGER_SECURITY_PATH_PROP);
+ addRequiredResource(RANGER_SECURITY_PATH_PROP, securityConfigValue, pluginConfig);
+
+ final PropertyValue auditConfigValue = configurationContext.getProperty(RANGER_AUDIT_PATH_PROP);
+ addRequiredResource(RANGER_AUDIT_PATH_PROP, auditConfigValue, pluginConfig);
+
+ 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());
+ }
+
+ rangerPlugin.init();
+
+ defaultAuditHandler = new RangerDefaultAuditHandler();
+ rangerAdminIdentity = getConfigValues(configurationContext, RANGER_ADMIN_IDENTITY_PATTERN, 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 userGroups = request.getGroups();
+ final String resourceIdentifier = request.getResource().getIdentifier();
+
+ // if a ranger admin identity was provided, and it contains the identity making the request,
+ // and the request is to retrieve the resources, then allow it through
+ if (rangerAdminIdentity != null && rangerAdminIdentity.contains(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
+ * @param configuration the RangerConfiguration to add the resource to
+ */
+ private void addRequiredResource(final String name, final PropertyValue resourceValue, final RangerConfiguration configuration) {
+ 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 {
+ configuration.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;
+ }
+
+ private Set getConfigValues(final AuthorizerConfigurationContext context, final Pattern namePattern, final String defaultValue) {
+ final Set configValues = new HashSet<>();
+
+ for (Map.Entry entry : context.getProperties().entrySet()) {
+ Matcher matcher = namePattern.matcher(entry.getKey());
+ if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+ configValues.add(entry.getValue());
+ }
+ }
+
+ if (configValues.isEmpty() && (defaultValue != null)) {
+ configValues.add(defaultValue);
+ }
+
+ return configValues;
+ }
+
+ @Override
+ public String getFingerprint() throws AuthorizationAccessException {
+ final StringWriter out = new StringWriter();
+ try {
+ // create the document
+ final DocumentProvider documentProvider = new StandardDocumentProvider();
+ final Document document = documentProvider.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 TransformProvider transformProvider = new StandardTransformProvider();
+ transformProvider.transform(new DOMSource(document), new StreamResult(out));
+ } catch (final ProcessingException 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 DocumentProvider documentProvider = new StandardDocumentProvider();
+ final Document document = documentProvider.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 (final ProcessingException | 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 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 {
+ }
+ };
+ }
+}
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/main/java/org/apache/nifi/registry/ranger/RangerBasePluginWithPolicies.java b/nifi-registry/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 000000000000..0d1c79e2d5f1
--- /dev/null
+++ b/nifi-registry/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 static final String WILDCARD_ASTERISK = "*";
+
+ private UserGroupProvider userGroupProvider;
+ private AtomicReference 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 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 policiesByIdentifier = new HashMap<>();
+ final Map> 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 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 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 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 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 policiesByIdentifier;
+ private final Map> policiesByResource;
+ private final Set allPolicies;
+
+ private PolicyLookup() {
+ this(null, null);
+ }
+
+ private PolicyLookup(final Map policiesByIdentifier, final Map> policiesByResource) {
+ if (policiesByIdentifier == null) {
+ allPolicies = Collections.EMPTY_SET;
+ } else {
+ allPolicies = Collections.unmodifiableSet(new HashSet<>(policiesByIdentifier.values()));
+ }
+
+ this.policiesByIdentifier = policiesByIdentifier;
+ this.policiesByResource = policiesByResource;
+ }
+
+ private Set 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 policiesForResource = policiesByResource.get(resourceIdentifier);
+
+ if (policiesForResource != null) {
+ return policiesForResource.get(action);
+ }
+
+ return null;
+ }
+ }
+
+}
diff --git a/nifi-registry/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/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 000000000000..f8c1bc3bf0fc
--- /dev/null
+++ b/nifi-registry/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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java b/nifi-registry/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 000000000000..d03309f5c263
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerAuthorizer.java
@@ -0,0 +1,669 @@
+/*
+ * 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.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.authorization.hadoop.config.RangerPluginConfig;
+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.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentMatcher;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+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 =
+ ""
+ + ""
+ + ""
+ + ""
+ + ""
+ + "";
+
+ private static final String EMPTY_FINGERPRINT = ""
+ + ""
+ + ""
+ + "";
+
+ private static final String NON_EMPTY_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 MockRangerAuthorizer authorizer;
+ private RangerBasePluginWithPolicies rangerBasePlugin;
+
+ private final String serviceType = "nifiRegistryService";
+ private final String appId = "nifiRegistryAppId";
+
+ private RangerAccessResult allowedResult;
+ private RangerAccessResult notAllowedResult;
+ private Map authorizersXmlContent = null;
+
+ @BeforeEach
+ public void initialization() {
+ authorizersXmlContent = new HashMap<>();
+ authorizersXmlContent.put(RangerAuthorizer.USER_GROUP_PROVIDER, "user-group-provider");
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_SECURITY_PATH_PROP, "src/test/resources/ranger/ranger-nifi-registry-security.xml");
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_AUDIT_PATH_PROP, "src/test/resources/ranger/ranger-nifi-registry-audit.xml");
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_APP_ID_PROP, appId);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_SERVICE_TYPE_PROP, serviceType);
+ }
+
+ 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);
+
+ final RangerPluginConfig pluginConfig = new RangerPluginConfig(serviceType, null, appId, null, null, null);
+ when(rangerBasePlugin.getConfig()).thenReturn(pluginConfig);
+
+ 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);
+
+ for (Map.Entry entry : authorizersXmlContent.entrySet()) {
+ when(configurationContext.getProperty(eq(entry.getKey())))
+ .thenReturn(new StandardPropertyValue(entry.getValue()));
+ }
+
+ when(configurationContext.getProperties()).thenReturn(authorizersXmlContent);
+
+ 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("");
+
+ assertThrows(SecurityProviderCreationException.class, () -> setup(registryProperties, mock(UserGroupProvider.class), configurationContext));
+ }
+
+ @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("");
+
+ assertThrows(SecurityProviderCreationException.class, () -> setup(registryProperties, mock(UserGroupProvider.class), configurationContext));
+ }
+
+ @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("");
+
+ assertThrows(SecurityProviderCreationException.class, () -> setup(registryProperties, mock(UserGroupProvider.class), configurationContext));
+ }
+
+ @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");
+
+ assertThrows(SecurityProviderCreationException.class, () -> setup(registryProperties, mock(UserGroupProvider.class), configurationContext));
+ }
+
+ @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 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() {
+ final String acceptableIdentity = "ranger-admin";
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity);
+
+ final String requestIdentity = "ranger-admin";
+ runRangerAdminTest(RangerAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminApprovedMultipleAcceptableIdentities() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin2";
+ runRangerAdminTest(RangerAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminApprovedMultipleAcceptableIdentities2() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin3";
+ runRangerAdminTest(RangerAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.approved().getResult());
+ }
+
+ @Test
+ public void testRangerAdminDenied() {
+ final String acceptableIdentity = "ranger-admin";
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity);
+
+ final String requestIdentity = "ranger-admin";
+ runRangerAdminTest("/flow", requestIdentity, AuthorizationResult.denied().getResult());
+ }
+
+ @Test
+ public void testRangerAdminDeniedMultipleAcceptableIdentities() {
+ final String acceptableIdentity1 = "ranger-admin1";
+ final String acceptableIdentity2 = "ranger-admin2";
+ final String acceptableIdentity3 = "ranger-admin3";
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX, acceptableIdentity1);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 2", acceptableIdentity2);
+ authorizersXmlContent.put(RangerAuthorizer.RANGER_ADMIN_IDENTITY_PROP_PREFIX + " 3", acceptableIdentity3);
+
+ final String requestIdentity = "ranger-admin4";
+ runRangerAdminTest(RangerAuthorizer.RESOURCES_RESOURCE, requestIdentity, AuthorizationResult.denied().getResult());
+ }
+
+ private void runRangerAdminTest(final String resourceIdentifier, final String requestIdentity, final AuthorizationResult.Result expectedResult) {
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+
+ 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(requestIdentity)
+ .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());
+ }
+
+ /**
+ * 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 {
+
+ 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);
+
+ 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);
+
+ 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);
+
+ 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
+ public void testInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ assertThrows(AuthorizationAccessException.class, () -> 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
+ public void testCheckInheritInvalidFingerprint() {
+ final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ assertThrows(AuthorizationAccessException.class, () -> 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
+ public void testCheckInheritNonConfigurableUserGroupProvider() {
+ final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+ final AuthorizerConfigurationContext configurationContext = createMockConfigContext();
+ setup(mock(NiFiRegistryProperties.class), userGroupProvider, configurationContext);
+
+ assertThrows(UninheritableAuthorizationsException.class, () -> authorizer.checkInheritability(NON_EMPTY_FINGERPRINT));
+ }
+
+}
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/java/org/apache/nifi/registry/ranger/TestRangerBasePluginWithPolicies.java b/nifi-registry/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 000000000000..a58e3da2c5a5
--- /dev/null
+++ b/nifi-registry/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.jupiter.api.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.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.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 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 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 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 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));
+ assertEquals(readResource1, 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));
+ assertEquals(readResource2, 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));
+ assertEquals(writeResource2, 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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));
+ assertEquals(readResource1, 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));
+ assertEquals(readResource2, 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));
+ assertEquals(writeResource2, 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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/krb5.conf b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/krb5.conf
new file mode 100644
index 000000000000..0e3f142a9b48
--- /dev/null
+++ b/nifi-registry/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/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml
new file mode 100644
index 000000000000..d590a5039c04
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/core-site.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ hadoop.security.authentication
+ simple
+
+
\ No newline at end of file
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml
new file mode 100644
index 000000000000..3dbd576334b9
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-audit.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+ xasecure.audit.is.enabled
+ true
+
+
+
+
+ xasecure.audit.destination.db
+ false
+
+
+
+ xasecure.audit.destination.db.jdbc.driver
+ com.mysql.jdbc.Driver
+
+
+
+ xasecure.audit.destination.db.jdbc.url
+ jdbc:mysql://localhost/ranger_audit
+
+
+
+ xasecure.audit.destination.db.password
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.user
+ rangerlogger
+
+
+
+ xasecure.audit.destination.db.batch.filespool.dir
+ /tmp/audit/db/spool
+
+
+
+
+
+ xasecure.audit.destination.hdfs
+ false
+
+
+
+ xasecure.audit.destination.hdfs.dir
+ hdfs://localhost:8020/ranger/audit
+
+
+
+ xasecure.audit.destination.hdfs.batch.filespool.dir
+ /tmp/audit/hdfs/spool
+
+
+
+
+
+ xasecure.audit.destination.log4j
+ false
+
+
+
+ xasecure.audit.destination.log4j.logger
+ ranger_audit_logger
+
+
+
+
+ xasecure.audit.destination.solr
+ true
+
+
+
+ xasecure.audit.destination.solr.batch.filespool.dir
+ /tmp/audit/solr/spool
+
+
+
+ xasecure.audit.destination.solr.urls
+ http://localhost:6083/solr/ranger_audits
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml
new file mode 100644
index 000000000000..ab55fba7077e
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-nifi-registry-security.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.url
+ http://localhost:6080
+
+ URL to Ranger Admin
+
+
+
+
+ ranger.plugin.nifi-registry.service.name
+ nifi-registry
+
+ Name of the Ranger service containing policies for this nifi instance
+
+
+
+
+ ranger.plugin.nifi-registry.policy.source.impl
+ org.apache.ranger.admin.client.RangerAdminRESTClient
+
+ Class to retrieve policies from the source
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.ssl.config.file
+ ranger-policymgr-ssl.xml
+
+ Path to the file containing SSL details to contact Ranger Admin
+
+
+
+
+ ranger.plugin.nifi-registry.policy.pollIntervalMs
+ 30000
+
+ How often to poll for changes in policies?
+
+
+
+
+ ranger.plugin.nifi-registry.policy.cache.dir
+ /tmp
+
+ Directory where Ranger policies are cached after successful retrieval from the source
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.client.connection.timeoutMs
+ 120000
+
+ RangerRestClient Connection Timeout in Milli Seconds
+
+
+
+
+ ranger.plugin.nifi-registry.policy.rest.client.read.timeoutMs
+ 30000
+
+ RangerRestClient read Timeout in Milli Seconds
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
new file mode 100644
index 000000000000..a6e05747a3d5
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/nifi-registry-ranger-plugin/src/test/resources/ranger/ranger-policymgr-ssl.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+ xasecure.policymgr.clientssl.keystore
+
+
+ Java Keystore files
+
+
+
+ xasecure.policymgr.clientssl.keystore.password
+ none
+
+ password for keystore
+
+
+
+ xasecure.policymgr.clientssl.truststore
+
+
+ java truststore file
+
+
+
+ xasecure.policymgr.clientssl.truststore.password
+ none
+
+ java truststore password
+
+
+
+ xasecure.policymgr.clientssl.keystore.credential.file
+
+
+ java keystore credential file
+
+
+
+ xasecure.policymgr.clientssl.truststore.credential.file
+
+
+ java truststore credential file
+
+
+
\ No newline at end of file
diff --git a/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/pom.xml b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/pom.xml
new file mode 100644
index 000000000000..9ca1e7744b25
--- /dev/null
+++ b/nifi-registry/nifi-registry-extensions/nifi-registry-ranger/pom.xml
@@ -0,0 +1,92 @@
+
+
+
+
+ nifi-registry-extensions
+ org.apache.nifi.registry
+ 2.10.0.1
+
+ 4.0.0
+
+ nifi-registry-ranger
+ pom
+
+
+ nifi-registry-ranger-assembly
+ nifi-registry-ranger-jersey-bundle
+ nifi-registry-ranger-plugin
+
+
+
+
+
+ org.eclipse.jetty
+ jetty-client
+ 9.4.58.v20250814
+
+
+ org.eclipse.jetty
+ jetty-http
+ 9.4.58.v20250814
+
+
+ org.eclipse.jetty
+ jetty-io
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-util
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-alpn-client
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-util-ajax
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-alpn-java-client
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-xml
+ ${ranger.jetty.version}
+
+
+ org.eclipse.jetty
+ jetty-security
+ ${ranger.jetty.version}
+
+
+ org.apache.solr
+ solr-solrj
+ ${ranger.solr.version}
+
+
+ com.nimbusds
+ nimbus-jose-jwt
+ ${nimbus-jose-jwt.version}
+
+
+
+
diff --git a/nifi-registry/nifi-registry-extensions/pom.xml b/nifi-registry/nifi-registry-extensions/pom.xml
index af47faf54ccd..6ea2c0823215 100644
--- a/nifi-registry/nifi-registry-extensions/pom.xml
+++ b/nifi-registry/nifi-registry-extensions/pom.xml
@@ -26,5 +26,6 @@
nifi-registry-aws
+ nifi-registry-ranger
diff --git a/nifi-system-tests/nifi-system-test-suite/src/main/java/org/apache/nifi/tests/system/arenadata/service/JdbcService.java b/nifi-system-tests/nifi-system-test-suite/src/main/java/org/apache/nifi/tests/system/arenadata/service/JdbcService.java
index b56de3270468..422ee150dd4e 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/main/java/org/apache/nifi/tests/system/arenadata/service/JdbcService.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/main/java/org/apache/nifi/tests/system/arenadata/service/JdbcService.java
@@ -23,7 +23,6 @@
import org.springframework.jdbc.core.JdbcTemplate;
import org.testcontainers.shaded.org.awaitility.Awaitility;
-import javax.sql.DataSource;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
import java.util.List;
@@ -33,6 +32,7 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import javax.sql.DataSource;
public class JdbcService {
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/AllureExtension.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/AllureExtension.java
index 72d0413a2b7b..3a50861b55d3 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/AllureExtension.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/AllureExtension.java
@@ -43,4 +43,4 @@ public void beforeEach(ExtensionContext context) {
});
Allure.suite("Greengage connector: ADB " + adbVersion);
}
-}
\ No newline at end of file
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/GetGreengageRecordIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/GetGreengageRecordIT.java
index 7af98800f321..d0049ee52bda 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/GetGreengageRecordIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/GetGreengageRecordIT.java
@@ -60,32 +60,33 @@ public class GetGreengageRecordIT extends NifiSystemContainerizedIT {
private static final String CREATE_EXTENSION_UUID_SQL = "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"";
private static final String CREATE_TABLE_TEMPLATE_SQL = "CREATE TABLE %s (%s)";
private static final String ID_COLUMN = "id";
- private static final Map TABLE_COLUMNS = new LinkedHashMap<>() {{
- put(ID_COLUMN, "BIGSERIAL PRIMARY KEY");
- put("f_int", "INT");
- put("f_bigint", "BIGINT");
- put("f_bit", "BIT");
- put("f_bool", "BOOLEAN");
- put("f_bytea", "BYTEA");
- put("f_char", "CHAR(2)");
- put("code", "VARCHAR(10)");
- put("article", "VARCHAR");
- put("f_date", "DATE");
- put("f_float", "FLOAT8");
- put("f_real", "FLOAT4");
- put("f_jsonb", "JSONB");
- put("f_numeric", "DECIMAL(10, 5)");
- put("f_double", "DOUBLE PRECISION");
- put("f_tinyint", "SMALLINT");
- put("f_smallint", "SMALLINT");
- put("f_time", "TIME");
- put("f_timestampz", "TIMESTAMPTZ");
- put("f_timestamp", "TIMESTAMP");
- put("f_uuid", "UUID");
- put("f_text_array", "TEXT[]");
- put("f_hstore", "HSTORE");
- put("f_enum", "DAY");
- }};
+ private static final Map TABLE_COLUMNS = new LinkedHashMap<>();
+ static {
+ TABLE_COLUMNS.put(ID_COLUMN, "BIGSERIAL PRIMARY KEY");
+ TABLE_COLUMNS.put("f_int", "INT");
+ TABLE_COLUMNS.put("f_bigint", "BIGINT");
+ TABLE_COLUMNS.put("f_bit", "BIT");
+ TABLE_COLUMNS.put("f_bool", "BOOLEAN");
+ TABLE_COLUMNS.put("f_bytea", "BYTEA");
+ TABLE_COLUMNS.put("f_char", "CHAR(2)");
+ TABLE_COLUMNS.put("code", "VARCHAR(10)");
+ TABLE_COLUMNS.put("article", "VARCHAR");
+ TABLE_COLUMNS.put("f_date", "DATE");
+ TABLE_COLUMNS.put("f_float", "FLOAT8");
+ TABLE_COLUMNS.put("f_real", "FLOAT4");
+ TABLE_COLUMNS.put("f_jsonb", "JSONB");
+ TABLE_COLUMNS.put("f_numeric", "DECIMAL(10, 5)");
+ TABLE_COLUMNS.put("f_double", "DOUBLE PRECISION");
+ TABLE_COLUMNS.put("f_tinyint", "SMALLINT");
+ TABLE_COLUMNS.put("f_smallint", "SMALLINT");
+ TABLE_COLUMNS.put("f_time", "TIME");
+ TABLE_COLUMNS.put("f_timestampz", "TIMESTAMPTZ");
+ TABLE_COLUMNS.put("f_timestamp", "TIMESTAMP");
+ TABLE_COLUMNS.put("f_uuid", "UUID");
+ TABLE_COLUMNS.put("f_text_array", "TEXT[]");
+ TABLE_COLUMNS.put("f_hstore", "HSTORE");
+ TABLE_COLUMNS.put("f_enum", "DAY");
+ }
/**
* PG target columns derived from ADB.
* Types that GetGreengageRecord converts are mapped to PG-compatible types:
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/NifiSystemContainerizedIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/NifiSystemContainerizedIT.java
index 61518508da1c..24f91058a1ae 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/NifiSystemContainerizedIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/NifiSystemContainerizedIT.java
@@ -82,11 +82,13 @@ public void setup(final TestInfo testInfo) throws IOException, NoSuchAlgorithmEx
setupClient(9091);
}
+ @Override
protected void setupClient(final int apiPort) {
nifiClient = createClient(apiPort);
clientUtil = new NiFiClientUtil(nifiClient, getNiFiVersion(), getTestName());
}
+ @Override
protected NiFiClient createClient(final int port) {
final NiFiClientConfig.Builder clientConfigBuilder = new NiFiClientConfig.Builder()
.baseUrl("http://localhost:" + port)
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/PutGreengageRecordIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/PutGreengageRecordIT.java
index bd511400e43a..1fa600160bca 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/PutGreengageRecordIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/arenadata/PutGreengageRecordIT.java
@@ -65,31 +65,34 @@ public class PutGreengageRecordIT extends NifiSystemContainerizedIT {
private static final String CREATE_EXTENSION_UUID_SQL = "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"";
private static final String CREATE_TABLE_TEMPLATE_SQL = "CREATE TABLE %s (%s)";
private static final String ID_COLUMN = "id";
- private static final Map TABLE_COLUMNS = new LinkedHashMap<>() {{
- put(ID_COLUMN, "BIGSERIAL PRIMARY KEY");
- put("f_int", "INT");
- put("f_bigint", "BIGINT");
- put("f_bit", "BIT");
- put("f_bool", "BOOLEAN");
- put("f_bytea", "BYTEA");
- put("f_char", "CHAR(2)");
- put("code", "VARCHAR(10)");
- put("article", "VARCHAR");
- put("f_date", "DATE");
- put("f_float", "FLOAT8");
- put("f_real", "FLOAT4");
- put("f_jsonb", "JSONB");
- put("f_numeric", "DECIMAL(10, 5)");
- put("f_double", "DOUBLE PRECISION");
- put("f_tinyint", "SMALLINT");
- put("f_smallint", "SMALLINT");
- put("f_time", "TIME");
- put("f_timestampz", "TIMESTAMPTZ");
- put("f_timestamp", "TIMESTAMP");
- put("f_uuid", "UUID");
- put("f_text_array", "TEXT[]");
- put("f_hstore", "HSTORE");
- }};
+
+ private static final Map TABLE_COLUMNS = new LinkedHashMap<>();
+ static {
+ TABLE_COLUMNS.put(ID_COLUMN, "BIGSERIAL PRIMARY KEY");
+ TABLE_COLUMNS.put("f_int", "INT");
+ TABLE_COLUMNS.put("f_bigint", "BIGINT");
+ TABLE_COLUMNS.put("f_bit", "BIT");
+ TABLE_COLUMNS.put("f_bool", "BOOLEAN");
+ TABLE_COLUMNS.put("f_bytea", "BYTEA");
+ TABLE_COLUMNS.put("f_char", "CHAR(2)");
+ TABLE_COLUMNS.put("code", "VARCHAR(10)");
+ TABLE_COLUMNS.put("article", "VARCHAR");
+ TABLE_COLUMNS.put("f_date", "DATE");
+ TABLE_COLUMNS.put("f_float", "FLOAT8");
+ TABLE_COLUMNS.put("f_real", "FLOAT4");
+ TABLE_COLUMNS.put("f_jsonb", "JSONB");
+ TABLE_COLUMNS.put("f_numeric", "DECIMAL(10, 5)");
+ TABLE_COLUMNS.put("f_double", "DOUBLE PRECISION");
+ TABLE_COLUMNS.put("f_tinyint", "SMALLINT");
+ TABLE_COLUMNS.put("f_smallint", "SMALLINT");
+ TABLE_COLUMNS.put("f_time", "TIME");
+ TABLE_COLUMNS.put("f_timestampz", "TIMESTAMPTZ");
+ TABLE_COLUMNS.put("f_timestamp", "TIMESTAMP");
+ TABLE_COLUMNS.put("f_uuid", "UUID");
+ TABLE_COLUMNS.put("f_text_array", "TEXT[]");
+ TABLE_COLUMNS.put("f_hstore", "HSTORE");
+ }
+
private static final String GENERATE_DATASET_SQL = "select i,\n" +
" 25000000000 * random(),\n" +
" case when random() > 0.5 then 1 else 0 end::bit,\n" +
diff --git a/pom.xml b/pom.xml
index 6717dc2b1f4a..ef6acd093c77 100644
--- a/pom.xml
+++ b/pom.xml
@@ -142,6 +142,11 @@
3.5.0
1.4.1
+
+ 2.6.0
+ 8.11.3
+ 9.4.58.v20250814
+
7.7.0