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

Reply via email to