Repository: nifi Updated Branches: refs/heads/master ff04f8efe -> 5cfa29e48
NIFI-5135: - Adding support for applying transforms to user identities and group names. This closes #2673. Signed-off-by: Bryan Bende <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/5cfa29e4 Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/5cfa29e4 Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/5cfa29e4 Branch: refs/heads/master Commit: 5cfa29e48f8e0e88a270488770a268199a588f68 Parents: ff04f8e Author: Matt Gilman <[email protected]> Authored: Thu May 3 15:26:31 2018 -0400 Committer: Bryan Bende <[email protected]> Committed: Mon May 7 11:17:26 2018 -0400 ---------------------------------------------------------------------- .../org/apache/nifi/util/NiFiProperties.java | 4 + .../authorization/util/IdentityMapping.java | 17 +++++ .../authorization/util/IdentityMappingUtil.java | 80 +++++++++++++++++--- .../src/main/asciidoc/administration-guide.adoc | 16 +++- .../authorization/FileAccessPolicyProvider.java | 77 ++++++++++--------- .../authorization/FileUserGroupProvider.java | 76 ++++++++++--------- .../nifi/authorization/FileAuthorizerTest.java | 6 +- .../FileUserGroupProviderTest.java | 46 +++++++++++ .../src/main/resources/conf/nifi.properties | 10 +++ .../nifi/remote/TestStandardRootGroupPort.java | 29 +++++++ .../ldap/tenants/LdapUserGroupProvider.java | 4 +- .../ldap/tenants/LdapUserGroupProviderTest.java | 43 +++++++++++ 12 files changed, 323 insertions(+), 85 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java ---------------------------------------------------------------------- diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java index 41f0594..56ba75a 100644 --- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java @@ -151,6 +151,10 @@ public abstract class NiFiProperties { public static final String SECURITY_OCSP_RESPONDER_CERTIFICATE = "nifi.security.ocsp.responder.certificate"; public static final String SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX = "nifi.security.identity.mapping.pattern."; public static final String SECURITY_IDENTITY_MAPPING_VALUE_PREFIX = "nifi.security.identity.mapping.value."; + public static final String SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX = "nifi.security.identity.mapping.transform."; + public static final String SECURITY_GROUP_MAPPING_PATTERN_PREFIX = "nifi.security.group.mapping.pattern."; + public static final String SECURITY_GROUP_MAPPING_VALUE_PREFIX = "nifi.security.group.mapping.value."; + public static final String SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX = "nifi.security.group.mapping.transform."; // oidc public static final String SECURITY_USER_OIDC_DISCOVERY_URL = "nifi.security.user.oidc.discovery.url"; http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java ---------------------------------------------------------------------- diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java index eb79c3a..a615678 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMapping.java @@ -18,19 +18,33 @@ package org.apache.nifi.authorization.util; import java.util.regex.Pattern; +import static org.apache.nifi.authorization.util.IdentityMapping.Transform.NONE; + /** * Holder to pass around the key, pattern, and replacement from an identity mapping in NiFiProperties. */ public class IdentityMapping { + public enum Transform { + NONE, + UPPER, + LOWER + } + private final String key; private final Pattern pattern; private final String replacementValue; + private final Transform transform; public IdentityMapping(String key, Pattern pattern, String replacementValue) { + this(key, pattern, replacementValue, NONE); + } + + public IdentityMapping(String key, Pattern pattern, String replacementValue, Transform transform) { this.key = key; this.pattern = pattern; this.replacementValue = replacementValue; + this.transform = transform; } public String getKey() { @@ -45,4 +59,7 @@ public class IdentityMapping { return replacementValue; } + public Transform getTransform() { + return transform; + } } http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java ---------------------------------------------------------------------- diff --git a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java index f7087ac..cbaf7aa 100644 --- a/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java +++ b/nifi-commons/nifi-security-utils/src/main/java/org/apache/nifi/authorization/util/IdentityMappingUtil.java @@ -17,6 +17,7 @@ package org.apache.nifi.authorization.util; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.util.IdentityMapping.Transform; import org.apache.nifi.util.NiFiProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,9 +26,17 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_PATTERN_PREFIX; +import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX; +import static org.apache.nifi.util.NiFiProperties.SECURITY_GROUP_MAPPING_VALUE_PREFIX; +import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX; +import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX; +import static org.apache.nifi.util.NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX; + public class IdentityMappingUtil { private static final Logger LOGGER = LoggerFactory.getLogger(IdentityMappingUtil.class); @@ -40,33 +49,76 @@ public class IdentityMappingUtil { * @return a list of identity mappings */ public static List<IdentityMapping> getIdentityMappings(final NiFiProperties properties) { + return getMappings( + properties, + SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX, + SECURITY_IDENTITY_MAPPING_VALUE_PREFIX, + SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX, + () -> "Identity"); + } + + /** + * Buils the group mappings from NiFiProperties. + * + * @param properties the NiFiProperties instance + * @return a list of group mappings + */ + public static List<IdentityMapping> getGroupMappings(final NiFiProperties properties) { + return getMappings( + properties, + SECURITY_GROUP_MAPPING_PATTERN_PREFIX, + SECURITY_GROUP_MAPPING_VALUE_PREFIX, + SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX, + () -> "Group"); + } + + private static List<IdentityMapping> getMappings(final NiFiProperties properties, final String patternPrefix, + final String valuePrefix, final String transformPrefix, final Supplier<String> getSubject) { + final List<IdentityMapping> mappings = new ArrayList<>(); // go through each property for (String propertyName : properties.getPropertyKeys()) { - if (StringUtils.startsWith(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) { - final String key = StringUtils.substringAfter(propertyName, NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX); + if (StringUtils.startsWith(propertyName, patternPrefix)) { + final String key = StringUtils.substringAfter(propertyName, patternPrefix); final String identityPattern = properties.getProperty(propertyName); if (StringUtils.isBlank(identityPattern)) { - LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName}); + LOGGER.warn("{} Mapping property {} was found, but was empty", new Object[] {getSubject.get(), propertyName}); continue; } - final String identityValueProperty = NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key; + final String identityValueProperty = valuePrefix + 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}); + LOGGER.warn("{} Mapping property {} was found, but corresponding value {} was not found", + new Object[] {getSubject.get(), propertyName, identityValueProperty}); + continue; + } + + final String identityTransformProperty = transformPrefix + key; + String rawIdentityTransform = properties.getProperty(identityTransformProperty); + + if (StringUtils.isBlank(rawIdentityTransform)) { + LOGGER.debug("{} Mapping property {} was found, but no transform was present. Using NONE.", new Object[] {getSubject.get(), propertyName}); + rawIdentityTransform = Transform.NONE.name(); + } + + final Transform identityTransform; + try { + identityTransform = Transform.valueOf(rawIdentityTransform); + } catch (final IllegalArgumentException iae) { + LOGGER.warn("{} Mapping property {} was found, but corresponding transform {} was not valid. Allowed values {}", + new Object[] {getSubject.get(), propertyName, rawIdentityTransform, StringUtils.join(Transform.values(), ", ")}); continue; } - final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue); + final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue, identityTransform); mappings.add(identityMapping); - LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}", - new Object[] {key, identityPattern, identityValue}); + LOGGER.debug("Found {} Mapping with key = {}, pattern = {}, value = {}, transform = {}", + new Object[] {getSubject.get(), key, identityPattern, identityValue, rawIdentityTransform}); } } @@ -95,7 +147,15 @@ public class IdentityMappingUtil { if (m.matches()) { final String pattern = mapping.getPattern().pattern(); final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount()); - return identity.replaceAll(pattern, replacementValue); + final String replacement = identity.replaceAll(pattern, replacementValue); + + if (Transform.UPPER.equals(mapping.getTransform())) { + return replacement.toUpperCase(); + } else if (Transform.LOWER.equals(mapping.getTransform())) { + return replacement.toLowerCase(); + } else { + return replacement; + } } } http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-docs/src/main/asciidoc/administration-guide.adoc ---------------------------------------------------------------------- diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc index 0c7dac5..64facc7 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -3406,13 +3406,27 @@ The following examples demonstrate normalizing DNs from certificates and princip ---- nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$ nifi.security.identity.mapping.value.dn=$1@$2 +nifi.security.identity.mapping.transform.dn=NONE nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$ nifi.security.identity.mapping.value.kerb=$1@$2 +nifi.security.identity.mapping.transform.kerb=NONE ---- The last segment of each property is an identifier used to associate the pattern with the replacement value. When a user makes a request to NiFi, their identity is checked to see if it matches each of those patterns in lexicographical order. For the first one that matches, the replacement specified in the `nifi.security.identity.mapping.value.xxxx` property is used. So a login with `CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US` matches the DN mapping pattern above and the DN mapping value `$1@$2` is applied. The user is normalized to `localhost@Apache NiFi`. -NOTE: These mappings are also applied to the "Initial Admin Identity" and "Cluster Node Identity" properties in the authorizers.xml file (See <<authorizers-setup>>). +In addition to mapping a transform may be applied. The supported versions are NONE (no transform applied), LOWER (identity lowercased), and UPPER (identity uppercased). If not specified, the default value is NONE. + +NOTE: These mappings are also applied to the "Initial Admin Identity", "Cluster Node Identity", and any legacy users in the authorizers.xml file as well as users imported from LDAP (See <<authorizers-setup>>). + +Group names can also be mapped. The following example will accept the existing group name but will lowercase it. This may be helpful when used in conjunction with an external authorizer. + +---- +nifi.security.group.mapping.pattern.anygroup=^(.*)$ +nifi.security.group.mapping.value.anygroup=$1 +nifi.security.group.mapping.transform.anygroup=LOWER +---- + +NOTE: These mappings are applied to any legacy groups referenced in the authorizers.xml as well as groups imported from LDAP. === Cluster Common Properties http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java index f71ad71..99f447f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java @@ -16,39 +16,6 @@ */ package org.apache.nifi.authorization; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLStreamWriter; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.exception.AuthorizationAccessException; @@ -76,6 +43,40 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); @@ -130,6 +131,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide private Set<String> nodeIdentities; private List<PortDTO> ports = new ArrayList<>(); private List<IdentityMapping> identityMappings; + private List<IdentityMapping> groupMappings; private UserGroupProvider userGroupProvider; private UserGroupProviderLookup userGroupProviderLookup; @@ -200,6 +202,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide // extract the identity mappings from nifi.properties if any are provided identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); + groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties)); // get the value of the initial admin identity final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY); @@ -728,10 +731,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide if (portDTO.getGroupAccessControl() != null) { for (String groupAccessControl : portDTO.getGroupAccessControl()) { + final String legacyGroupName = IdentityMappingUtil.mapIdentity(groupAccessControl, groupMappings); + // find a group where the name is the groupAccessControl Group foundGroup = null; for (Group group : userGroupProvider.getGroups()) { - if (group.getName().equals(groupAccessControl)) { + if (group.getName().equals(legacyGroupName)) { foundGroup = group; break; } @@ -740,7 +745,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide // couldn't find the group matching the access control so log a warning and skip if (foundGroup == null) { logger.warn("Found port with group access control for {} but no group exists with this name, skipping...", - new Object[] {groupAccessControl}); + new Object[] {legacyGroupName}); continue; } @@ -751,7 +756,7 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide resource.getIdentifier(), WRITE_CODE); - addGroupToPolicy(IdentifierUtil.getIdentifier(groupAccessControl), policy); + addGroupToPolicy(IdentifierUtil.getIdentifier(legacyGroupName), policy); } } } http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java index 2add45e..d2066a9 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileUserGroupProvider.java @@ -16,39 +16,6 @@ */ package org.apache.nifi.authorization; -import java.io.ByteArrayInputStream; -import java.io.File; -import java.io.IOException; -import java.io.StringWriter; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.xml.XMLConstants; -import javax.xml.bind.JAXBContext; -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.bind.Marshaller; -import javax.xml.bind.Unmarshaller; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.stream.XMLOutputFactory; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import javax.xml.stream.XMLStreamWriter; -import javax.xml.transform.stream.StreamSource; -import javax.xml.validation.Schema; -import javax.xml.validation.SchemaFactory; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.exception.AuthorizationAccessException; @@ -72,6 +39,40 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.stream.XMLOutputFactory; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import javax.xml.stream.XMLStreamWriter; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class FileUserGroupProvider implements ConfigurableUserGroupProvider { private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class); @@ -118,6 +119,7 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider { private String legacyAuthorizedUsersFile; private Set<String> initialUserIdentities; private List<IdentityMapping> identityMappings; + private List<IdentityMapping> groupMappings; private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>(); @@ -172,8 +174,9 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider { } } - // extract the identity mappings from nifi.properties if any are provided + // extract the identity and group mappings from nifi.properties if any are provided identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); + groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties)); // get the value of the legacy authorized users file final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE); @@ -697,8 +700,9 @@ public class FileUserGroupProvider implements ConfigurableUserGroupProvider { org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn); // if there was a group name find or create the group and add the user to it - org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup()); - if (group != null) { + if (StringUtils.isNotBlank(legacyUser.getGroup())) { + final String legacyGroupName = IdentityMappingUtil.mapIdentity(legacyUser.getGroup(), groupMappings); + org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyGroupName); org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); groupUser.setIdentifier(user.getIdentifier()); group.getUser().add(groupUser); http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/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 8140ef9..d434344 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 @@ -435,6 +435,10 @@ public class FileAuthorizerTest { props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$"); props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); + props.setProperty("nifi.security.group.mapping.pattern.anygroup", "^(.*)$"); + props.setProperty("nifi.security.group.mapping.value.anygroup", "$1"); + props.setProperty("nifi.security.group.mapping.transform.anygroup", "UPPER"); + properties = getNiFiProperties(props); when(properties.getRestoreDirectory()).thenReturn(restoreAuthorizations.getParentFile()); when(properties.getFlowConfigurationFile()).thenReturn(flowWithDns); @@ -469,7 +473,7 @@ public class FileAuthorizerTest { final Set<Group> groups = authorizer.getGroups(); assertEquals(1, groups.size()); final Group group1 = groups.iterator().next(); - assertEquals("group1", group1.getName()); + assertEquals("GROUP1", group1.getName()); final Resource inputPortResource = ResourceFactory.getDataTransferResource( ResourceFactory.getComponentResource(ResourceType.InputPort, "2f7d1606-b090-4be7-a592-a5b70fb55531", "TCP Input")); http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java index a4b7764..5448eb0 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java @@ -227,6 +227,52 @@ public class FileUserGroupProviderTest { assertEquals("group1", group1.getName()); } + @Test + public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappingsAndTransforms() 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"); + props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER"); + + props.setProperty("nifi.security.group.mapping.pattern.anygroup", "^(.*)$"); + props.setProperty("nifi.security.group.mapping.value.anygroup", "$1"); + props.setProperty("nifi.security.group.mapping.transform.anygroup", "UPPER"); + + properties = getNiFiProperties(props); + when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile()); + userGroupProvider.setNiFiProperties(properties); + + when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null)); + + writeFile(primaryTenants, EMPTY_TENANTS_CONCISE); + userGroupProvider.onConfigured(configurationContext); + + final User user1 = userGroupProvider.getUserByIdentity("USER1"); + assertNotNull(user1); + + final User user2 = userGroupProvider.getUserByIdentity("USER2"); + assertNotNull(user2); + + final User user3 = userGroupProvider.getUserByIdentity("USER3"); + assertNotNull(user3); + + final User user4 = userGroupProvider.getUserByIdentity("USER4"); + assertNotNull(user4); + + final User user5 = userGroupProvider.getUserByIdentity("USER5"); + assertNotNull(user5); + + final User user6 = userGroupProvider.getUserByIdentity("USER6"); + assertNotNull(user6); + + // verify one group got created + final Set<Group> groups = userGroupProvider.getGroups(); + assertEquals(1, groups.size()); + final Group group1 = groups.iterator().next(); + assertEquals("GROUP1", group1.getName()); + } + @Test(expected = AuthorizerCreationException.class) public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties index 29c2257..dbd9837 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties @@ -186,8 +186,18 @@ nifi.security.user.knox.audiences=${nifi.security.user.knox.audiences} # # nifi.security.identity.mapping.pattern.dn=^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$ # nifi.security.identity.mapping.value.dn=$1@$2 +# nifi.security.identity.mapping.transform.dn=NONE # nifi.security.identity.mapping.pattern.kerb=^(.*?)/instance@(.*?)$ # nifi.security.identity.mapping.value.kerb=$1@$2 +# nifi.security.identity.mapping.transform.kerb=UPPER + +# Group Mapping Properties # +# These properties allow normalizing group names coming from external sources like LDAP. The following example +# lowercases any group name. +# +# nifi.security.group.mapping.pattern.anygroup=^(.*)$ +# nifi.security.group.mapping.value.anygroup=$1 +# nifi.security.group.mapping.transform.anygroup=LOWER # cluster common properties (all nodes must have same values) # nifi.cluster.protocol.heartbeat.interval=${nifi.cluster.protocol.heartbeat.interval} http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRootGroupPort.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRootGroupPort.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRootGroupPort.java index affa9d9..594684f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRootGroupPort.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-site-to-site/src/test/java/org/apache/nifi/remote/TestStandardRootGroupPort.java @@ -47,6 +47,8 @@ public class TestStandardRootGroupPort { final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class); if ("[email protected]".equals(request.getIdentity())) { return AuthorizationResult.approved(); + } else if ("[email protected]".equals(request.getIdentity())) { + return AuthorizationResult.approved(); } return AuthorizationResult.denied(); }).when(authorizer).authorize(any(AuthorizationRequest.class)); @@ -97,4 +99,31 @@ public class TestStandardRootGroupPort { Assert.assertTrue(authResult.isAuthorized()); } + @Test + public void testCheckUserAuthorizationByMappedDnWithTransformation() { + + final NiFiProperties nifiProperties = mock(NiFiProperties.class); + final String mapKey = ".dn"; + Set<String> propertyKeys = new LinkedHashSet<>(); + propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX + mapKey); + propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + mapKey); + propertyKeys.add(NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX + mapKey); + doReturn(propertyKeys).when(nifiProperties).getPropertyKeys(); + + final String mapPattern = "^CN=(.*?), OU=(.*?)$"; + final String mapValue = "$1@$2"; + final String mapTransform = "UPPER"; + doReturn(mapPattern).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX + mapKey)); + doReturn(mapValue).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + mapKey)); + doReturn(mapTransform).when(nifiProperties).getProperty(eq(NiFiProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX + mapKey)); + + final RootGroupPort port = createRootGroupPort(nifiProperties); + + PortAuthorizationResult authResult = port.checkUserAuthorization("CN=node2, OU=nifi.test"); + Assert.assertFalse(authResult.isAuthorized()); + + authResult = port.checkUserAuthorization("CN=node1, OU=nifi.test"); + Assert.assertTrue(authResult.isAuthorized()); + } + } http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java index 1d2b344..2282578 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/main/java/org/apache/nifi/ldap/tenants/LdapUserGroupProvider.java @@ -117,6 +117,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { private static final long MINIMUM_SYNC_INTERVAL_MILLISECONDS = 10_000; private List<IdentityMapping> identityMappings; + private List<IdentityMapping> groupMappings; private NiFiProperties properties; private ScheduledExecutorService ldapSync; @@ -350,6 +351,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { // extract the identity mappings from nifi.properties if any are provided identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties)); + groupMappings = Collections.unmodifiableList(IdentityMappingUtil.getGroupMappings(properties)); // set the base environment is necessary if (!baseEnvironment.isEmpty()) { @@ -708,7 +710,7 @@ public class LdapUserGroupProvider implements UserGroupProvider { } } - return name; + return IdentityMappingUtil.mapIdentity(name, groupMappings); } private String getReferencedGroupValue(final DirContextOperations ctx) { http://git-wip-us.apache.org/repos/asf/nifi/blob/5cfa29e4/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java ---------------------------------------------------------------------- diff --git a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java index 0ea99ba..a3188b6 100644 --- a/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java +++ b/nifi-nar-bundles/nifi-ldap-iaa-providers-bundle/nifi-ldap-iaa-providers/src/test/java/org/apache/nifi/ldap/tenants/LdapUserGroupProviderTest.java @@ -507,6 +507,49 @@ public class LdapUserGroupProviderTest extends AbstractLdapTestUnit { assertNotNull(ldapUserGroupProvider.getUserByIdentity("User 1,ou=users")); } + @Test + public void testUserIdentityMappingWithTransforms() throws Exception { + final Properties props = new Properties(); + props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$"); + props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); + props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER"); + + final NiFiProperties properties = getNiFiProperties(props); + ldapUserGroupProvider.setNiFiProperties(properties); + + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, null); + when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null)); + ldapUserGroupProvider.onConfigured(configurationContext); + + assertEquals(1, ldapUserGroupProvider.getUsers().size()); + assertNotNull(ldapUserGroupProvider.getUserByIdentity("USER 1")); + } + + @Test + public void testUserIdentityAndGroupMappingWithTransforms() throws Exception { + final Properties props = new Properties(); + props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$"); + props.setProperty("nifi.security.identity.mapping.value.dn1", "$1"); + props.setProperty("nifi.security.identity.mapping.transform.dn1", "UPPER"); + props.setProperty("nifi.security.group.mapping.pattern.dn1", "^cn=(.*?),ou=(.*?),o=(.*?)$"); + props.setProperty("nifi.security.group.mapping.value.dn1", "$1"); + props.setProperty("nifi.security.group.mapping.transform.dn1", "UPPER"); + + final NiFiProperties properties = getNiFiProperties(props); + ldapUserGroupProvider.setNiFiProperties(properties); + + final AuthorizerConfigurationContext configurationContext = getBaseConfiguration(USER_SEARCH_BASE, GROUP_SEARCH_BASE); + when(configurationContext.getProperty(PROP_USER_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(uid=user1)", null)); + when(configurationContext.getProperty(PROP_GROUP_SEARCH_FILTER)).thenReturn(new StandardPropertyValue("(cn=admins)", null)); + ldapUserGroupProvider.onConfigured(configurationContext); + + assertEquals(1, ldapUserGroupProvider.getUsers().size()); + assertNotNull(ldapUserGroupProvider.getUserByIdentity("USER 1")); + + assertEquals(1, ldapUserGroupProvider.getGroups().size()); + assertEquals("ADMINS", ldapUserGroupProvider.getGroups().iterator().next().getName()); + } + @Test(expected = AuthorizerCreationException.class) public void testReferencedGroupAttributeWithoutGroupSearchBase() throws Exception { final AuthorizerConfigurationContext configurationContext = getBaseConfiguration("ou=users-2,o=nifi", null);
