Repository: nifi
Updated Branches:
  refs/heads/master 52bc23f5d -> c3b4872b5


NIFI-2389 Refactoring identity mapping and applying it to FileAuthorizer for 
initial admin, cluster nodes, and legacy authorized users. This closes #719


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/c3b4872b
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/c3b4872b
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/c3b4872b

Branch: refs/heads/master
Commit: c3b4872b552922058c6536e0810dabfe9ca20b10
Parents: 52bc23f
Author: Bryan Bende <[email protected]>
Authored: Mon Jul 25 16:15:49 2016 -0400
Committer: Matt Gilman <[email protected]>
Committed: Tue Jul 26 15:24:50 2016 -0400

----------------------------------------------------------------------
 .../nifi/authorization/FileAuthorizer.java      |  19 ++-
 .../nifi/authorization/FileAuthorizerTest.java  | 140 ++++++++++++++++++
 .../resources/authorized-users-with-dns.xml     |  35 +++++
 .../src/test/resources/flow-with-dns.xml.gz     | Bin 0 -> 784 bytes
 .../nifi-framework-authorization/pom.xml        |   4 +
 .../authorization/util/IdentityMapping.java     |  48 ++++++
 .../authorization/util/IdentityMappingUtil.java | 145 +++++++++++++++++++
 .../src/main/resources/conf/authorizers.xml     |   6 +
 .../security/NiFiAuthenticationProvider.java    | 138 +-----------------
 .../NiFiAuthenticationProviderTest.java         |  11 +-
 10 files changed, 402 insertions(+), 144 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
index 99672b6..3f714d4 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
@@ -27,6 +27,8 @@ import org.apache.nifi.authorization.file.generated.Policy;
 import org.apache.nifi.authorization.file.generated.Users;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.util.file.FileUtils;
@@ -106,6 +108,7 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
     private String legacyAuthorizedUsersFile;
     private Set<String> nodeIdentities;
     private List<PortDTO> ports = new ArrayList<>();
+    private List<IdentityMapping> identityMappings;
 
     private final AtomicReference<AuthorizationsHolder> authorizationsHolder = 
new AtomicReference<>();
 
@@ -160,9 +163,12 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
                 }
             }
 
+            // extract the identity mappings from nifi.properties if any are 
provided
+            identityMappings = 
Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
             // get the value of the initial admin identity
             final PropertyValue initialAdminIdentityProp = 
configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
-            initialAdminIdentity = initialAdminIdentityProp == null ? null : 
initialAdminIdentityProp.getValue();
+            initialAdminIdentity = initialAdminIdentityProp == null ? null : 
IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), 
identityMappings);
 
             // get the value of the legacy authorized users file
             final PropertyValue legacyAuthorizedUsersProp = 
configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE);
@@ -173,7 +179,7 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
             for (Map.Entry<String,String> entry : 
configurationContext.getProperties().entrySet()) {
                 Matcher matcher = 
NODE_IDENTITY_PATTERN.matcher(entry.getKey());
                 if (matcher.matches() && 
!StringUtils.isBlank(entry.getValue())) {
-                    nodeIdentities.add(entry.getValue());
+                    
nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), 
identityMappings));
                 }
             }
 
@@ -362,7 +368,7 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
         // get all the user DNs into a list
         List<String> userIdentities = new ArrayList<>();
         for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) 
{
-            userIdentities.add(legacyUser.getDn());
+            
userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), 
identityMappings));
         }
 
         // sort the list and pull out the first identity
@@ -376,7 +382,7 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
 
         for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) 
{
             // create the identifier of the new user based on the DN
-            final String legacyUserDn = legacyUser.getDn();
+            final String legacyUserDn = 
IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
             final String userIdentifier = 
UUID.nameUUIDFromBytes(legacyUserDn.getBytes(StandardCharsets.UTF_8)).toString();
 
             // create the new User and add it to the list of users
@@ -421,10 +427,13 @@ public class FileAuthorizer extends 
AbstractPolicyBasedAuthorizer {
 
             if (portDTO.getUserAccessControl() != null) {
                 for (String userAccessControl : 
portDTO.getUserAccessControl()) {
+                    // need to perform the identity mapping on the access 
control so it matches the identities in the User objects
+                    final String mappedUserAccessControl = 
IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings);
+
                     // find a user where the identity is the userAccessControl
                     org.apache.nifi.authorization.file.generated.User 
foundUser = null;
                     for (org.apache.nifi.authorization.file.generated.User 
jaxbUser : authorizations.getUsers().getUser()) {
-                        if (jaxbUser.getIdentity().equals(userAccessControl)) {
+                        if 
(jaxbUser.getIdentity().equals(mappedUserAccessControl)) {
                             foundUser = jaxbUser;
                             break;
                         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
index e5d6a54..0958b27 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
@@ -28,6 +28,8 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -36,6 +38,7 @@ import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
@@ -43,6 +46,7 @@ import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -112,6 +116,7 @@ public class FileAuthorizerTest {
     private File restore;
     private File flow;
     private File flowNoPorts;
+    private File flowWithDns;
 
     private AuthorizerConfigurationContext configurationContext;
 
@@ -131,6 +136,9 @@ public class FileAuthorizerTest {
         flowNoPorts = new File("src/test/resources/flow-no-ports.xml.gz");
         
FileUtils.ensureDirectoryExistAndCanAccess(flowNoPorts.getParentFile());
 
+        flowWithDns = new File("src/test/resources/flow-with-dns.xml.gz");
+        
FileUtils.ensureDirectoryExistAndCanAccess(flowWithDns.getParentFile());
+
         properties = mock(NiFiProperties.class);
         
when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile());
         when(properties.getFlowConfigurationFile()).thenReturn(flow);
@@ -332,6 +340,62 @@ public class FileAuthorizerTest {
         return resourceActionMap;
     }
 
+    @Test
+    public void 
testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws 
Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", 
"^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
+
+        properties = getNiFiProperties(props);
+        
when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns);
+        authorizer.setNiFiProperties(properties);
+
+        
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new 
StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", 
null));
+
+        writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
+        authorizer.onConfigured(configurationContext);
+
+        final User user1 = authorizer.getUserByIdentity("user1");
+        assertNotNull(user1);
+
+        final User user2 = authorizer.getUserByIdentity("user2");
+        assertNotNull(user2);
+
+        final User user3 = authorizer.getUserByIdentity("user3");
+        assertNotNull(user3);
+
+        final User user4 = authorizer.getUserByIdentity("user4");
+        assertNotNull(user4);
+
+        final User user5 = authorizer.getUserByIdentity("user5");
+        assertNotNull(user5);
+
+        final User user6 = authorizer.getUserByIdentity("user6");
+        assertNotNull(user6);
+
+        // verify one group got created
+        final Set<Group> groups = authorizer.getGroups();
+        assertEquals(1, groups.size());
+        final Group group1 = groups.iterator().next();
+        assertEquals("group1", group1.getName());
+
+        final Resource inputPortResource = 
ResourceFactory.getDataTransferResource(true, 
"2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input");
+        final AccessPolicy inputPortPolicy = 
authorizer.getUsersAndAccessPolicies().getAccessPolicy(inputPortResource.getIdentifier(),
 RequestAction.WRITE);
+        assertNotNull(inputPortPolicy);
+        assertEquals(1, inputPortPolicy.getUsers().size());
+        assertTrue(inputPortPolicy.getUsers().contains(user6.getIdentifier()));
+        assertEquals(1, inputPortPolicy.getGroups().size());
+        
assertTrue(inputPortPolicy.getGroups().contains(group1.getIdentifier()));
+
+        final Resource outputPortResource = 
ResourceFactory.getDataTransferResource(false, 
"2f7d1606-b090-4be7-a592-a5b70fb55532", "TCP Output");
+        final AccessPolicy outputPortPolicy = 
authorizer.getUsersAndAccessPolicies().getAccessPolicy(outputPortResource.getIdentifier(),
 RequestAction.WRITE);
+        assertNotNull(outputPortPolicy);
+        assertEquals(1, outputPortPolicy.getUsers().size());
+        
assertTrue(outputPortPolicy.getUsers().contains(user4.getIdentifier()));
+    }
+
     @Test(expected = AuthorizerCreationException.class)
     public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws 
Exception {
         
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
@@ -474,6 +538,31 @@ public class FileAuthorizerTest {
     }
 
     @Test
+    public void testOnConfiguredWhenInitialAdminProvidedWithIdentityMapping() 
throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", 
"^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", 
"$1_$2_$3");
+
+        properties = getNiFiProperties(props);
+        
when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flow);
+        authorizer.setNiFiProperties(properties);
+
+        final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, 
L=Santa Monica, ST=CA, C=US";
+        
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
+        authorizer.onConfigured(configurationContext);
+
+        final Set<User> users = authorizer.getUsers();
+        assertEquals(1, users.size());
+
+        final User adminUser = users.iterator().next();
+        assertEquals("localhost_Apache NiFi_Apache", adminUser.getIdentity());
+    }
+
+    @Test
     public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
         final String adminIdentity = "admin-user";
 
@@ -514,6 +603,43 @@ public class FileAuthorizerTest {
     }
 
     @Test
+    public void 
testOnConfiguredWhenNodeIdentitiesProvidedWithIdentityMappings() throws 
Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", 
"^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
+
+        properties = getNiFiProperties(props);
+        
when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile());
+        when(properties.getFlowConfigurationFile()).thenReturn(flow);
+        authorizer.setNiFiProperties(properties);
+
+        final String adminIdentity = "CN=user1, OU=Apache NiFi, O=Apache, 
L=Santa Monica, ST=CA, C=US";
+        
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+
+        final String nodeIdentity1 = "CN=node1, OU=Apache NiFi, O=Apache, 
L=Santa Monica, ST=CA, C=US";
+        final String nodeIdentity2 = "CN=node2, OU=Apache NiFi, O=Apache, 
L=Santa Monica, ST=CA, C=US";
+
+        final Map<String,String> nodeProps = new HashMap<>();
+        nodeProps.put("Node Identity 1", nodeIdentity1);
+        nodeProps.put("Node Identity 2", nodeIdentity2);
+
+        when(configurationContext.getProperties()).thenReturn(nodeProps);
+
+        writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
+        authorizer.onConfigured(configurationContext);
+
+        User adminUser = authorizer.getUserByIdentity("user1");
+        assertNotNull(adminUser);
+
+        User nodeUser1 = authorizer.getUserByIdentity("node1");
+        assertNotNull(nodeUser1);
+
+        User nodeUser2 = authorizer.getUserByIdentity("node2");
+        assertNotNull(nodeUser2);
+    }
+
+    @Test
     public void testOnConfiguredWhenAuthorizationsFileDoesNotExist() {
         authorizer.onConfigured(configurationContext);
         assertEquals(0, authorizer.getAccessPolicies().size());
@@ -1178,4 +1304,18 @@ public class FileAuthorizerTest {
         }
         return FileUtils.deleteFile(file, null, 10);
     }
+
+    private NiFiProperties getNiFiProperties(final Properties properties) {
+        final NiFiProperties nifiProperties = 
Mockito.mock(NiFiProperties.class);
+        
when(nifiProperties.stringPropertyNames()).thenReturn(properties.stringPropertyNames());
+
+        when(nifiProperties.getProperty(anyString())).then(new 
Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocationOnMock) throws 
Throwable {
+                return 
properties.getProperty((String)invocationOnMock.getArguments()[0]);
+            }
+        });
+        return nifiProperties;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-with-dns.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-with-dns.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-with-dns.xml
new file mode 100644
index 0000000..7c5d0d0
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-with-dns.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<users>
+    <user dn="CN=user1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US" 
group="group1">
+        <role name="ROLE_MONITOR"/>
+    </user>
+    <user dn="CN=user2, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US">
+        <role name="ROLE_PROVENANCE"/>
+    </user>
+    <user dn="CN=user3, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US">
+        <role name="ROLE_DFM"/>
+    </user>
+    <user dn="CN=user4, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US">
+        <role name="ROLE_ADMIN"/>
+    </user>
+    <user dn="CN=user5, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US">
+        <role name="ROLE_PROXY"/>
+    </user>
+    <user dn="CN=user6, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US">
+        <role name="ROLE_NIFI"/>
+    </user>
+</users>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow-with-dns.xml.gz
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow-with-dns.xml.gz
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow-with-dns.xml.gz
new file mode 100644
index 0000000..e4b4ed6
Binary files /dev/null and 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow-with-dns.xml.gz
 differ

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
index 59d5b2f..359e1c5 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
@@ -36,6 +36,10 @@
             <artifactId>nifi-expression-language</artifactId>
         </dependency>
         <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-properties</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.springframework.security</groupId>
             <artifactId>spring-security-core</artifactId>
         </dependency>

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java
new file mode 100644
index 0000000..eb79c3a
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java
@@ -0,0 +1,48 @@
+/*
+ * 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.authorization.util;
+
+import java.util.regex.Pattern;
+
+/**
+ * Holder to pass around the key, pattern, and replacement from an identity 
mapping in NiFiProperties.
+ */
+public class IdentityMapping {
+
+    private final String key;
+    private final Pattern pattern;
+    private final String replacementValue;
+
+    public IdentityMapping(String key, Pattern pattern, String 
replacementValue) {
+        this.key = key;
+        this.pattern = pattern;
+        this.replacementValue = replacementValue;
+    }
+
+    public String getKey() {
+        return key;
+    }
+
+    public Pattern getPattern() {
+        return pattern;
+    }
+
+    public String getReplacementValue() {
+        return replacementValue;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java
new file mode 100644
index 0000000..2485db5
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java
@@ -0,0 +1,145 @@
+/*
+ * 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.authorization.util;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class IdentityMappingUtil {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(IdentityMappingUtil.class);
+    private static final Pattern backReferencePattern = 
Pattern.compile("\\$(\\d+)");
+
+    /**
+     * Builds the identity mappings from NiFiProperties.
+     *
+     * @param properties the NiFiProperties instance
+     * @return a list of identity mappings
+     */
+    public static List<IdentityMapping> getIdentityMappings(final 
NiFiProperties properties) {
+        final List<IdentityMapping> mappings = new ArrayList<>();
+
+        // go through each property
+        for (String propertyName : properties.stringPropertyNames()) {
+            if (StringUtils.startsWith(propertyName, 
NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
+                final String key = StringUtils.substringAfter(propertyName, 
NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
+                final String identityPattern = 
properties.getProperty(propertyName);
+
+                if (StringUtils.isBlank(identityPattern)) {
+                    LOGGER.warn("Identity Mapping property {} was found, but 
was empty", new Object[]{propertyName});
+                    continue;
+                }
+
+                final String identityValueProperty = 
NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
+                final String identityValue = 
properties.getProperty(identityValueProperty);
+
+                if (StringUtils.isBlank(identityValue)) {
+                    LOGGER.warn("Identity Mapping property {} was found, but 
corresponding value {} was not found",
+                            new Object[]{propertyName, identityValueProperty});
+                    continue;
+                }
+
+                final IdentityMapping identityMapping = new 
IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
+                mappings.add(identityMapping);
+
+                LOGGER.debug("Found Identity Mapping with key = {}, pattern = 
{}, value = {}",
+                        new Object[] {key, identityPattern, identityValue});
+            }
+        }
+
+        // sort the list by the key so users can control the ordering in 
nifi.properties
+        Collections.sort(mappings, new Comparator<IdentityMapping>() {
+            @Override
+            public int compare(IdentityMapping m1, IdentityMapping m2) {
+                return m1.getKey().compareTo(m2.getKey());
+            }
+        });
+
+        return mappings;
+    }
+
+    /**
+     * Checks the given identity against each provided mapping and performs 
the mapping using the first one that matches.
+     * If none match then the identity is returned as is.
+     *
+     * @param identity the identity to map
+     * @param mappings the mappings
+     * @return the mapped identity, or the same identity if no mappings matched
+     */
+    public static String mapIdentity(final String identity, 
List<IdentityMapping> mappings) {
+        for (IdentityMapping mapping : mappings) {
+            Matcher m = mapping.getPattern().matcher(identity);
+            if (m.matches()) {
+                final String pattern = mapping.getPattern().pattern();
+                final String replacementValue = 
escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
+                return identity.replaceAll(pattern, replacementValue);
+            }
+        }
+
+        return identity;
+    }
+
+    // If we find a back reference that is not valid, then we will treat it as 
a literal string. For example, if we have 3 capturing
+    // groups and the Replacement Value has the value is "I owe $8 to him", 
then we want to treat the $8 as a literal "$8", rather
+    // than attempting to use it as a back reference.
+    private static String escapeLiteralBackReferences(final String unescaped, 
final int numCapturingGroups) {
+        if (numCapturingGroups == 0) {
+            return unescaped;
+        }
+
+        String value = unescaped;
+        final Matcher backRefMatcher = backReferencePattern.matcher(value);
+        while (backRefMatcher.find()) {
+            final String backRefNum = backRefMatcher.group(1);
+            if (backRefNum.startsWith("0")) {
+                continue;
+            }
+            final int originalBackRefIndex = Integer.parseInt(backRefNum);
+            int backRefIndex = originalBackRefIndex;
+
+            // if we have a replacement value like $123, and we have less than 
123 capturing groups, then
+            // we want to truncate the 3 and use capturing group 12; if we 
have less than 12 capturing groups,
+            // then we want to truncate the 2 and use capturing group 1; if we 
don't have a capturing group then
+            // we want to truncate the 1 and get 0.
+            while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
+                backRefIndex /= 10;
+            }
+
+            if (backRefIndex > numCapturingGroups) {
+                final StringBuilder sb = new StringBuilder(value.length() + 1);
+                final int groupStart = backRefMatcher.start(1);
+
+                sb.append(value.substring(0, groupStart - 1));
+                sb.append("\\");
+                sb.append(value.substring(groupStart - 1));
+                value = sb.toString();
+            }
+        }
+
+        return value;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
index 4971d62..cc1544d 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml
@@ -31,6 +31,9 @@
             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.
@@ -39,6 +42,9 @@
             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>

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
index f3f6bd0..62d0858 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/NiFiAuthenticationProvider.java
@@ -16,18 +16,15 @@
  */
 package org.apache.nifi.web.security;
 
+import org.apache.nifi.authorization.util.IdentityMapping;
+import org.apache.nifi.authorization.util.IdentityMappingUtil;
 import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.util.StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.security.authentication.AuthenticationProvider;
 
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Base AuthenticationProvider that provides common functionality to mapping 
identities.
@@ -35,7 +32,6 @@ import java.util.regex.Pattern;
 public abstract class NiFiAuthenticationProvider implements 
AuthenticationProvider {
 
     private static final Logger LOGGER = 
LoggerFactory.getLogger(NiFiAuthenticationProvider.class);
-    private static final Pattern backReferencePattern = 
Pattern.compile("\\$(\\d+)");
 
     private NiFiProperties properties;
     private List<IdentityMapping> mappings;
@@ -45,55 +41,7 @@ public abstract class NiFiAuthenticationProvider implements 
AuthenticationProvid
      */
     public NiFiAuthenticationProvider(final NiFiProperties properties) {
         this.properties = properties;
-        this.mappings = 
Collections.unmodifiableList(getIdentityMappings(properties));
-    }
-
-    /**
-     * Builds the identity mappings from NiFiProperties.
-     *
-     * @param properties the NiFiProperties instance
-     * @return a list of identity mappings
-     */
-    private List<IdentityMapping> getIdentityMappings(final NiFiProperties 
properties) {
-        final List<IdentityMapping> mappings = new ArrayList<>();
-
-        // go through each property
-        for (String propertyName : properties.stringPropertyNames()) {
-            if (StringUtils.startsWith(propertyName, 
NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
-                final String key = StringUtils.substringAfter(propertyName, 
NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
-                final String identityPattern = 
properties.getProperty(propertyName);
-
-                if (StringUtils.isBlank(identityPattern)) {
-                    LOGGER.warn("Identity Mapping property {} was found, but 
was empty", new Object[]{propertyName});
-                    continue;
-                }
-
-                final String identityValueProperty = 
NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
-                final String identityValue = 
properties.getProperty(identityValueProperty);
-
-                if (StringUtils.isBlank(identityValue)) {
-                    LOGGER.warn("Identity Mapping property {} was found, but 
corresponding value {} was not found",
-                            new Object[]{propertyName, identityValueProperty});
-                    continue;
-                }
-
-                final IdentityMapping identityMapping = new 
IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
-                mappings.add(identityMapping);
-
-                LOGGER.debug("Found Identity Mapping with key = {}, pattern = 
{}, value = {}",
-                        new Object[] {key, identityPattern, identityValue});
-            }
-        }
-
-        // sort the list by the key so users can control the ordering in 
nifi.properties
-        Collections.sort(mappings, new Comparator<IdentityMapping>() {
-            @Override
-            public int compare(IdentityMapping m1, IdentityMapping m2) {
-                return m1.getKey().compareTo(m2.getKey());
-            }
-        });
-
-        return mappings;
+        this.mappings = 
Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
     }
 
     public List<IdentityMapping> getMappings() {
@@ -101,85 +49,7 @@ public abstract class NiFiAuthenticationProvider implements 
AuthenticationProvid
     }
 
     protected String mapIdentity(final String identity) {
-        for (IdentityMapping mapping : mappings) {
-            Matcher m = mapping.getPattern().matcher(identity);
-            if (m.matches()) {
-                final String pattern = mapping.getPattern().pattern();
-                final String replacementValue = 
escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
-                return identity.replaceAll(pattern, replacementValue);
-            }
-        }
-
-        return identity;
-    }
-
-    // If we find a back reference that is not valid, then we will treat it as 
a literal string. For example, if we have 3 capturing
-    // groups and the Replacement Value has the value is "I owe $8 to him", 
then we want to treat the $8 as a literal "$8", rather
-    // than attempting to use it as a back reference.
-    private static String escapeLiteralBackReferences(final String unescaped, 
final int numCapturingGroups) {
-        if (numCapturingGroups == 0) {
-            return unescaped;
-        }
-
-        String value = unescaped;
-        final Matcher backRefMatcher = backReferencePattern.matcher(value);
-        while (backRefMatcher.find()) {
-            final String backRefNum = backRefMatcher.group(1);
-            if (backRefNum.startsWith("0")) {
-                continue;
-            }
-            final int originalBackRefIndex = Integer.parseInt(backRefNum);
-            int backRefIndex = originalBackRefIndex;
-
-            // if we have a replacement value like $123, and we have less than 
123 capturing groups, then
-            // we want to truncate the 3 and use capturing group 12; if we 
have less than 12 capturing groups,
-            // then we want to truncate the 2 and use capturing group 1; if we 
don't have a capturing group then
-            // we want to truncate the 1 and get 0.
-            while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
-                backRefIndex /= 10;
-            }
-
-            if (backRefIndex > numCapturingGroups) {
-                final StringBuilder sb = new StringBuilder(value.length() + 1);
-                final int groupStart = backRefMatcher.start(1);
-
-                sb.append(value.substring(0, groupStart - 1));
-                sb.append("\\");
-                sb.append(value.substring(groupStart - 1));
-                value = sb.toString();
-            }
-        }
-
-        return value;
-    }
-
-    /**
-     * Holder to pass around the key, pattern, and replacement from an 
identity mapping in NiFiProperties.
-     */
-    public static final class IdentityMapping {
-
-        private final String key;
-        private final Pattern pattern;
-        private final String replacementValue;
-
-        public IdentityMapping(String key, Pattern pattern, String 
replacementValue) {
-            this.key = key;
-            this.pattern = pattern;
-            this.replacementValue = replacementValue;
-        }
-
-        public String getKey() {
-            return key;
-        }
-
-        public Pattern getPattern() {
-            return pattern;
-        }
-
-        public String getReplacementValue() {
-            return replacementValue;
-        }
-
+        return IdentityMappingUtil.mapIdentity(identity, mappings);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/c3b4872b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
index addd314..0e25747 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/java/org/apache/nifi/web/security/NiFiAuthenticationProviderTest.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.web.security;
 
+import org.apache.nifi.authorization.util.IdentityMapping;
 import org.apache.nifi.util.NiFiProperties;
 import org.junit.Test;
 import org.mockito.Mockito;
@@ -45,7 +46,7 @@ public class NiFiAuthenticationProviderTest {
         final NiFiProperties nifiProperties = getNiFiProperties(properties);
 
         TestableNiFiAuthenticationProvider provider = new 
TestableNiFiAuthenticationProvider(nifiProperties);
-        List<NiFiAuthenticationProvider.IdentityMapping> mappings = 
provider.getMappings();
+        List<IdentityMapping> mappings = provider.getMappings();
         assertEquals(1, mappings.size());
         assertEquals("dn", mappings.get(0).getKey());
         assertEquals(pattern, mappings.get(0).getPattern().pattern());
@@ -58,7 +59,7 @@ public class NiFiAuthenticationProviderTest {
         final NiFiProperties nifiProperties = getNiFiProperties(properties);
 
         TestableNiFiAuthenticationProvider provider = new 
TestableNiFiAuthenticationProvider(nifiProperties);
-        List<NiFiAuthenticationProvider.IdentityMapping> mappings = 
provider.getMappings();
+        List<IdentityMapping> mappings = provider.getMappings();
         assertEquals(0, mappings.size());
 
         final String identity = "john";
@@ -74,7 +75,7 @@ public class NiFiAuthenticationProviderTest {
         final NiFiProperties nifiProperties = getNiFiProperties(properties);
 
         TestableNiFiAuthenticationProvider provider = new 
TestableNiFiAuthenticationProvider(nifiProperties);
-        List<NiFiAuthenticationProvider.IdentityMapping> mappings = 
provider.getMappings();
+        List<IdentityMapping> mappings = provider.getMappings();
         assertEquals(0, mappings.size());
     }
 
@@ -86,7 +87,7 @@ public class NiFiAuthenticationProviderTest {
         final NiFiProperties nifiProperties = getNiFiProperties(properties);
 
         TestableNiFiAuthenticationProvider provider = new 
TestableNiFiAuthenticationProvider(nifiProperties);
-        List<NiFiAuthenticationProvider.IdentityMapping> mappings = 
provider.getMappings();
+        List<IdentityMapping> mappings = provider.getMappings();
         assertEquals(0, mappings.size());
     }
 
@@ -103,7 +104,7 @@ public class NiFiAuthenticationProviderTest {
         final NiFiProperties nifiProperties = getNiFiProperties(properties);
 
         TestableNiFiAuthenticationProvider provider = new 
TestableNiFiAuthenticationProvider(nifiProperties);
-        List<NiFiAuthenticationProvider.IdentityMapping> mappings = 
provider.getMappings();
+        List<IdentityMapping> mappings = provider.getMappings();
         assertEquals(3, mappings.size());
     }
 

Reply via email to