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()); }
