http://git-wip-us.apache.org/repos/asf/nifi/blob/482f3719/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
 
b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
index a5e4103..6b056d7 100644
--- 
a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
+++ 
b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/groovy/org/apache/nifi/properties/ConfigEncryptionToolTest.groovy
@@ -80,6 +80,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     final String FLOW_PASSWORD = isUnlimitedStrengthCryptoAvailable() ? 
FLOW_PASSWORD_256 : FLOW_PASSWORD_128
 
     private static final int LIP_PASSWORD_LINE_COUNT = 3
+    private static final int AUTHORIZERS_PASSWORD_LINE_COUNT = 3
     private final String PASSWORD_PROP_REGEX = "<property[^>]* name=\".* 
Password\""
 
     private static final String DEFAULT_ALGORITHM = 
"PBEWITHMD5AND256BITAES-CBC-OPENSSL"
@@ -88,7 +89,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     private final String DEFAULT_LEGACY_SENSITIVE_PROPS_KEY = "nififtw!"
 
     @BeforeClass
-    public static void setUpOnce() throws Exception {
+    static void setUpOnce() throws Exception {
         Security.addProvider(new BouncyCastleProvider())
 
         logger.metaClass.methodMissing = { String name, args ->
@@ -99,17 +100,17 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @AfterClass
-    public static void tearDownOnce() throws Exception {
+    static void tearDownOnce() throws Exception {
         File tmpDir = new File("target/tmp/")
         tmpDir.delete()
     }
 
     @Before
-    public void setUp() throws Exception {
+    void setUp() throws Exception {
     }
 
     @After
-    public void tearDown() throws Exception {
+    void tearDown() throws Exception {
         TestAppender.reset()
     }
 
@@ -320,6 +321,60 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
+    void testShouldParseAuthorizersArgument() {
+        // Arrange
+        def flags = ["-a", "--authorizers"]
+        String authorizersPath = "src/test/resources/authorizers.xml"
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+
+        // Act
+        flags.each { String arg ->
+            tool.parse([arg, authorizersPath] as String[])
+            logger.info("Parsed authorizers.xml location: 
${tool.authorizersPath}")
+
+            // Assert
+            assert tool.authorizersPath == authorizersPath
+            assert tool.handlingAuthorizers
+        }
+    }
+
+    @Test
+    void testShouldParseOutputAuthorizersArgument() {
+        // Arrange
+        def flags = ["-u", "--outputAuthorizers"]
+        String authorizersPath = "src/test/resources/authorizers.xml"
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+
+        // Act
+        flags.each { String arg ->
+            final outputAuthorizersPath = authorizersPath.reverse()
+            tool.parse([arg, outputAuthorizersPath, "-a", authorizersPath] as 
String[])
+            logger.info("Parsed output authorizers.xml location: 
${tool.outputAuthorizersPath}")
+
+            // Assert
+            assert tool.outputAuthorizersPath == outputAuthorizersPath
+        }
+    }
+
+    @Test
+    void testParseShouldWarnIfAuthorizersWillBeOverwritten() {
+        // Arrange
+        String authorizersPath = "conf/authorizers.xml"
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+
+        // Act
+        tool.parse("-a ${authorizersPath} -u ${authorizersPath}".split(" ") as 
String[])
+        logger.info("Parsed authorizers.xml location: ${tool.authorizersPath}")
+        logger.info("Parsed output authorizers.xml location: 
${tool.outputAuthorizersPath}")
+
+        // Assert
+        assert !TestAppender.events.isEmpty()
+        assert TestAppender.events.any {
+            it.message =~ "The source authorizers.xml and destination 
authorizers.xml are identical \\[.*\\] so the original will be overwritten"
+        }
+    }
+
+    @Test
     void testShouldParseKeyArgument() {
         // Arrange
         def flags = ["-k", "--key"]
@@ -351,7 +406,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    void 
testParseShouldFailIfNiFiPropertiesAndLoginIdentityProviderBothMissing() {
+    void testParseShouldFailIfPropertiesAndProvidersMissing() {
         // Arrange
         ConfigEncryptionTool tool = new ConfigEncryptionTool()
 
@@ -360,16 +415,22 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 "-v -s password",
                 "-n",
                 "-l",
-                "-o output-nifi.properties -i 
output-login-identity-providers.xml",
+                "-a",
+                "-o output-nifi.properties -i 
output-login-identity-providers.xml -u output-authorizers.xml",
                 "-f flow.xml.gz",
         ]
 
-        final String NO_NFP_OR_LIP = "One or both of 
'-n'/'--${ConfigEncryptionTool.NIFI_PROPERTIES_ARG}' or 
'-l'/'--${ConfigEncryptionTool.LOGIN_IDENTITY_PROVIDERS_ARG}' must be provided 
unless '-x'/--'${ConfigEncryptionTool.DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG}' is 
specified"
+        final String NO_NFP_OR_LIP = "One or more of [" +
+                "'-n'/'--${ConfigEncryptionTool.NIFI_PROPERTIES_ARG}', " +
+                
"'-l'/'--${ConfigEncryptionTool.LOGIN_IDENTITY_PROVIDERS_ARG}', " +
+                "'-a'/'--${ConfigEncryptionTool.AUTHORIZERS_ARG}'" +
+                "] must be provided unless 
'-x'/--'${ConfigEncryptionTool.DO_NOT_ENCRYPT_NIFI_PROPERTIES_ARG}' is 
specified"
         final String MISSING_NFP_ARGUMENT = "Error parsing command line. 
(Missing argument for option: n)"
         final String MISSING_LIP_ARGUMENT = "Error parsing command line. 
(Missing argument for option: l)"
+        final String MISSING_A_ARGUMENT = "Error parsing command line. 
(Missing argument for option: a)"
         final String MIGRATE_NEEDS_NFP = "In order to migrate a flow.xml.gz, a 
nifi.properties file must also be specified via '-n'/'--niFiProperties'."
 
-        def ACCEPTABLE_ERROR_MSGS = [NO_NFP_OR_LIP, MISSING_NFP_ARGUMENT, 
MISSING_LIP_ARGUMENT, MIGRATE_NEEDS_NFP]
+        def ACCEPTABLE_ERROR_MSGS = [NO_NFP_OR_LIP, MISSING_NFP_ARGUMENT, 
MISSING_LIP_ARGUMENT, MISSING_A_ARGUMENT, MIGRATE_NEEDS_NFP]
 
         // Act
         invalidArgs.each { String badArgs ->
@@ -1457,7 +1518,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", inputPropertiesFile.path, "-b", 
bootstrapFile.path, "-o", outputPropertiesFile.path, "-k", KEY_HEX]
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -1489,7 +1550,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -1539,7 +1600,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", inputPropertiesFile.path, "-b", 
bootstrapFile.path, "-o", outputPropertiesFile.path, "-p", PASSWORD]
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -1571,7 +1632,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -1633,7 +1694,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         outputPropertiesFile.text = 
outputPropertiesFile.text.replace("nifi.sensitive.props.additional.keys=", 
"nifi.sensitive.props.additional.keys=nifi.ui.banner.text")
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -1665,7 +1726,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         logger.info("Invoked #main second time with ${args.join(" ")}")
         ConfigEncryptionTool.main(args)
@@ -1752,7 +1813,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         logger.info("Running [${scenario}] with args: ${localArgs}")
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 assert outputPropertiesFile.exists()
                 final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
@@ -1785,7 +1846,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         logger.info("Migrating key (${scenario}) with ${localArgs.join(" ")}")
         ConfigEncryptionTool.main(localArgs as String[])
@@ -1799,7 +1860,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
      * Ideally all of the combination tests would be a single test with 
iterative argument lists, but due to the System.exit(), it can only be captured 
once per test.
      */
     @Test
-    public void testShouldMigrateFromPasswordToPassword() {
+    void testShouldMigrateFromPasswordToPassword() {
         // Arrange
         String scenario = "password to password"
         def args = ["-w", PASSWORD, "-p", PASSWORD.reverse()]
@@ -1813,7 +1874,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    public void testShouldMigrateFromPasswordToKey() {
+    void testShouldMigrateFromPasswordToKey() {
         // Arrange
         String scenario = "password to key"
         def args = ["-w", PASSWORD, "-k", KEY_HEX]
@@ -1827,7 +1888,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    public void testShouldMigrateFromKeyToPassword() {
+    void testShouldMigrateFromKeyToPassword() {
         // Arrange
         String scenario = "key to password"
         def args = ["-e", PASSWORD_KEY_HEX, "-p", PASSWORD.reverse()]
@@ -1841,7 +1902,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    public void testShouldMigrateFromKeyToKey() {
+    void testShouldMigrateFromKeyToKey() {
         // Arrange
         String scenario = "key to key"
         def args = ["-e", PASSWORD_KEY_HEX, "-k", KEY_HEX]
@@ -2313,6 +2374,44 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
+    void 
testSerializeLoginIdentityProvidersAndPreserveFormatShouldHandleManyProviders() 
{
+        // Arrange
+        String loginIdentityProvidersPath = 
"src/test/resources/login-identity-providers-populated-with-many-providers.xml"
+        File loginIdentityProvidersFile = new File(loginIdentityProvidersPath)
+
+        File tmpDir = setupTmpDir()
+
+        File workingFile = new 
File("target/tmp/tmp-login-identity-providers.xml")
+        workingFile.delete()
+        Files.copy(loginIdentityProvidersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX_128
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        String plainXml = workingFile.text
+        String encryptedXml = tool.encryptLoginIdentityProviders(plainXml, 
KEY_HEX)
+        logger.info("Encrypted XML: \n${encryptedXml}")
+
+        // Act
+        def serializedLines = 
tool.serializeLoginIdentityProvidersAndPreserveFormat(encryptedXml, workingFile)
+        logger.info("Serialized lines: \n${serializedLines.join("\n")}")
+
+        // Assert
+
+        // Some empty lines will be removed
+        def trimmedLines = lines.collect { it.trim() }.findAll { it }
+        def trimmedSerializedLines = serializedLines.collect { it.trim() 
}.findAll { it }
+        assert trimmedLines.size() == trimmedSerializedLines.size()
+
+        // Ensure the replacement actually occurred
+        assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() 
== LIP_PASSWORD_LINE_COUNT
+    }
+
+    @Test
     void 
testSerializeLoginIdentityProvidersAndPreserveFormatShouldHandleEmptyFile() {
         // Arrange
         File tmpDir = setupTmpDir()
@@ -2344,6 +2443,52 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
+    void testWriteLoginIdentityProvidersShouldHandleUnreadableFile() {
+        // Arrange
+        String providersPath = 
"src/test/resources/login-identity-providers-populated.xml"
+        File providersFile = new File(providersPath)
+
+        setupTmpDir()
+
+        File workingFile = new 
File("target/tmp/tmp-login-identity-providers.xml")
+        workingFile.delete()
+        Files.copy(providersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        tool.loginIdentityProvidersPath = workingFile.path
+        String writtenPath = 
"target/tmp/tmp-login-identity-providers-written.xml"
+        tool.outputLoginIdentityProvidersPath = writtenPath
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        String plainXml = workingFile.text
+        String encryptedXml = tool.encryptLoginIdentityProviders(plainXml, 
KEY_HEX)
+        logger.info("Encrypted XML: \n${encryptedXml}")
+
+        tool.loginIdentityProviders = encryptedXml
+
+        // Remove the working file (simulating an external process)
+        workingFile.delete()
+
+        // Act
+        tool.writeLoginIdentityProviders()
+
+        // Assert
+        File writtenFile = new File(writtenPath)
+        List<String> writtenLines = writtenFile.readLines()
+        logger.info("Written lines: \n${writtenLines.join("\n")}")
+
+        // The output should contain only what was explicitly serialized in 
this operation (no pre-existing content)
+        assert writtenLines.join("\n") == encryptedXml.trim()
+
+        // Ensure the replacement actually occurred
+        assert writtenLines.findAll { it =~ "encryption=" }.size() == 
LIP_PASSWORD_LINE_COUNT
+    }
+
+    @Test
     void testShouldPerformFullOperationForLoginIdentityProviders() {
         // Arrange
         exit.expectSystemExitWithStatus(0)
@@ -2376,7 +2521,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final String updatedXmlContent = outputLIPFile.text
                 logger.info("Updated XML content: ${updatedXmlContent}")
 
@@ -2413,7 +2558,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -2459,7 +2604,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(PASSWORD_KEY_HEX)
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final String updatedXmlContent = outputLIPFile.text
                 logger.info("Updated XML content: ${updatedXmlContent}")
 
@@ -2494,7 +2639,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -2506,92 +2651,835 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     }
 
     @Test
-    void 
testShouldPerformFullOperationForNiFiPropertiesAndLoginIdentityProviders() {
+    void testShouldDecryptAuthorizers() {
         // Arrange
-        exit.expectSystemExitWithStatus(0)
+        String authorizersPath = 
"src/test/resources/authorizers-populated-encrypted.xml"
+        File authorizersFile = new File(authorizersPath)
 
-        File tmpDir = setupTmpDir()
+        setupTmpDir()
 
-        File emptyKeyFile = new 
File("src/test/resources/bootstrap_with_empty_master_key.conf")
-        File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
-        bootstrapFile.delete()
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
 
-        Files.copy(emptyKeyFile.toPath(), bootstrapFile.toPath())
-        final List<String> originalBootstrapLines = bootstrapFile.readLines()
-        String originalKeyLine = originalBootstrapLines.find {
-            it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
-        }
-        logger.info("Original key line from bootstrap.conf: 
${originalKeyLine}")
-        assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
+        // Sanity check for decryption
+        String cipherText = 
"q4r7WIgN0MaxdAKM||SGgdCTPGSFEcuH4RraMYEdeyVbOx93abdWTVSWvh1w+klA"
+        String EXPECTED_PASSWORD = "thisIsABadPassword"
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX_128)
+        assert spp.unprotect(cipherText) == EXPECTED_PASSWORD
 
-        final String EXPECTED_KEY_LINE = 
ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX
+        tool.keyHex = KEY_HEX_128
 
-        // Set up the NFP file
-        File inputPropertiesFile = new 
File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties")
-        File outputPropertiesFile = new File("target/tmp/tmp_nifi.properties")
-        outputPropertiesFile.delete()
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
 
-        NiFiProperties inputProperties = new 
NiFiPropertiesLoader().load(inputPropertiesFile)
-        logger.info("Loaded ${inputProperties.size()} properties from input 
file")
-        ProtectedNiFiProperties protectedInputProperties = new 
ProtectedNiFiProperties(inputProperties)
-        def originalSensitiveValues = 
protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key 
-> [(key): protectedInputProperties.getProperty(key)] }
-        logger.info("Original sensitive values: ${originalSensitiveValues}")
+        // Act
+        def decryptedLines = 
tool.decryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Decrypted lines: \n${decryptedLines.join("\n")}")
 
-        // Set up the LIP file
-        File inputLIPFile = new 
File("src/test/resources/login-identity-providers-populated.xml")
-        File outputLIPFile = new File("target/tmp/tmp-lip.xml")
-        outputLIPFile.delete()
+        // Assert
+        def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { it =~ ">thisIsABadPassword<" }
+        // Some lines were not encrypted originally so the encryption 
attribute would not have been updated
+        assert passwordLines.any { it =~ "encryption=\"none\"" }
+    }
 
-        String originalXmlContent = inputLIPFile.text
-        logger.info("Original XML content: ${originalXmlContent}")
+    @Test
+    void testShouldDecryptAuthorizersWithMultilineElements() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-encrypted-multiline.xml"
+        File authorizersFile = new File(authorizersPath)
 
-        String[] args = ["-n", inputPropertiesFile.path, "-l", 
inputLIPFile.path, "-b", bootstrapFile.path, "-i", outputLIPFile.path, "-o", 
outputPropertiesFile.path, "-k", KEY_HEX, "-v"]
+        setupTmpDir()
 
-        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
 
-        exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
-                final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
-                logger.info("Updated nifi.properties:")
-                logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
+        tool.keyHex = KEY_HEX_128
 
-                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
-                NiFiProperties updatedProperties = new 
NiFiPropertiesLoader().readProtectedPropertiesFromDisk(outputPropertiesFile)
-                assert updatedProperties.size() >= inputProperties.size()
-                originalSensitiveValues.every { String key, String 
originalValue ->
-                    assert updatedProperties.getProperty(key) != originalValue
-                }
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
 
-                // Check that the new NiFiProperties instance matches the 
output file (values still encrypted)
-                updatedProperties.getPropertyKeys().every { String key ->
-                    assert 
updatedPropertiesLines.contains("${key}=${updatedProperties.getProperty(key)}".toString())
-                }
+        // Act
+        def decryptedLines = 
tool.decryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Decrypted lines: \n${decryptedLines.join("\n")}")
 
-                final String updatedXmlContent = outputLIPFile.text
-                logger.info("Updated XML content: ${updatedXmlContent}")
+        // Assert
+        def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { it =~ ">thisIsABadPassword<" }
+        // Some lines were not encrypted originally so the encryption 
attribute would not have been updated
+        assert passwordLines.any { it =~ "encryption=\"none\"" }
+    }
 
-                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
-                def originalParsedXml = new 
XmlSlurper().parseText(originalXmlContent)
-                def updatedParsedXml = new 
XmlSlurper().parseText(updatedXmlContent)
-                assert originalParsedXml != updatedParsedXml
-                assert originalParsedXml.'**'.findAll { it.@encryption } != 
updatedParsedXml.'**'.findAll {
-                    it.@encryption
-                }
+    @Test
+    void testShouldDecryptAuthorizersWithMultipleElementsPerLine() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-encrypted-multiple-per-line.xml"
+        File authorizersFile = new File(authorizersPath)
 
-                def encryptedValues = updatedParsedXml.provider.find {
-                    it.identifier == 'ldap-provider'
-                }.property.findAll {
-                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
-                }
+        setupTmpDir()
 
-                encryptedValues.each {
-                    assert spp.unprotect(it.text()) == PASSWORD
-                }
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
 
-                // Check that the comments are still there
-                def trimmedLines = inputLIPFile.readLines().collect { 
it.trim() }.findAll { it }
-                def trimmedSerializedLines = 
updatedXmlContent.split("\n").collect { it.trim() }.findAll { it }
-                assert trimmedLines.size() == trimmedSerializedLines.size()
+        tool.keyHex = KEY_HEX_128
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        // Act
+        def decryptedLines = 
tool.decryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Decrypted lines: \n${decryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = decryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { it =~ ">thisIsABadPassword<" }
+        // Some lines were not encrypted originally so the encryption 
attribute would not have been updated
+        assert passwordLines.any { it =~ "encryption=\"none\"" }
+    }
+
+
+    @Test
+    void testDecryptAuthorizersShouldHandleCommentedElements() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-commented.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX_128
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        // Act
+        def decryptedLines = 
tool.decryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Decrypted lines: \n${decryptedLines.join("\n")}")
+
+        // Assert
+
+        // If no encrypted properties are found, the original input text is 
just returned (comments and formatting in tact)
+        assert decryptedLines == lines
+    }
+
+    @Test
+    void testShouldEncryptAuthorizers() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-populated.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { !it.contains(">thisIsABadPassword<") }
+        assert passwordLines.every { it.contains(encryptionScheme) }
+        passwordLines.each {
+            String ct = (it =~ ">(.*)</property>")[0][1]
+            logger.info("Cipher text: ${ct}")
+            assert spp.unprotect(ct) == PASSWORD
+        }
+    }
+
+    @Test
+    void testShouldEncryptAuthorizersWithEmptySensitiveElements() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-empty.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        def populatedPasswordLines = passwordLines.findAll { it =~ />.+</ }
+        assert populatedPasswordLines.every { 
!it.contains(">thisIsABadPassword<") }
+        assert populatedPasswordLines.every { it.contains(encryptionScheme) }
+        populatedPasswordLines.each {
+            String ct = (it =~ ">(.*)</property>")[0][1]
+            logger.info("Cipher text: ${ct}")
+            assert spp.unprotect(ct) == PASSWORD
+        }
+    }
+
+    @Test
+    void testShouldEncryptAuthorizersWithMultilineElements() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-multiline.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { !it.contains(">thisIsABadPassword<") }
+        assert passwordLines.every { it.contains(encryptionScheme) }
+        passwordLines.each {
+            String ct = (it =~ ">(.*)</property>")[0][1]
+            logger.info("Cipher text: ${ct}")
+            assert spp.unprotect(ct) == PASSWORD
+        }
+    }
+
+    @Test
+    void testShouldEncryptAuthorizersWithMultipleElementsPerLine() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-multiple-per-line.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        assert passwordLines.every { !it.contains(">thisIsABadPassword<") }
+        assert passwordLines.every { it.contains(encryptionScheme) }
+        passwordLines.each {
+            String ct = (it =~ ">(.*)</property>")[0][1]
+            logger.info("Cipher text: ${ct}")
+            assert spp.unprotect(ct) == PASSWORD
+        }
+    }
+
+    @Test
+    void testShouldEncryptAuthorizersWithRenamedProvider() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-renamed.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        String encryptionScheme = 
"encryption=\"aes/gcm/${getKeyLength(KEY_HEX)}\""
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+        assert lines.findAll { it =~ "ldap-user-group-provider" }.empty
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+        def passwordLines = encryptedLines.findAll { it =~ PASSWORD_PROP_REGEX 
}
+        assert passwordLines.size() == AUTHORIZERS_PASSWORD_LINE_COUNT
+        def populatedPasswordLines = passwordLines.findAll { it =~ />.+</ }
+        assert populatedPasswordLines.every { 
!it.contains(">thisIsABadPassword<") }
+        assert populatedPasswordLines.every { it.contains(encryptionScheme) }
+        populatedPasswordLines.each {
+            String ct = (it =~ ">(.*)</property>")[0][1]
+            logger.info("Cipher text: ${ct}")
+            assert spp.unprotect(ct) == PASSWORD
+        }
+    }
+
+    @Test
+    void testEncryptAuthorizersShouldHandleCommentedElements() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-commented.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX_128
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        // Act
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Assert
+
+        // If no sensitive properties are found, the original input text is 
just returned (comments and formatting in tact)
+        assert encryptedLines == lines
+    }
+
+    @Test
+    void testSerializeAuthorizersAndPreserveFormatShouldRespectComments() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-populated.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        String plainXml = workingFile.text
+        String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX)
+        logger.info("Encrypted XML: \n${encryptedXml}")
+
+        // Act
+        def serializedLines = 
tool.serializeAuthorizersAndPreserveFormat(encryptedXml, workingFile)
+        logger.info("Serialized lines: \n${serializedLines.join("\n")}")
+
+        // Assert
+
+        // Some empty lines will be removed
+        def trimmedLines = lines.collect { it.trim() }.findAll { it }
+        def trimmedSerializedLines = serializedLines.collect { it.trim() 
}.findAll { it }
+        assert trimmedLines.size() == trimmedSerializedLines.size()
+
+        // Ensure the replacement actually occurred
+        assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() 
== AUTHORIZERS_PASSWORD_LINE_COUNT
+    }
+
+    @Test
+    void 
testSerializeAuthorizersAndPreserveFormatShouldHandleRenamedProvider() {
+        // Arrange
+        String authorizersPath = 
"src/test/resources/authorizers-populated-renamed.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+        assert lines.findAll { it =~ "ldap-user-group-provider" }.empty
+
+        String plainXml = workingFile.text
+        String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX)
+        logger.info("Encrypted XML: \n${encryptedXml}")
+
+        // Act
+        def serializedLines = 
tool.serializeAuthorizersAndPreserveFormat(encryptedXml, workingFile)
+        logger.info("Serialized lines: \n${serializedLines.join("\n")}")
+
+        // Assert
+
+        // Some empty lines will be removed
+        def trimmedLines = lines.collect { it.trim() }.findAll { it }
+        def trimmedSerializedLines = serializedLines.collect { it.trim() 
}.findAll { it }
+        assert trimmedLines.size() == trimmedSerializedLines.size()
+
+        // Ensure the replacement actually occurred
+        assert trimmedSerializedLines.findAll { it =~ "encryption=" }.size() 
== AUTHORIZERS_PASSWORD_LINE_COUNT
+    }
+
+    @Test
+    void testSerializeAuthorizersAndPreserveFormatShouldHandleCommentedFile() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-commented.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        File tmpDir = setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        // If no sensitive properties are found, the original input text is 
just returned (comments and formatting in tact)
+        def encryptedLines = 
tool.encryptAuthorizers(lines.join("\n")).split("\n")
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+        assert encryptedLines == lines
+
+        // Act
+        def serializedLines = 
ConfigEncryptionTool.serializeAuthorizersAndPreserveFormat(encryptedLines.join("\n"),
 workingFile)
+        logger.info("Serialized lines: \n${serializedLines.join("\n")}")
+
+        // Assert
+        assert serializedLines == encryptedLines
+        assert TestAppender.events.any {
+            it.renderedMessage =~ "No provider element with class 
org.apache.nifi.ldap.tenants.LdapUserGroupProvider found in XML content; " +
+                    "the file could be empty or the element may be missing or 
commented out"
+        }
+    }
+
+    @Test
+    void testSerializeAuthorizersAndPreserveFormatShouldHandleEmptyFile() {
+        // Arrange
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        workingFile.createNewFile()
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        // If no sensitive properties are found, the original input text is 
just returned (comments and formatting in tact)
+        def encryptedLines = lines
+        logger.info("Encrypted lines: \n${encryptedLines.join("\n")}")
+
+        // Act
+        def serializedLines = 
ConfigEncryptionTool.serializeAuthorizersAndPreserveFormat(encryptedLines.join("\n"),
 workingFile)
+        logger.info("Serialized lines: \n${serializedLines.join("\n")}")
+
+        // Assert
+        assert serializedLines.findAll { it }.isEmpty()
+        assert TestAppender.events.any {
+            it.renderedMessage =~ "No provider element with class 
org.apache.nifi.ldap.tenants.LdapUserGroupProvider found in XML content; " +
+                    "the file could be empty or the element may be missing or 
commented out"
+        }
+    }
+
+    @Test
+    void testWriteAuthorizersShouldHandleUnreadableFile() {
+        // Arrange
+        String authorizersPath = "src/test/resources/authorizers-populated.xml"
+        File authorizersFile = new File(authorizersPath)
+
+        setupTmpDir()
+
+        File workingFile = new File("target/tmp/tmp-authorizers.xml")
+        workingFile.delete()
+        Files.copy(authorizersFile.toPath(), workingFile.toPath())
+        ConfigEncryptionTool tool = new ConfigEncryptionTool()
+        tool.isVerbose = true
+
+        tool.keyHex = KEY_HEX
+        tool.authorizersPath = workingFile.path
+        String writtenPath = "target/tmp/tmp-authorizers-written.xml"
+        tool.outputAuthorizersPath = writtenPath
+
+        def lines = workingFile.readLines()
+        logger.info("Read lines: \n${lines.join("\n")}")
+
+        String plainXml = workingFile.text
+        String encryptedXml = tool.encryptAuthorizers(plainXml, KEY_HEX)
+        logger.info("Encrypted XML: \n${encryptedXml}")
+
+        tool.authorizers = encryptedXml
+
+        // Remove the working file (simulating an external process)
+        workingFile.delete()
+
+        // Act
+        tool.writeAuthorizers()
+
+        // Assert
+        File writtenFile = new File(writtenPath)
+        List<String> writtenLines = writtenFile.readLines()
+        logger.info("Written lines: \n${writtenLines.join("\n")}")
+
+        // The output should contain only what was explicitly serialized in 
this operation (no pre-existing content)
+        assert writtenLines.join("\n") == encryptedXml.trim()
+
+        // Ensure the replacement actually occurred
+        assert writtenLines.findAll { it =~ "encryption=" }.size() == 
AUTHORIZERS_PASSWORD_LINE_COUNT
+    }
+
+    @Test
+    void testShouldPerformFullOperationForAuthorizers() {
+        // Arrange
+        exit.expectSystemExitWithStatus(0)
+
+        File tmpDir = setupTmpDir()
+
+        File emptyKeyFile = new 
File("src/test/resources/bootstrap_with_empty_master_key.conf")
+        File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
+        bootstrapFile.delete()
+
+        Files.copy(emptyKeyFile.toPath(), bootstrapFile.toPath())
+        final List<String> originalBootstrapLines = bootstrapFile.readLines()
+        String originalKeyLine = originalBootstrapLines.find {
+            it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
+        }
+        logger.info("Original key line from bootstrap.conf: 
${originalKeyLine}")
+        assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
+
+        final String EXPECTED_KEY_LINE = 
ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX
+
+        File inputAuthorizersFile = new 
File("src/test/resources/authorizers-populated.xml")
+        File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml")
+        outputAuthorizersFile.delete()
+
+        String originalXmlContent = inputAuthorizersFile.text
+        logger.info("Original XML content: ${originalXmlContent}")
+
+        String[] args = ["-a", inputAuthorizersFile.path, "-b", 
bootstrapFile.path, "-u", outputAuthorizersFile.path, "-k", KEY_HEX, "-v"]
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        exit.checkAssertionAfterwards(new Assertion() {
+            void checkAssertion() {
+                final String updatedXmlContent = outputAuthorizersFile.text
+                logger.info("Updated XML content: ${updatedXmlContent}")
+
+                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
+                def originalParsedXml = new 
XmlSlurper().parseText(originalXmlContent)
+                def updatedParsedXml = new 
XmlSlurper().parseText(updatedXmlContent)
+                assert originalParsedXml != updatedParsedXml
+                assert originalParsedXml.'**'.findAll { it.@encryption } != 
updatedParsedXml.'**'.findAll {
+                    it.@encryption
+                }
+
+                def encryptedValues = updatedParsedXml.userGroupProvider.find {
+                    it.identifier == 'ldap-user-group-provider'
+                }.property.findAll {
+                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
+                }
+
+                encryptedValues.each {
+                    assert spp.unprotect(it.text()) == PASSWORD
+                }
+
+                // Check that the key was persisted to the bootstrap.conf
+                final List<String> updatedBootstrapLines = 
bootstrapFile.readLines()
+                String updatedKeyLine = updatedBootstrapLines.find {
+                    it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
+                }
+                logger.info("Updated key line: ${updatedKeyLine}")
+
+                assert updatedKeyLine == EXPECTED_KEY_LINE
+                assert originalBootstrapLines.size() == 
updatedBootstrapLines.size()
+
+                // Clean up
+                outputAuthorizersFile.deleteOnExit()
+                bootstrapFile.deleteOnExit()
+                tmpDir.deleteOnExit()
+            }
+        })
+
+        // Act
+        ConfigEncryptionTool.main(args)
+        logger.info("Invoked #main with ${args.join(" ")}")
+
+        // Assert
+
+        // Assertions defined above
+    }
+
+    @Test
+    void testShouldPerformFullOperationMigratingAuthorizers() {
+        // Arrange
+        exit.expectSystemExitWithStatus(0)
+
+        File tmpDir = setupTmpDir()
+
+        // Start with 128-bit encryption and go to whatever is supported on 
this system
+        File emptyKeyFile = new 
File("src/test/resources/bootstrap_with_master_key_128.conf")
+        File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
+        bootstrapFile.delete()
+
+        Files.copy(emptyKeyFile.toPath(), bootstrapFile.toPath())
+        final List<String> originalBootstrapLines = bootstrapFile.readLines()
+        String originalKeyLine = originalBootstrapLines.find {
+            it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
+        }
+        logger.info("Original key line from bootstrap.conf: 
${originalKeyLine}")
+        assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + 
KEY_HEX_128
+
+        final String EXPECTED_KEY_LINE = 
ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + PASSWORD_KEY_HEX
+
+        File inputAuthorizersFile = new 
File("src/test/resources/authorizers-populated-encrypted.xml")
+        File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml")
+        outputAuthorizersFile.delete()
+
+        String originalXmlContent = inputAuthorizersFile.text
+        logger.info("Original XML content: ${originalXmlContent}")
+
+        // Migrate from KEY_HEX_128 to PASSWORD_KEY_HEX
+        String[] args = ["-a", inputAuthorizersFile.path, "-b", 
bootstrapFile.path, "-u", outputAuthorizersFile.path, "-m", "-e", KEY_HEX_128, 
"-k", PASSWORD_KEY_HEX, "-v"]
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(PASSWORD_KEY_HEX)
+
+        exit.checkAssertionAfterwards(new Assertion() {
+            void checkAssertion() {
+                final String updatedXmlContent = outputAuthorizersFile.text
+                logger.info("Updated XML content: ${updatedXmlContent}")
+
+                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
+                def originalParsedXml = new 
XmlSlurper().parseText(originalXmlContent)
+                def updatedParsedXml = new 
XmlSlurper().parseText(updatedXmlContent)
+                assert originalParsedXml != updatedParsedXml
+//                assert originalParsedXml.'**'.findAll { it.@encryption } != 
updatedParsedXml.'**'.findAll { it.@encryption }
+
+                def encryptedValues = updatedParsedXml.userGroupProvider.find {
+                    it.identifier == 'ldap-user-group-provider'
+                }.property.findAll {
+                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
+                }
+
+                encryptedValues.each {
+                    assert spp.unprotect(it.text()) == PASSWORD
+                }
+
+                // Check that the key was persisted to the bootstrap.conf
+                final List<String> updatedBootstrapLines = 
bootstrapFile.readLines()
+                String updatedKeyLine = updatedBootstrapLines.find {
+                    it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
+                }
+                logger.info("Updated key line: ${updatedKeyLine}")
+
+                assert updatedKeyLine == EXPECTED_KEY_LINE
+                assert originalBootstrapLines.size() == 
updatedBootstrapLines.size()
+
+                // Clean up
+                outputAuthorizersFile.deleteOnExit()
+                bootstrapFile.deleteOnExit()
+                tmpDir.deleteOnExit()
+            }
+        })
+
+        // Act
+        ConfigEncryptionTool.main(args)
+        logger.info("Invoked #main with ${args.join(" ")}")
+
+        // Assert
+
+        // Assertions defined above
+    }
+
+    @Test
+    void 
testShouldPerformFullOperationForNiFiPropertiesAndLoginIdentityProvidersAndAuthorizers()
 {
+        // Arrange
+        exit.expectSystemExitWithStatus(0)
+
+        File tmpDir = setupTmpDir()
+
+        File emptyKeyFile = new 
File("src/test/resources/bootstrap_with_empty_master_key.conf")
+        File bootstrapFile = new File("target/tmp/tmp_bootstrap.conf")
+        bootstrapFile.delete()
+
+        Files.copy(emptyKeyFile.toPath(), bootstrapFile.toPath())
+        final List<String> originalBootstrapLines = bootstrapFile.readLines()
+        String originalKeyLine = originalBootstrapLines.find {
+            it.startsWith(ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX)
+        }
+        logger.info("Original key line from bootstrap.conf: 
${originalKeyLine}")
+        assert originalKeyLine == ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX
+
+        final String EXPECTED_KEY_LINE = 
ConfigEncryptionTool.BOOTSTRAP_KEY_PREFIX + KEY_HEX
+
+        // Set up the NFP file
+        File inputPropertiesFile = new 
File("src/test/resources/nifi_with_sensitive_properties_unprotected.properties")
+        File outputPropertiesFile = new File("target/tmp/tmp_nifi.properties")
+        outputPropertiesFile.delete()
+
+        NiFiProperties inputProperties = new 
NiFiPropertiesLoader().load(inputPropertiesFile)
+        logger.info("Loaded ${inputProperties.size()} properties from input 
file")
+        ProtectedNiFiProperties protectedInputProperties = new 
ProtectedNiFiProperties(inputProperties)
+        def originalSensitiveValues = 
protectedInputProperties.getSensitivePropertyKeys().collectEntries { String key 
-> [(key): protectedInputProperties.getProperty(key)] }
+        logger.info("Original sensitive values: ${originalSensitiveValues}")
+
+        // Set up the LIP file
+        File inputLIPFile = new 
File("src/test/resources/login-identity-providers-populated.xml")
+        File outputLIPFile = new File("target/tmp/tmp-lip.xml")
+        outputLIPFile.delete()
+
+        String originalLipXmlContent = inputLIPFile.text
+        logger.info("Original LIP XML content: ${originalLipXmlContent}")
+
+        // Set up the Authorizers file
+        File inputAuthorizersFile = new 
File("src/test/resources/authorizers-populated.xml")
+        File outputAuthorizersFile = new File("target/tmp/tmp-authorizers.xml")
+        outputAuthorizersFile.delete()
+
+        String originalAuthorizersXmlContent = inputAuthorizersFile.text
+        logger.info("Original Authorizers XML content: 
${originalAuthorizersXmlContent}")
+
+        String[] args = [
+                "-n", inputPropertiesFile.path,
+                "-l", inputLIPFile.path,
+                "-a", inputAuthorizersFile.path,
+                "-b", bootstrapFile.path,
+                "-o", outputPropertiesFile.path,
+                "-i", outputLIPFile.path,
+                "-u", outputAuthorizersFile.path,
+                "-k", KEY_HEX,
+                "-v"]
+
+        AESSensitivePropertyProvider spp = new 
AESSensitivePropertyProvider(KEY_HEX)
+
+        exit.checkAssertionAfterwards(new Assertion() {
+            void checkAssertion() {
+
+                /*** NiFi Properties Assertions ***/
+
+                final List<String> updatedPropertiesLines = 
outputPropertiesFile.readLines()
+                logger.info("Updated nifi.properties:")
+                logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
+
+                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
+                NiFiProperties updatedProperties = new 
NiFiPropertiesLoader().readProtectedPropertiesFromDisk(outputPropertiesFile)
+                assert updatedProperties.size() >= inputProperties.size()
+                originalSensitiveValues.every { String key, String 
originalValue ->
+                    assert updatedProperties.getProperty(key) != originalValue
+                }
+
+                // Check that the new NiFiProperties instance matches the 
output file (values still encrypted)
+                updatedProperties.getPropertyKeys().every { String key ->
+                    assert 
updatedPropertiesLines.contains("${key}=${updatedProperties.getProperty(key)}".toString())
+                }
+
+                /*** Login Identity Providers Assertions ***/
+
+                final String updatedLipXmlContent = outputLIPFile.text
+                logger.info("Updated LIP XML content: ${updatedLipXmlContent}")
+                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
+                def originalLipParsedXml = new 
XmlSlurper().parseText(originalLipXmlContent)
+                def updatedLipParsedXml = new 
XmlSlurper().parseText(updatedLipXmlContent)
+                assert originalLipParsedXml != updatedLipParsedXml
+                assert originalLipParsedXml.'**'.findAll { it.@encryption } != 
updatedLipParsedXml.'**'.findAll {
+                    it.@encryption
+                }
+                def lipEncryptedValues = updatedLipParsedXml.provider.find {
+                    it.identifier == 'ldap-provider'
+                }.property.findAll {
+                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
+                }
+                lipEncryptedValues.each {
+                    assert spp.unprotect(it.text()) == PASSWORD
+                }
+                // Check that the comments are still there
+                def lipTrimmedLines = inputLIPFile.readLines().collect { 
it.trim() }.findAll { it }
+                def lipTrimmedSerializedLines = 
updatedLipXmlContent.split("\n").collect { it.trim() }.findAll { it }
+                assert lipTrimmedLines.size() == 
lipTrimmedSerializedLines.size()
+
+                /*** Authorizers Assertions ***/
+
+                final String updatedAuthorizersXmlContent = 
outputAuthorizersFile.text
+                logger.info("Updated Authorizers XML content: 
${updatedAuthorizersXmlContent}")
+                // Check that the output values for sensitive properties are 
not the same as the original (i.e. it was encrypted)
+                def originalAuthorizersParsedXml = new 
XmlSlurper().parseText(originalAuthorizersXmlContent)
+                def updatedAuthorizersParsedXml = new 
XmlSlurper().parseText(updatedAuthorizersXmlContent)
+                assert originalAuthorizersParsedXml != 
updatedAuthorizersParsedXml
+                assert originalAuthorizersParsedXml.'**'.findAll { 
it.@encryption } != updatedAuthorizersParsedXml.'**'.findAll {
+                    it.@encryption
+                }
+                def authorizersEncryptedValues = 
updatedAuthorizersParsedXml.userGroupProvider.find {
+                    it.identifier == 'ldap-user-group-provider'
+                }.property.findAll {
+                    it.@name =~ "Password" && it.@encryption =~ 
"aes/gcm/\\d{3}"
+                }
+                authorizersEncryptedValues.each {
+                    assert spp.unprotect(it.text()) == PASSWORD
+                }
+                // Check that the comments are still there
+                def authorizersTrimmedLines = 
inputAuthorizersFile.readLines().collect { it.trim() }.findAll { it }
+                def authorizersTrimmedSerializedLines = 
updatedAuthorizersXmlContent.split("\n").collect { it.trim() }.findAll { it }
+                assert authorizersTrimmedLines.size() == 
authorizersTrimmedSerializedLines.size()
+
+                /*** Bootstrap assertions ***/
 
                 // Check that the key was persisted to the bootstrap.conf
                 final List<String> updatedBootstrapLines = 
bootstrapFile.readLines()
@@ -2606,10 +3494,11 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                 // Clean up
                 outputPropertiesFile.deleteOnExit()
                 outputLIPFile.deleteOnExit()
+                outputAuthorizersFile.deleteOnExit()
                 bootstrapFile.deleteOnExit()
                 tmpDir.deleteOnExit()
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -2726,7 +3615,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", 
workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword]
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
workingNiFiPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -2769,7 +3658,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                     assert ConfigEncryptionTool.decryptFlowElement(it, 
newFlowPassword) == "thisIsABadPassword"
                 }
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -2834,7 +3723,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", 
workingFlowXmlFile.path, "-x", "-v", "-s", newFlowPassword]
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
workingNiFiPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -2874,7 +3763,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                     assert ConfigEncryptionTool.decryptFlowElement(it, 
newFlowPassword) == "thisIsABadPassword"
                 }
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -2950,7 +3839,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
         String[] args = ["-n", workingNiFiPropertiesFile.path, "-f", 
workingFlowXmlFile.path, "-b", bootstrapFile.path, "-x", "-v", "-s", 
newFlowPassword]
 
         exit.checkAssertionAfterwards(new Assertion() {
-            public void checkAssertion() {
+            void checkAssertion() {
                 final List<String> updatedPropertiesLines = 
workingNiFiPropertiesFile.readLines()
                 logger.info("Updated nifi.properties:")
                 logger.info("\n" * 2 + updatedPropertiesLines.join("\n"))
@@ -3015,7 +3904,7 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
                     assert ConfigEncryptionTool.decryptFlowElement(it, 
newFlowPassword) == "thisIsABadPassword"
                 }
             }
-        });
+        })
 
         // Act
         ConfigEncryptionTool.main(args)
@@ -3550,19 +4439,19 @@ class ConfigEncryptionToolTest extends GroovyTestCase {
     // TODO: Test with 128/256-bit available
 }
 
-public class TestAppender extends AppenderSkeleton {
-    static final List<LoggingEvent> events = new ArrayList<>();
+class TestAppender extends AppenderSkeleton {
+    static final List<LoggingEvent> events = new ArrayList<>()
 
     @Override
     protected void append(LoggingEvent e) {
         synchronized (events) {
-            events.add(e);
+            events.add(e)
         }
     }
 
-    public static void reset() {
+    static void reset() {
         synchronized (events) {
-            events.clear();
+            events.clear()
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/482f3719/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml
----------------------------------------------------------------------
diff --git 
a/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml
 
b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml
new file mode 100644
index 0000000..3808404
--- /dev/null
+++ 
b/nifi-toolkit/nifi-toolkit-encrypt-config/src/test/resources/authorizers-commented.xml
@@ -0,0 +1,309 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!--
+  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.
+-->
+<!--
+    This file lists the userGroupProviders, accessPolicyProviders, and 
authorizers to use when running securely. In order
+    to use a specific authorizer it must be configured here and it's 
identifier must be specified in the nifi.properties file.
+    If the authorizer is a managedAuthorizer, it may need to be configured 
with an accessPolicyProvider and an userGroupProvider.
+    This file allows for configuration of them, but they must be configured in 
order:
+
+    ...
+    all userGroupProviders
+    all accessPolicyProviders
+    all Authorizers
+    ...
+-->
+<authorizers>
+
+    <!--
+        The FileUserGroupProvider will provide support for managing users and 
groups which is backed by a file
+        on the local file system.
+
+        - Users File - The file where the FileUserGroupProvider will store 
users and groups.
+
+        - Legacy Authorized Users File - The full path to an existing 
authorized-users.xml that will be automatically
+            be used to load the users and groups into the Users File.
+
+        - Initial User Identity [unique key] - The identity of a users and 
systems to seed the Users File. The name of
+            each property must be unique, for example: "Initial User Identity 
A", "Initial User Identity B",
+            "Initial User Identity C" or "Initial User Identity 1", "Initial 
User Identity 2", "Initial User Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the user identities,
+            so the values should be the unmapped identities (i.e. full DN from 
a certificate).
+    -->
+    <userGroupProvider>
+        <identifier>file-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.FileUserGroupProvider</class>
+        <property name="Users File">./conf/users.xml</property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Initial User Identity 1"></property>
+    </userGroupProvider>
+
+    <!--
+        The LdapUserGroupProvider will retrieve users and groups from an LDAP 
server. The users and groups
+        are not configurable.
+
+        'Authentication Strategy' - How the connection to the LDAP server is 
authenticated. Possible
+            values are ANONYMOUS, SIMPLE, LDAPS, or START_TLS.
+
+        'Manager DN' - The DN of the manager that is used to bind to the LDAP 
server to search for users.
+        'Manager Password' - The password of the manager that is used to bind 
to the LDAP server to
+            search for users.
+
+        'TLS - Keystore' - Path to the Keystore that is used when connecting 
to LDAP using LDAPS or START_TLS.
+        'TLS - Keystore Password' - Password for the Keystore that is used 
when connecting to LDAP
+            using LDAPS or START_TLS.
+        'TLS - Keystore Type' - Type of the Keystore that is used when 
connecting to LDAP using
+            LDAPS or START_TLS (i.e. JKS or PKCS12).
+        'TLS - Truststore' - Path to the Truststore that is used when 
connecting to LDAP using LDAPS or START_TLS.
+        'TLS - Truststore Password' - Password for the Truststore that is used 
when connecting to
+            LDAP using LDAPS or START_TLS.
+        'TLS - Truststore Type' - Type of the Truststore that is used when 
connecting to LDAP using
+            LDAPS or START_TLS (i.e. JKS or PKCS12).
+        'TLS - Client Auth' - Client authentication policy when connecting to 
LDAP using LDAPS or START_TLS.
+            Possible values are REQUIRED, WANT, NONE.
+        'TLS - Protocol' - Protocol to use when connecting to LDAP using LDAPS 
or START_TLS. (i.e. TLS,
+            TLSv1.1, TLSv1.2, etc).
+        'TLS - Shutdown Gracefully' - Specifies whether the TLS should be shut 
down gracefully
+            before the target context is closed. Defaults to false.
+
+        'Referral Strategy' - Strategy for handling referrals. Possible values 
are FOLLOW, IGNORE, THROW.
+        'Connect Timeout' - Duration of connect timeout. (i.e. 10 secs).
+        'Read Timeout' - Duration of read timeout. (i.e. 10 secs).
+
+        'Url' - Space-separated list of URLs of the LDAP servers (i.e. 
ldap://<hostname>:<port>).
+        'Page Size' - Sets the page size when retrieving users and groups. If 
not specified, no paging is performed.
+        'Sync Interval' - Duration of time between syncing users and groups 
(i.e. 30 mins). Minimum allowable value is 10 secs.
+
+        'User Search Base' - Base DN for searching for users (i.e. 
ou=users,o=nifi). Required to search users.
+        'User Object Class' - Object class for identifying users (i.e. 
person). Required if searching users.
+        'User Search Scope' - Search scope for searching users (ONE_LEVEL, 
OBJECT, or SUBTREE). Required if searching users.
+        'User Search Filter' - Filter for searching for users against the 
'User Search Base' (i.e. (memberof=cn=team1,ou=groups,o=nifi) ). Optional.
+        'User Identity Attribute' - Attribute to use to extract user identity 
(i.e. cn). Optional. If not set, the entire DN is used.
+        'User Group Name Attribute' - Attribute to use to define group 
membership (i.e. memberof). Optional. If not set
+            group membership will not be calculated through the users. Will 
rely on group membership being defined
+            through 'Group Member Attribute' if set. The value of this 
property is the name of the attribute in the user ldap entry that
+            associates them with a group. The value of that user attribute 
could be a dn or group name for instance. What value is expected
+            is configured in the 'User Group Name Attribute - Referenced Group 
Attribute'.
+        'User Group Name Attribute - Referenced Group Attribute' - If blank, 
the value of the attribute defined in 'User Group Name Attribute'
+            is expected to be the full dn of the group. If not blank, this 
property will define the attribute of the group ldap entry that
+            the value of the attribute defined in 'User Group Name Attribute' 
is referencing (i.e. name). Use of this property requires that
+            'Group Search Base' is also configured.
+
+        'Group Search Base' - Base DN for searching for groups (i.e. 
ou=groups,o=nifi). Required to search groups.
+        'Group Object Class' - Object class for identifying groups (i.e. 
groupOfNames). Required if searching groups.
+        'Group Search Scope' - Search scope for searching groups (ONE_LEVEL, 
OBJECT, or SUBTREE). Required if searching groups.
+        'Group Search Filter' - Filter for searching for groups against the 
'Group Search Base'. Optional.
+        'Group Name Attribute' - Attribute to use to extract group name (i.e. 
cn). Optional. If not set, the entire DN is used.
+        'Group Member Attribute' - Attribute to use to define group membership 
(i.e. member). Optional. If not set
+            group membership will not be calculated through the groups. Will 
rely on group membership being defined
+            through 'User Group Name Attribute' if set. The value of this 
property is the name of the attribute in the group ldap entry that
+            associates them with a user. The value of that group attribute 
could be a dn or memberUid for instance. What value is expected
+            is configured in the 'Group Member Attribute - Referenced User 
Attribute'. (i.e. member: cn=User 1,ou=users,o=nifi vs. memberUid: user1)
+        'Group Member Attribute - Referenced User Attribute' - If blank, the 
value of the attribute defined in 'Group Member Attribute'
+            is expected to be the full dn of the user. If not blank, this 
property will define the attribute of the user ldap entry that
+            the value of the attribute defined in 'Group Member Attribute' is 
referencing (i.e. uid). Use of this property requires that
+            'User Search Base' is also configured. (i.e. member: cn=User 
1,ou=users,o=nifi vs. memberUid: user1)
+
+        NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the user identities.
+            Group names are not mapped.
+    -->
+    <!-- To enable the ldap-user-group-provider remove 2 lines. This is 1 of 2.
+    <userGroupProvider>
+        <identifier>ldap-user-group-provider</identifier>
+        <class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
+        <property name="Authentication Strategy">START_TLS</property>
+
+        <property name="Manager DN"></property>
+        <property name="Manager Password"></property>
+
+        <property name="TLS - Keystore"></property>
+        <property name="TLS - Keystore Password"></property>
+        <property name="TLS - Keystore Type"></property>
+        <property name="TLS - Truststore"></property>
+        <property name="TLS - Truststore Password"></property>
+        <property name="TLS - Truststore Type"></property>
+        <property name="TLS - Client Auth"></property>
+        <property name="TLS - Protocol"></property>
+        <property name="TLS - Shutdown Gracefully"></property>
+
+        <property name="Referral Strategy">FOLLOW</property>
+        <property name="Connect Timeout">10 secs</property>
+        <property name="Read Timeout">10 secs</property>
+
+        <property name="Url"></property>
+        <property name="Page Size"></property>
+        <property name="Sync Interval">30 mins</property>
+
+        <property name="User Search Base"></property>
+        <property name="User Object Class">person</property>
+        <property name="User Search Scope">ONE_LEVEL</property>
+        <property name="User Search Filter"></property>
+        <property name="User Identity Attribute"></property>
+        <property name="User Group Name Attribute"></property>
+        <property name="User Group Name Attribute - Referenced Group 
Attribute"></property>
+
+        <property name="Group Search Base"></property>
+        <property name="Group Object Class">group</property>
+        <property name="Group Search Scope">ONE_LEVEL</property>
+        <property name="Group Search Filter"></property>
+        <property name="Group Name Attribute"></property>
+        <property name="Group Member Attribute"></property>
+        <property name="Group Member Attribute - Referenced User 
Attribute"></property>
+    </userGroupProvider>
+    To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. -->
+
+    <!--
+        The CompositeUserGroupProvider will provide support for retrieving 
users and groups from multiple sources.
+
+        - User Group Provider [unique key] - The identifier of user group 
providers to load from. The name of
+            each property must be unique, for example: "User Group Provider 
A", "User Group Provider B",
+            "User Group Provider C" or "User Group Provider 1", "User Group 
Provider 2", "User Group Provider 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties are 
not applied in this implementation. This behavior
+            would need to be applied by the base implementation.
+    -->
+    <!-- To enable the composite-user-group-provider remove 2 lines. This is 1 
of 2.
+    <userGroupProvider>
+        <identifier>composite-user-group-provider</identifier>
+        <class>org.apache.nifi.authorization.CompositeUserGroupProvider</class>
+        <property name="User Group Provider 1"></property>
+    </userGroupProvider>
+    To enable the composite-user-group-provider remove 2 lines. This is 2 of 
2. -->
+
+    <!--
+        The CompositeConfigurableUserGroupProvider will provide support for 
retrieving users and groups from multiple sources.
+        Additionally, a single configurable user group provider is required. 
Users from the configurable user group provider
+        are configurable, however users loaded from one of the User Group 
Provider [unique key] will not be.
+
+        - Configurable User Group Provider - A configurable user group 
provider.
+
+        - User Group Provider [unique key] - The identifier of user group 
providers to load from. The name of
+            each property must be unique, for example: "User Group Provider 
A", "User Group Provider B",
+            "User Group Provider C" or "User Group Provider 1", "User Group 
Provider 2", "User Group Provider 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties are 
not applied in this implementation. This behavior
+            would need to be applied by the base implementation.
+    -->
+    <!-- To enable the composite-configurable-user-group-provider remove 2 
lines. This is 1 of 2.
+    <userGroupProvider>
+        <identifier>composite-configurable-user-group-provider</identifier>
+        
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
+        <property name="Configurable User Group 
Provider">file-user-group-provider</property>
+        <property name="User Group Provider 1"></property>
+    </userGroupProvider>
+    To enable the composite-configurable-user-group-provider remove 2 lines. 
This is 2 of 2. -->
+
+    <!--
+        The FileAccessPolicyProvider will provide support for managing access 
policies which is backed by a file
+        on the local file system.
+
+        - User Group Provider - The identifier for an User Group Provider 
defined above that will be used to access
+            users and groups for use in the managed access policies.
+
+        - Authorizations File - The file where the FileAccessPolicyProvider 
will store policies.
+
+        - Initial Admin Identity - The identity of an initial admin user that 
will be granted access to the UI and
+            given the ability to create additional users, groups, and 
policies. The value of this property could be
+            a DN when using certificates or LDAP, or a Kerberos principal. 
This property will only be used when there
+            are no other policies defined. If this property is specified then 
a Legacy Authorized Users File can not be specified.
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the initial admin identity,
+            so the value should be the unmapped identity. This identity must 
be found in the configured User Group Provider.
+
+        - Legacy Authorized Users File - The full path to an existing 
authorized-users.xml that will be automatically
+            converted to the new authorizations model. If this property is 
specified then an Initial Admin Identity can
+            not be specified, and this property will only be used when there 
are no other users, groups, and policies defined.
+
+            NOTE: Any users in the legacy users file must be found in the 
configured User Group Provider.
+
+        - Node Identity [unique key] - The identity of a NiFi cluster node. 
When clustered, a property for each node
+            should be defined, so that every node knows about every other 
node. If not clustered these properties can be ignored.
+            The name of each property must be unique, for example for a three 
node cluster:
+            "Node Identity A", "Node Identity B", "Node Identity C" or "Node 
Identity 1", "Node Identity 2", "Node Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the node identities,
+            so the values should be the unmapped identities (i.e. full DN from 
a certificate). This identity must be found
+            in the configured User Group Provider.
+    -->
+    <accessPolicyProvider>
+        <identifier>file-access-policy-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
+        <property name="User Group 
Provider">file-user-group-provider</property>
+        <property name="Authorizations 
File">./conf/authorizations.xml</property>
+        <property name="Initial Admin Identity"></property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Node Identity 1"></property>
+    </accessPolicyProvider>
+
+    <!--
+        The StandardManagedAuthorizer. This authorizer implementation must be 
configured with the
+        Access Policy Provider which it will use to access and manage users, 
groups, and policies.
+        These users, groups, and policies will be used to make all access 
decisions during authorization
+        requests.
+
+        - Access Policy Provider - The identifier for an Access Policy 
Provider defined above.
+    -->
+    <authorizer>
+        <identifier>managed-authorizer</identifier>
+        <class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
+        <property name="Access Policy 
Provider">file-access-policy-provider</property>
+    </authorizer>
+
+    <!--
+        NOTE: This Authorizer has been replaced with the more granular 
approach configured above with the Standard
+        Managed Authorizer. However, it is still available for backwards 
compatibility reasons.
+
+        The FileAuthorizer is NiFi's provided authorizer and has the following 
properties:
+
+        - Authorizations File - The file where the FileAuthorizer will store 
policies.
+
+        - Users File - The file where the FileAuthorizer will store users and 
groups.
+
+        - Initial Admin Identity - The identity of an initial admin user that 
will be granted access to the UI and
+            given the ability to create additional users, groups, and 
policies. The value of this property could be
+            a DN when using certificates or LDAP, or a Kerberos principal. 
This property will only be used when there
+            are no other users, groups, and policies defined. If this property 
is specified then a Legacy Authorized
+            Users File can not be specified.
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the initial admin identity,
+            so the value should be the unmapped identity.
+
+        - Legacy Authorized Users File - The full path to an existing 
authorized-users.xml that will be automatically
+            converted to the new authorizations model. If this property is 
specified then an Initial Admin Identity can
+            not be specified, and this property will only be used when there 
are no other users, groups, and policies defined.
+
+        - Node Identity [unique key] - The identity of a NiFi cluster node. 
When clustered, a property for each node
+            should be defined, so that every node knows about every other 
node. If not clustered these properties can be ignored.
+            The name of each property must be unique, for example for a three 
node cluster:
+            "Node Identity A", "Node Identity B", "Node Identity C" or "Node 
Identity 1", "Node Identity 2", "Node Identity 3"
+
+            NOTE: Any identity mapping rules specified in nifi.properties will 
also be applied to the node identities,
+            so the values should be the unmapped identities (i.e. full DN from 
a certificate).
+    -->
+    <!-- <authorizer>
+        <identifier>file-provider</identifier>
+        <class>org.apache.nifi.authorization.FileAuthorizer</class>
+        <property name="Authorizations 
File">./conf/authorizations.xml</property>
+        <property name="Users File">./conf/users.xml</property>
+        <property name="Initial Admin Identity"></property>
+        <property name="Legacy Authorized Users File"></property>
+
+        <property name="Node Identity 1"></property>
+    </authorizer>
+    -->
+</authorizers>
\ No newline at end of file

Reply via email to