mirror of https://github.com/apache/nifi.git
NIFI-2389 Refactoring identity mapping and applying it to FileAuthorizer for initial admin, cluster nodes, and legacy authorized users. This closes #719
This commit is contained in:
parent
52bc23f5db
commit
c3b4872b55
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)))
|
||||
|
@ -473,6 +537,31 @@ public class FileAuthorizerTest {
|
|||
assertFalse(foundRootGroupPolicy);
|
||||
}
|
||||
|
||||
@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";
|
||||
|
@ -513,6 +602,43 @@ public class FileAuthorizerTest {
|
|||
assertTrue(proxyWritePolicy.getUsers().contains(nodeUser2.getIdentifier()));
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
Binary file not shown.
|
@ -35,6 +35,10 @@
|
|||
<groupId>org.apache.nifi</groupId>
|
||||
<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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue