nifi-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From alopre...@apache.org
Subject [2/2] nifi git commit: Refactored user identity parsing and proxied entity chain formatting. Added unit tests.
Date Tue, 14 Feb 2017 22:43:12 GMT
Refactored user identity parsing and proxied entity chain formatting.
Added unit tests.

Signed-off-by: Andy LoPresto <alopresto@apache.org>


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

Branch: refs/heads/support/nifi-1.1.x
Commit: e80ad9539cb070843f77a20b045633c9ad424566
Parents: 986e716
Author: Andy LoPresto <alopresto@apache.org>
Authored: Wed Feb 1 21:32:35 2017 -0800
Committer: Andy LoPresto <alopresto@apache.org>
Committed: Tue Feb 14 14:10:07 2017 -0800

----------------------------------------------------------------------
 .../apache/nifi/web/NiFiWebRequestContext.java  |   5 +
 .../resource/DataAuthorizable.java              |  61 ++--
 .../nifi/authorization/user/NiFiUserUtils.java  |  17 +-
 .../authorization/user/StandardNiFiUser.java    |  35 ++-
 .../authorization/user/NiFiUserUtilsTest.groovy | 111 ++++++++
 .../resource/DataAuthorizableTest.java          | 160 +++++++++++
 .../ThreadPoolRequestReplicator.java            |  89 +++---
 .../TestThreadPoolRequestReplicator.java        | 111 +++++++-
 .../nifi/web/HttpServletRequestContext.java     |  38 +--
 .../nifi/web/StandardNiFiContentAccess.java     |  24 +-
 .../StandardNiFiWebConfigurationContext.java    |  32 +--
 .../apache/nifi/web/ContentRequestContext.java  |   5 +
 .../nifi/web/ContentViewerController.java       |   4 +-
 .../nifi/web/security/ProxiedEntitiesUtils.java | 130 +++++----
 .../x509/X509AuthenticationProvider.java        |  67 +++--
 .../security/ProxiedEntitiesUtilsTest.groovy    | 265 ++++++++++++++++++
 .../x509/X509AuthenticationProviderTest.java    | 276 +++++++++++++++++++
 17 files changed, 1183 insertions(+), 247 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
----------------------------------------------------------------------
diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
index 9dd44ab..bb3bbd3 100644
--- a/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
+++ b/nifi-framework-api/src/main/java/org/apache/nifi/web/NiFiWebRequestContext.java
@@ -49,8 +49,13 @@ public interface NiFiWebRequestContext {
      * &lt;CN=original-proxied-entity&gt;&lt;CN=first-proxy&gt;&lt;CN=second-proxy&gt;...
      * </code>
      *
+     * Update:
+     * This method has been deprecated since the entire proxy
+     * chain is able to be rebuilt using the current user if necessary.
+     *
      * @return the proxied entities chain or null if no chain
      */
+    @Deprecated
     String getProxiedEntitiesChain();
 
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java
index cb0d0f1..8fa2741 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/DataAuthorizable.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.authorization.resource;
 
+import java.util.Map;
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
@@ -23,13 +24,8 @@ import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.Resource;
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.NiFiUserUtils;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
 import org.apache.nifi.web.ResourceNotFoundException;
 
-import java.util.List;
-import java.util.Map;
-
 /**
  * Authorizable for authorizing access to data. Data based authorizable requires authorization for the entire DN chain.
  */
@@ -67,28 +63,24 @@ public class DataAuthorizable implements Authorizable, EnforcePolicyPermissionsT
 
         AuthorizationResult result = null;
 
-        // calculate the dn chain
-        final List<String> dnChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
-        for (final String identity : dnChain) {
+        // authorize each element in the chain
+        NiFiUser chainedUser = user;
+        do {
             try {
-                final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
-                final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
-                    @Override
-                    public boolean isAnonymous() {
-                        // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case
-                        return user.isAnonymous();
-                    }
-                };
-
-                result = Authorizable.super.checkAuthorization(authorizer, action, chainUser, resourceContext);
+                // perform the current user authorization
+                result = Authorizable.super.checkAuthorization(authorizer, action, chainedUser, resourceContext);
+
+                // if authorization is not approved, reject
+                if (!Result.Approved.equals(result.getResult())) {
+                    return result;
+                }
+
+                // go to the next user in the chain
+                chainedUser = chainedUser.getChain();
             } catch (final ResourceNotFoundException e) {
                 result = AuthorizationResult.denied("Unknown source component.");
             }
-
-            if (!Result.Approved.equals(result.getResult())) {
-                break;
-            }
-        }
+        } while (chainedUser != null);
 
         if (result == null) {
             result = AuthorizationResult.denied();
@@ -103,23 +95,18 @@ public class DataAuthorizable implements Authorizable, EnforcePolicyPermissionsT
             throw new AccessDeniedException("Unknown user");
         }
 
-        // calculate the dn chain
-        final List<String> dnChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
-        for (final String identity : dnChain) {
+        // authorize each element in the chain
+        NiFiUser chainedUser = user;
+        do {
             try {
-                final String clientAddress = user.getIdentity().equals(identity) ? user.getClientAddress() : null;
-                final NiFiUser chainUser = new StandardNiFiUser(identity, clientAddress) {
-                    @Override
-                    public boolean isAnonymous() {
-                        // allow current user to drive anonymous flag as anonymous users are never chained... supports single user case
-                        return user.isAnonymous();
-                    }
-                };
-
-                Authorizable.super.authorize(authorizer, action, chainUser, resourceContext);
+                // perform the current user authorization
+                Authorizable.super.authorize(authorizer, action, chainedUser, resourceContext);
+
+                // go to the next user in the chain
+                chainedUser = chainedUser.getChain();
             } catch (final ResourceNotFoundException e) {
                 throw new AccessDeniedException("Unknown source component.");
             }
-        }
+        } while (chainedUser != null);
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
index 6a4776a..93e070d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/NiFiUserUtils.java
@@ -16,13 +16,13 @@
  */
 package org.apache.nifi.authorization.user;
 
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Utility methods for retrieving information about the current application user.
  *
@@ -72,13 +72,18 @@ public final class NiFiUserUtils {
 
         // build the dn chain
         NiFiUser chainedUser = user;
-        do {
+        while (chainedUser != null) {
             // add the entry for this user
-            proxyChain.add(chainedUser.getIdentity());
+            if (chainedUser.isAnonymous()) {
+                // use an empty string to represent an anonymous user in the proxy entities chain
+                proxyChain.add(StringUtils.EMPTY);
+            } else {
+                proxyChain.add(chainedUser.getIdentity());
+            }
 
             // go to the next user in the chain
             chainedUser = chainedUser.getChain();
-        } while (chainedUser != null);
+        }
 
         return proxyChain;
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
index 372d89f..2a82795 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
@@ -23,30 +23,55 @@ import java.util.Objects;
  */
 public class StandardNiFiUser implements NiFiUser {
 
-    public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser("anonymous");
+    public static final String ANONYMOUS_IDENTITY = "anonymous";
+    public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser(ANONYMOUS_IDENTITY, null, null, true);
 
     private final String identity;
     private final NiFiUser chain;
     private final String clientAddress;
+    private final boolean isAnonymous;
 
     public StandardNiFiUser(String identity) {
-        this(identity, null, null);
+        this(identity, null, null, false);
     }
 
     public StandardNiFiUser(String identity, String clientAddress) {
-        this(identity, null, clientAddress);
+        this(identity, null, clientAddress, false);
     }
 
     public StandardNiFiUser(String identity, NiFiUser chain) {
-        this(identity, chain, null);
+        this(identity, chain, null, false);
     }
 
     public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
+        this(identity, chain, clientAddress, false);
+    }
+
+    /**
+     * This constructor is private as the only instance of this class which should have {@code isAnonymous} set to true is the singleton ANONYMOUS.
+     *
+     * @param identity      the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
+     * @param chain         the proxy chain that leads to this users
+     * @param clientAddress the source address of the request
+     * @param isAnonymous   true to represent the canonical "anonymous" user
+     */
+    private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
         this.identity = identity;
         this.chain = chain;
         this.clientAddress = clientAddress;
+        this.isAnonymous = isAnonymous;
     }
 
+    /**
+     * This static builder allows the chain and clientAddress to be populated without allowing calling code to provide a non-anonymous identity of the anonymous user.
+     *
+     * @param chain the proxied entities in {@see NiFiUser} form
+     * @param clientAddress the address the request originated from
+     * @return an anonymous user instance with the identity "anonymous"
+     */
+    public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) {
+        return new StandardNiFiUser(ANONYMOUS_IDENTITY, chain, clientAddress, true);
+    }
 
     @Override
     public String getIdentity() {
@@ -60,7 +85,7 @@ public class StandardNiFiUser implements NiFiUser {
 
     @Override
     public boolean isAnonymous() {
-        return this == ANONYMOUS;
+        return isAnonymous;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
new file mode 100644
index 0000000..e76b171
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/groovy/org/apache/nifi/authorization/user/NiFiUserUtilsTest.groovy
@@ -0,0 +1,111 @@
+/*
+ * 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.user
+
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+@RunWith(JUnit4.class)
+class NiFiUserUtilsTest {
+    private static final Logger logger = LoggerFactory.getLogger(NiFiUserUtilsTest.class)
+
+    private static final String SAFE_USER_NAME_ANDY = "alopresto"
+    private static final String SAFE_USER_DN_ANDY = "CN=${SAFE_USER_NAME_ANDY}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_JOHN = "jdoe"
+    private static final String SAFE_USER_DN_JOHN = "CN=${SAFE_USER_NAME_JOHN}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_PROXY_1 = "proxy1.nifi.apache.org"
+    private static final String SAFE_USER_DN_PROXY_1 = "CN=${SAFE_USER_NAME_PROXY_1}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_PROXY_2 = "proxy2.nifi.apache.org"
+    private static final String SAFE_USER_DN_PROXY_2 = "CN=${SAFE_USER_NAME_PROXY_2}, OU=Apache NiFi"
+
+    @BeforeClass
+    static void setUpOnce() throws Exception {
+        logger.metaClass.methodMissing = { String name, args ->
+            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+        }
+    }
+
+    @Before
+    void setUp() {
+    }
+
+    @After
+    void tearDown() {
+    }
+
+    @Test
+    void testShouldBuildProxyChain() throws Exception {
+        // Arrange
+        def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1}, getChain: { -> null}, isAnonymous: { -> false}] as NiFiUser
+        def mockJohn = [getIdentity: { -> SAFE_USER_NAME_JOHN}, getChain: { -> mockProxy1}, isAnonymous: { -> false}] as NiFiUser
+
+        // Act
+        List<String> proxiedEntitiesChain = NiFiUserUtils.buildProxiedEntitiesChain(mockJohn)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == [SAFE_USER_NAME_JOHN, SAFE_USER_NAME_PROXY_1]
+    }
+
+    @Test
+    void testShouldBuildProxyChainFromSingleUser() throws Exception {
+        // Arrange
+        def mockJohn = [getIdentity: { -> SAFE_USER_NAME_JOHN}, getChain: { -> null}, isAnonymous: { -> false}] as NiFiUser
+
+        // Act
+        List<String> proxiedEntitiesChain = NiFiUserUtils.buildProxiedEntitiesChain(mockJohn)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == [SAFE_USER_NAME_JOHN]
+    }
+
+    @Test
+    void testShouldBuildProxyChainFromAnonymousUser() throws Exception {
+        // Arrange
+        def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1}, getChain: { -> null}, isAnonymous: { -> false}] as NiFiUser
+        def mockAnonymous = [getIdentity: { -> "anonymous"}, getChain: { -> mockProxy1}, isAnonymous: { -> true}] as NiFiUser
+
+        // Act
+        List<String> proxiedEntitiesChain = NiFiUserUtils.buildProxiedEntitiesChain(mockAnonymous)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == ["", SAFE_USER_NAME_PROXY_1]
+    }
+
+    @Test
+    void testBuildProxyChainFromNullUserShouldBeEmpty() throws Exception {
+        // Arrange
+
+        // Act
+        List<String> proxiedEntitiesChain = NiFiUserUtils.buildProxiedEntitiesChain(null)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == []
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
new file mode 100644
index 0000000..069bf79
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.resource;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizationRequest;
+import org.apache.nifi.authorization.AuthorizationResult;
+import org.apache.nifi.authorization.AuthorizationResult.Result;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentMatcher;
+
+public class DataAuthorizableTest {
+
+    private static final String IDENTITY_1 = "identity-1";
+    private static final String PROXY_1 = "proxy-1";
+    private static final String PROXY_2 = "proxy-2";
+
+    private Authorizable testProcessorAuthorizable;
+    private Authorizer testAuthorizer;
+    private DataAuthorizable testDataAuthorizable;
+
+    @Before
+    public void setup() {
+        testProcessorAuthorizable = mock(Authorizable.class);
+        when(testProcessorAuthorizable.getParentAuthorizable()).thenReturn(null);
+        when(testProcessorAuthorizable.getResource()).thenReturn(ResourceFactory.getComponentResource(ResourceType.Processor, "id", "name"));
+
+        testAuthorizer = mock(Authorizer.class);
+        when(testAuthorizer.authorize(any(AuthorizationRequest.class))).then(invocation -> {
+            final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class);
+
+            if (IDENTITY_1.equals(request.getIdentity())) {
+                return AuthorizationResult.approved();
+            } else if (PROXY_1.equals(request.getIdentity())) {
+                return AuthorizationResult.approved();
+            } else if (PROXY_2.equals(request.getIdentity())) {
+                return AuthorizationResult.approved();
+            }
+
+            return AuthorizationResult.denied();
+        });
+
+        testDataAuthorizable = new DataAuthorizable(testProcessorAuthorizable);
+    }
+
+    @Test(expected = AccessDeniedException.class)
+    public void testAuthorizeNullUser() {
+        testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, null, null);
+    }
+
+    @Test
+    public void testCheckAuthorizationNullUser() {
+        final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, null, null);
+        assertEquals(Result.Denied, result.getResult());
+    }
+
+    @Test(expected = AccessDeniedException.class)
+    public void testAuthorizeUnauthorizedUser() {
+        final NiFiUser user = new StandardNiFiUser("unknown");
+        testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
+    }
+
+    @Test
+    public void testCheckAuthorizationUnauthorizedUser() {
+        final NiFiUser user = new StandardNiFiUser("unknown");
+        final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
+        assertEquals(Result.Denied, result.getResult());
+    }
+
+    @Test
+    public void testAuthorizedUser() {
+        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
+
+        verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
+            @Override
+            public boolean matches(Object o) {
+                return IDENTITY_1.equals(((AuthorizationRequest) o).getIdentity());
+            }
+        }));
+    }
+
+    @Test
+    public void testCheckAuthorizationUser() {
+        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
+
+        assertEquals(Result.Approved, result.getResult());
+        verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
+            @Override
+            public boolean matches(Object o) {
+                return IDENTITY_1.equals(((AuthorizationRequest) o).getIdentity());
+            }
+        }));
+    }
+
+    @Test
+    public void testAuthorizedUserChain() {
+        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
+        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
+        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
+
+        verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class));
+        verifyAuthorizeForUser(IDENTITY_1);
+        verifyAuthorizeForUser(PROXY_1);
+        verifyAuthorizeForUser(PROXY_2);
+    }
+
+    @Test
+    public void testCheckAuthorizationUserChain() {
+        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
+        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
+        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
+
+        assertEquals(Result.Approved, result.getResult());
+        verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class));
+        verifyAuthorizeForUser(IDENTITY_1);
+        verifyAuthorizeForUser(PROXY_1);
+        verifyAuthorizeForUser(PROXY_2);
+    }
+
+    private void verifyAuthorizeForUser(final String identity) {
+        verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
+            @Override
+            public boolean matches(Object o) {
+                return identity.equals(((AuthorizationRequest) o).getIdentity());
+            }
+        }));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
index ff79000..3b4470f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/replication/ThreadPoolRequestReplicator.java
@@ -23,35 +23,6 @@ import com.sun.jersey.api.client.WebResource;
 import com.sun.jersey.api.client.config.ClientConfig;
 import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
-import org.apache.nifi.authorization.AccessDeniedException;
-import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.NiFiUserUtils;
-import org.apache.nifi.cluster.coordination.ClusterCoordinator;
-import org.apache.nifi.cluster.coordination.http.HttpResponseMapper;
-import org.apache.nifi.cluster.coordination.http.StandardHttpResponseMapper;
-import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
-import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
-import org.apache.nifi.cluster.manager.NodeResponse;
-import org.apache.nifi.cluster.manager.exception.ConnectingNodeMutableRequestException;
-import org.apache.nifi.cluster.manager.exception.DisconnectedNodeMutableRequestException;
-import org.apache.nifi.cluster.manager.exception.IllegalClusterStateException;
-import org.apache.nifi.cluster.manager.exception.NoConnectedNodesException;
-import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
-import org.apache.nifi.cluster.manager.exception.UriConstructionException;
-import org.apache.nifi.cluster.protocol.NodeIdentifier;
-import org.apache.nifi.events.EventReporter;
-import org.apache.nifi.reporting.Severity;
-import org.apache.nifi.util.ComponentIdGenerator;
-import org.apache.nifi.util.FormatUtils;
-import org.apache.nifi.util.NiFiProperties;
-import org.apache.nifi.web.security.ProxiedEntitiesUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response.Status;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.Collections;
@@ -76,6 +47,34 @@ import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.function.Function;
 import java.util.stream.Collectors;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response.Status;
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.coordination.http.HttpResponseMapper;
+import org.apache.nifi.cluster.coordination.http.StandardHttpResponseMapper;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
+import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.manager.exception.ConnectingNodeMutableRequestException;
+import org.apache.nifi.cluster.manager.exception.DisconnectedNodeMutableRequestException;
+import org.apache.nifi.cluster.manager.exception.IllegalClusterStateException;
+import org.apache.nifi.cluster.manager.exception.NoConnectedNodesException;
+import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
+import org.apache.nifi.cluster.manager.exception.UriConstructionException;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.events.EventReporter;
+import org.apache.nifi.reporting.Severity;
+import org.apache.nifi.util.ComponentIdGenerator;
+import org.apache.nifi.util.FormatUtils;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.security.ProxiedEntitiesUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class ThreadPoolRequestReplicator implements RequestReplicator {
 
@@ -220,6 +219,18 @@ public class ThreadPoolRequestReplicator implements RequestReplicator {
         return replicate(nodeIdSet, method, uri, entity, headers, true, true);
     }
 
+    void addProxiedEntitiesHeader(final Map<String, String> headers) {
+        final NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user == null) {
+            throw new AccessDeniedException("Unknown user");
+        }
+
+        // Add the user as a proxied entity so that when the receiving NiFi receives the request,
+        // it knows that we are acting as a proxy on behalf of the current user.
+        final String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user);
+        headers.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesChain);
+    }
+
     @Override
     public AsyncClusterResponse replicate(Set<NodeIdentifier> nodeIds, String method, URI uri, Object entity, Map<String, String> headers,
                                           final boolean indicateReplicated, final boolean performVerification) {
@@ -230,14 +241,8 @@ public class ThreadPoolRequestReplicator implements RequestReplicator {
             updatedHeaders.put(RequestReplicator.REPLICATION_INDICATOR_HEADER, "true");
         }
 
-
-        // If the user is authenticated, add them as a proxied entity so that when the receiving NiFi receives the request,
-        // it knows that we are acting as a proxy on behalf of the current user.
-        final NiFiUser user = NiFiUserUtils.getNiFiUser();
-        if (user != null && !user.isAnonymous()) {
-            final String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user);
-            updatedHeaders.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesChain);
-        }
+        // include the proxied entities header
+        addProxiedEntitiesHeader(updatedHeaders);
 
         if (indicateReplicated) {
             // If we are replicating a request and indicating that it is replicated, then this means that we are
@@ -275,14 +280,10 @@ public class ThreadPoolRequestReplicator implements RequestReplicator {
 
     @Override
     public AsyncClusterResponse forwardToCoordinator(final NodeIdentifier coordinatorNodeId, final String method, final URI uri, final Object entity, final Map<String, String> headers) {
-        // If the user is authenticated, add them as a proxied entity so that when the receiving NiFi receives the request,
-        // it knows that we are acting as a proxy on behalf of the current user.
         final Map<String, String> updatedHeaders = new HashMap<>(headers);
-        final NiFiUser user = NiFiUserUtils.getNiFiUser();
-        if (user != null && !user.isAnonymous()) {
-            final String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(user);
-            updatedHeaders.put(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN, proxiedEntitiesChain);
-        }
+
+        // include the proxied entities header
+        addProxiedEntitiesHeader(updatedHeaders);
 
         return replicate(Collections.singleton(coordinatorNodeId), method, uri, entity, updatedHeaders, false, null, false, false, null);
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
index 02578a5..3c782a7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/coordination/http/replication/TestThreadPoolRequestReplicator.java
@@ -21,6 +21,13 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import com.sun.jersey.api.client.Client;
+import com.sun.jersey.api.client.ClientHandlerException;
+import com.sun.jersey.api.client.ClientResponse;
+import com.sun.jersey.api.client.ClientResponse.Status;
+import com.sun.jersey.api.client.WebResource;
+import com.sun.jersey.core.header.InBoundHeaders;
+import com.sun.jersey.core.header.OutBoundHeaders;
 import java.io.ByteArrayInputStream;
 import java.net.SocketTimeoutException;
 import java.net.URI;
@@ -35,10 +42,11 @@ import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
-
 import javax.ws.rs.HttpMethod;
-
 import org.apache.commons.collections4.map.MultiValueMap;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserDetails;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
 import org.apache.nifi.cluster.coordination.ClusterCoordinator;
 import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
 import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
@@ -50,6 +58,8 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.util.NiFiProperties;
 import org.apache.nifi.web.api.entity.Entity;
 import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.security.ProxiedEntitiesUtils;
+import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.junit.Assert;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -57,14 +67,8 @@ import org.mockito.Mockito;
 import org.mockito.internal.util.reflection.Whitebox;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
-
-import com.sun.jersey.api.client.Client;
-import com.sun.jersey.api.client.ClientHandlerException;
-import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.ClientResponse.Status;
-import com.sun.jersey.api.client.WebResource;
-import com.sun.jersey.core.header.InBoundHeaders;
-import com.sun.jersey.core.header.OutBoundHeaders;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
 
 public class TestThreadPoolRequestReplicator {
 
@@ -87,6 +91,10 @@ public class TestThreadPoolRequestReplicator {
             final URI uri = new URI("http://localhost:8080/processors/1");
             final Entity entity = new ProcessorEntity();
 
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             final AsyncClusterResponse response = replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, true);
 
             // We should get back the same response object
@@ -105,6 +113,29 @@ public class TestThreadPoolRequestReplicator {
         });
     }
 
+    @Test
+    public void testRequestChain() {
+        final String proxyIdentity2 = "proxy-2";
+        final String proxyIdentity1 = "proxy-1";
+        final String userIdentity = "user";
+
+        withReplicator(replicator -> {
+            final Set<NodeIdentifier> nodeIds = new HashSet<>();
+            nodeIds.add(new NodeIdentifier("1", "localhost", 8000, "localhost", 8001, "localhost", 8002, 8003, false));
+            final URI uri = new URI("http://localhost:8080/processors/1");
+            final Entity entity = new ProcessorEntity();
+
+            // set the user
+            final NiFiUser proxy2 = new StandardNiFiUser(proxyIdentity2);
+            final NiFiUser proxy1 = new StandardNiFiUser(proxyIdentity1, proxy2);
+            final NiFiUser user = new StandardNiFiUser(userIdentity, proxy1);
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(user));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, true);
+        }, ClientResponse.Status.OK, 0L, null, "<" + userIdentity + "><" + proxyIdentity1 + "><" + proxyIdentity2 +">");
+    }
+
     @Test(timeout = 15000)
     public void testLongWaitForResponse() {
         withReplicator(replicator -> {
@@ -114,6 +145,10 @@ public class TestThreadPoolRequestReplicator {
             final URI uri = new URI("http://localhost:8080/processors/1");
             final Entity entity = new ProcessorEntity();
 
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             final AsyncClusterResponse response = replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, true);
 
             // We should get back the same response object
@@ -146,6 +181,10 @@ public class TestThreadPoolRequestReplicator {
             final URI uri = new URI("http://localhost:8080/processors/1");
             final Entity entity = new ProcessorEntity();
 
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             final AsyncClusterResponse response = replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, true);
             assertNotNull(response.awaitMergedResponse(1, TimeUnit.SECONDS));
         }, null, 0L, new IllegalArgumentException("Exception created for unit test"));
@@ -186,6 +225,10 @@ public class TestThreadPoolRequestReplicator {
         };
 
         try {
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             final AsyncClusterResponse clusterResponse = replicator.replicate(nodeIds, HttpMethod.POST,
                     new URI("http://localhost:80/processors/1"), new ProcessorEntity(), new HashMap<>(), true, true);
             clusterResponse.awaitMergedResponse();
@@ -239,6 +282,10 @@ public class TestThreadPoolRequestReplicator {
         };
 
         try {
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             try {
                 replicator.replicate(HttpMethod.POST, new URI("http://localhost:80/processors/1"), new ProcessorEntity(), new HashMap<>());
                 Assert.fail("Expected ConnectingNodeMutableRequestException");
@@ -306,6 +353,10 @@ public class TestThreadPoolRequestReplicator {
         };
 
         try {
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
             final AsyncClusterResponse clusterResponse = replicator.replicate(nodeIds, HttpMethod.POST,
                     new URI("http://localhost:80/processors/1"), new ProcessorEntity(), new HashMap<>(), true, true);
             clusterResponse.awaitMergedResponse();
@@ -352,9 +403,17 @@ public class TestThreadPoolRequestReplicator {
             preNotifyLatch.await();
 
             try {
+                // set the user
+                final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+                SecurityContextHolder.getContext().setAuthentication(authentication);
+
+                // ensure the proxied entities header is set
+                final Map<String, String> updatedHeaders = new HashMap<>();
+                replicator.addProxiedEntitiesHeader(updatedHeaders);
+
                 // Pass in Collections.emptySet() for the node ID's so that an Exception is thrown
                 replicator.replicate(Collections.emptySet(), "GET", new URI("localhost:8080/nifi"), Collections.emptyMap(),
-                    Collections.emptyMap(), true, null, true, true, monitor);
+                    updatedHeaders, true, null, true, true, monitor);
                 Assert.fail("replicate did not throw IllegalArgumentException");
             } catch (final IllegalArgumentException iae) {
                 // expected
@@ -402,7 +461,15 @@ public class TestThreadPoolRequestReplicator {
             final URI uri = new URI("http://localhost:8080/processors/1");
             final Entity entity = new ProcessorEntity();
 
-            replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, null, true, true, monitor);
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // ensure the proxied entities header is set
+            final Map<String, String> updatedHeaders = new HashMap<>();
+            replicator.addProxiedEntitiesHeader(updatedHeaders);
+
+            replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, updatedHeaders, true, null, true, true, monitor);
 
             // wait for monitor to be notified.
             postNotifyLatch.await();
@@ -447,7 +514,15 @@ public class TestThreadPoolRequestReplicator {
             final URI uri = new URI("http://localhost:8080/processors/1");
             final Entity entity = new ProcessorEntity();
 
-            replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, new HashMap<>(), true, null, true, true, monitor);
+            // set the user
+            final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(StandardNiFiUser.ANONYMOUS));
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+
+            // ensure the proxied entities header is set
+            final Map<String, String> updatedHeaders = new HashMap<>();
+            replicator.addProxiedEntitiesHeader(updatedHeaders);
+
+            replicator.replicate(nodeIds, HttpMethod.GET, uri, entity, updatedHeaders, true, null, true, true, monitor);
 
             // wait for monitor to be notified.
             postNotifyLatch.await();
@@ -460,6 +535,10 @@ public class TestThreadPoolRequestReplicator {
     }
 
     private void withReplicator(final WithReplicator function, final Status status, final long delayMillis, final RuntimeException failure) {
+        withReplicator(function, status, delayMillis, failure, "<>");
+    }
+
+    private void withReplicator(final WithReplicator function, final Status status, final long delayMillis, final RuntimeException failure, final String expectedRequestChain) {
         final ClusterCoordinator coordinator = createClusterCoordinator();
         final NiFiProperties nifiProps = NiFiProperties.createBasicNiFiProperties(null, null);
         final ThreadPoolRequestReplicator replicator = new ThreadPoolRequestReplicator(2, new Client(), coordinator, "1 sec", "1 sec", null, null, nifiProps) {
@@ -478,6 +557,12 @@ public class TestThreadPoolRequestReplicator {
                     throw failure;
                 }
 
+                final OutBoundHeaders headers = (OutBoundHeaders) Whitebox.getInternalState(resourceBuilder, "metadata");
+                final Object proxiedEntities = headers.getFirst(ProxiedEntitiesUtils.PROXY_ENTITIES_CHAIN);
+
+                // ensure the request chain is in the request
+                Assert.assertEquals(expectedRequestChain, proxiedEntities);
+
                 // Return given response from all nodes.
                 final ClientResponse clientResponse = new ClientResponse(status, new InBoundHeaders(), new ByteArrayInputStream(new byte[0]), null);
                 return new NodeResponse(nodeId, method, uri, clientResponse, -1L, requestId);

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java
index 311fbc7..06f389f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-custom-ui-utilities/src/main/java/org/apache/nifi/web/HttpServletRequestContext.java
@@ -16,7 +16,6 @@
  */
 package org.apache.nifi.web;
 
-import java.security.cert.X509Certificate;
 import javax.servlet.http.HttpServletRequest;
 
 /**
@@ -42,19 +41,7 @@ public class HttpServletRequestContext implements NiFiWebRequestContext {
 
     @Override
     public String getProxiedEntitiesChain() {
-        String xProxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain");
-        final X509Certificate cert = extractClientCertificate(request);
-        if (cert != null) {
-            final String extractedPrincipal = extractPrincipal(cert);
-            final String formattedPrincipal = formatProxyDn(extractedPrincipal);
-            if (xProxiedEntitiesChain == null || xProxiedEntitiesChain.trim().isEmpty()) {
-                xProxiedEntitiesChain = formattedPrincipal;
-            } else {
-                xProxiedEntitiesChain += formattedPrincipal;
-            }
-        }
-
-        return xProxiedEntitiesChain;
+        return null;
     }
 
     /**
@@ -74,27 +61,4 @@ public class HttpServletRequestContext implements NiFiWebRequestContext {
         return request.getParameter(ID_PARAM);
     }
 
-    /**
-     * Utility methods that have been copied into this class to reduce the
-     * dependency footprint of this artifact. These utility methods typically
-     * live in web-utilities but that would pull in spring, jersey, jackson,
-     * etc.
-     */
-    private X509Certificate extractClientCertificate(HttpServletRequest request) {
-        X509Certificate[] certs = (X509Certificate[]) request.getAttribute("javax.servlet.request.X509Certificate");
-
-        if (certs != null && certs.length > 0) {
-            return certs[0];
-        }
-
-        return null;
-    }
-
-    private String extractPrincipal(X509Certificate cert) {
-        return cert.getSubjectDN().getName().trim();
-    }
-
-    private String formatProxyDn(String dn) {
-        return "<" + dn + ">";
-    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
index c39fbc3..027aa73 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiContentAccess.java
@@ -19,6 +19,16 @@ package org.apache.nifi.web;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
 import com.sun.jersey.core.util.MultivaluedMapImpl;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.cluster.coordination.ClusterCoordinator;
@@ -30,17 +40,6 @@ import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.controller.repository.claim.ContentDirection;
 import org.apache.nifi.util.NiFiProperties;
 
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MultivaluedMap;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  *
  */
@@ -77,9 +76,6 @@ public class StandardNiFiContentAccess implements ContentAccess {
 
             // set the headers
             final Map<String, String> headers = new HashMap<>();
-            if (StringUtils.isNotBlank(request.getProxiedEntitiesChain())) {
-                headers.put("X-ProxiedEntitiesChain", request.getProxiedEntitiesChain());
-            }
 
             // ensure we were able to detect the cluster node id
             if (request.getClusterNodeId() == null) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
index 177e557..f866b24 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiWebConfigurationContext.java
@@ -17,6 +17,20 @@
 package org.apache.nifi.web;
 
 import com.sun.jersey.core.util.MultivaluedMapImpl;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLEncoder;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.action.Action;
 import org.apache.nifi.action.Component;
@@ -65,21 +79,6 @@ import org.apache.nifi.web.util.ClientResponseUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.core.MultivaluedMap;
-import javax.ws.rs.core.Response;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-
 /**
  * Implements the NiFiWebConfigurationContext interface to support a context in both standalone and clustered environments.
  */
@@ -854,9 +853,6 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
     private Map<String, String> getHeaders(final NiFiWebRequestContext config) {
         final Map<String, String> headers = new HashMap<>();
         headers.put("Accept", "application/json,application/xml");
-        if (StringUtils.isNotBlank(config.getProxiedEntitiesChain())) {
-            headers.put("X-ProxiedEntitiesChain", config.getProxiedEntitiesChain());
-        }
         return headers;
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java
index 6154576..a7d83e3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-access/src/main/java/org/apache/nifi/web/ContentRequestContext.java
@@ -45,7 +45,12 @@ public interface ContentRequestContext {
     /**
      * The proxy chain for the current request, if applicable.
      *
+     * Update:
+     * This method has been deprecated since the entire proxy
+     * chain is able to be rebuilt using the current user if necessary.
+     *
      * @return the proxied entities chain
      */
+    @Deprecated
     String getProxiedEntitiesChain();
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
index b1decd0..b2114d5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-content-viewer/src/main/java/org/apache/nifi/web/ContentViewerController.java
@@ -22,7 +22,6 @@ import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
-
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
@@ -299,7 +298,6 @@ public class ContentViewerController extends HttpServlet {
     private ContentRequestContext getContentRequest(final HttpServletRequest request) {
         final String ref = request.getParameter("ref");
         final String clientId = request.getParameter("clientId");
-        final String proxiedEntitiesChain = request.getHeader("X-ProxiedEntitiesChain");
 
         final URI refUri = URI.create(ref);
         final String query = refUri.getQuery();
@@ -334,7 +332,7 @@ public class ContentViewerController extends HttpServlet {
 
             @Override
             public String getProxiedEntitiesChain() {
-                return proxiedEntitiesChain;
+                return null;
             }
         };
     }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
index 0ff9fed..09f45bf 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/ProxiedEntitiesUtils.java
@@ -16,29 +16,36 @@
  */
 package org.apache.nifi.web.security;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  *
  */
 public class ProxiedEntitiesUtils {
+    private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class);
 
     public static final String PROXY_ENTITIES_CHAIN = "X-ProxiedEntitiesChain";
     public static final String PROXY_ENTITIES_ACCEPTED = "X-ProxiedEntitiesAccepted";
     public static final String PROXY_ENTITIES_DETAILS = "X-ProxiedEntitiesDetails";
 
-    private static final Pattern proxyChainPattern = Pattern.compile("<(.*?)>");
+    private static final String GT = ">";
+    private static final String ESCAPED_GT = "\\\\>";
+    private static final String LT = "<";
+    private static final String ESCAPED_LT = "\\\\<";
+
+    private static final String ANONYMOUS_CHAIN = "<>";
 
     /**
      * Formats the specified DN to be set as a HTTP header using well known conventions.
@@ -47,7 +54,51 @@ public class ProxiedEntitiesUtils {
      * @return the dn formatted as an HTTP header
      */
     public static String formatProxyDn(String dn) {
-        return "<" + dn + ">";
+        return LT + sanitizeDn(dn) + GT;
+    }
+
+    /**
+     * If a user provides a DN with the sequence '><', they could escape the tokenization process and impersonate another user.
+     * <p>
+     * Example:
+     * <p>
+     * Provided DN: {@code jdoe><alopresto} -> {@code <jdoe><alopresto><proxy...>} would allow the user to impersonate jdoe
+     *
+     * @param rawDn the unsanitized DN
+     * @return the sanitized DN
+     */
+    private static String sanitizeDn(String rawDn) {
+        if (StringUtils.isEmpty(rawDn)) {
+            return rawDn;
+        } else {
+            String sanitizedDn = rawDn.replaceAll(GT, ESCAPED_GT).replaceAll(LT, ESCAPED_LT);
+            if (!sanitizedDn.equals(rawDn)) {
+                logger.warn("The provided DN [" + rawDn + "] contained dangerous characters that were escaped to [" + sanitizedDn + "]");
+            }
+            return sanitizedDn;
+        }
+    }
+
+    /**
+     * Reconstitutes the original DN from the sanitized version passed in the proxy chain.
+     * <p>
+     * Example:
+     * <p>
+     * {@code alopresto\>\<proxy1} -> {@code alopresto><proxy1}
+     *
+     * @param sanitizedDn the sanitized DN
+     * @return the original DN
+     */
+    private static String unsanitizeDn(String sanitizedDn) {
+        if (StringUtils.isEmpty(sanitizedDn)) {
+            return sanitizedDn;
+        } else {
+            String unsanitizedDn = sanitizedDn.replaceAll(ESCAPED_GT, GT).replaceAll(ESCAPED_LT, LT);
+            if (!unsanitizedDn.equals(sanitizedDn)) {
+                logger.warn("The provided DN [" + sanitizedDn + "] had been escaped, and was reconstituted to the dangerous DN [" + unsanitizedDn + "]");
+            }
+            return unsanitizedDn;
+        }
     }
 
     /**
@@ -58,9 +109,24 @@ public class ProxiedEntitiesUtils {
      */
     public static List<String> tokenizeProxiedEntitiesChain(String rawProxyChain) {
         final List<String> proxyChain = new ArrayList<>();
-        final Matcher rawProxyChainMatcher = proxyChainPattern.matcher(rawProxyChain);
-        while (rawProxyChainMatcher.find()) {
-            proxyChain.add(rawProxyChainMatcher.group(1));
+        if (!StringUtils.isEmpty(rawProxyChain)) {
+            // Split the String on the >< token
+            List<String> elements = Arrays.asList(StringUtils.splitByWholeSeparatorPreserveAllTokens(rawProxyChain, "><"));
+
+            // Unsanitize each DN and collect back
+            elements = elements.stream().map(ProxiedEntitiesUtils::unsanitizeDn).collect(Collectors.toList());
+
+            // Remove the leading < from the first element
+            elements.set(0, elements.get(0).replaceFirst(LT, ""));
+
+            // Remove the trailing > from the last element
+            int last = elements.size() - 1;
+            String lastElement = elements.get(last);
+            if (lastElement.endsWith(GT)) {
+                elements.set(last, lastElement.substring(0, lastElement.length() - 1));
+            }
+
+            proxyChain.addAll(elements);
         }
 
         return proxyChain;
@@ -74,42 +140,12 @@ public class ProxiedEntitiesUtils {
      */
     public static String buildProxiedEntitiesChainString(final NiFiUser user) {
         // calculate the dn chain
-        final List<String> proxyChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
-        return formatProxyDn(StringUtils.join(proxyChain, "><"));
-    }
-
-    /**
-     * Builds the proxy chain from the specified request and user.
-     *
-     * @param request the request
-     * @param username the username
-     * @return the proxy chain in list form
-     */
-    public static List<String> buildProxiedEntitiesChain(final HttpServletRequest request, final String username) {
-        final String chain = buildProxiedEntitiesChainString(request, username);
-        return tokenizeProxiedEntitiesChain(chain);
-    }
-
-    /**
-     * Builds the dn chain from the specified request and user.
-     *
-     * @param request the request
-     * @param username the username
-     * @return the dn chain in string form
-     */
-    public static String buildProxiedEntitiesChainString(final HttpServletRequest request, final String username) {
-        String principal;
-        if (username.startsWith("<") && username.endsWith(">")) {
-            principal = username;
-        } else {
-            principal = formatProxyDn(username);
-        }
-
-        // look for a proxied user
-        if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
-            principal = request.getHeader(PROXY_ENTITIES_CHAIN) + principal;
+        List<String> proxyChain = NiFiUserUtils.buildProxiedEntitiesChain(user);
+        if (proxyChain.isEmpty()) {
+            return ANONYMOUS_CHAIN;
         }
-        return principal;
+        proxyChain = proxyChain.stream().map(ProxiedEntitiesUtils::formatProxyDn).collect(Collectors.toList());
+        return StringUtils.join(proxyChain, "");
     }
 
     public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
index 16160a4..b5835d0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/x509/X509AuthenticationProvider.java
@@ -16,6 +16,11 @@
  */
 package org.apache.nifi.web.security.x509;
 
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authentication.AuthenticationResponse;
 import org.apache.nifi.authorization.AuthorizationRequest;
@@ -37,12 +42,6 @@ import org.apache.nifi.web.security.token.NiFiAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-
 /**
  *
  */
@@ -79,19 +78,27 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
 
             // add the chain as appropriate to each proxy
             NiFiUser proxy = null;
-            for (final ListIterator<String> chainIter = proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious();) {
-                final String identity = mapIdentity(chainIter.previous());
+            for (final ListIterator<String> chainIter = proxyChain.listIterator(proxyChain.size()); chainIter.hasPrevious(); ) {
+                String identity = chainIter.previous();
+
+                // determine if the user is anonymous
+                final boolean isAnonymous = StringUtils.isBlank(identity);
+                if (isAnonymous) {
+                    identity = StandardNiFiUser.ANONYMOUS_IDENTITY;
+                } else {
+                    identity = mapIdentity(identity);
+                }
 
                 if (chainIter.hasPrevious()) {
                     // authorize this proxy in order to authenticate this user
                     final AuthorizationRequest proxyAuthorizationRequest = new AuthorizationRequest.Builder()
-                        .identity(identity)
-                        .anonymous(false)
-                        .accessAttempt(true)
-                        .action(RequestAction.WRITE)
-                        .resource(ResourceFactory.getProxyResource())
-                        .userContext(proxy == null ? getUserContext(request) : null) // only set the context for the real user
-                        .build();
+                            .identity(identity)
+                            .anonymous(isAnonymous)
+                            .accessAttempt(true)
+                            .action(RequestAction.WRITE)
+                            .resource(ResourceFactory.getProxyResource())
+                            .userContext(proxy == null ? getUserContext(request) : null) // only set the context for the real user
+                            .build();
 
                     final AuthorizationResult proxyAuthorizationResult = authorizer.authorize(proxyAuthorizationRequest);
                     if (!Result.Approved.equals(proxyAuthorizationResult.getResult())) {
@@ -99,20 +106,34 @@ public class X509AuthenticationProvider extends NiFiAuthenticationProvider {
                     }
                 }
 
-                // only set the client address for user making the request, we don't know the client address of the proxies
-                if (proxy == null) {
-                    proxy = new StandardNiFiUser(identity, proxy, request.getClientAddress());
-                } else {
-                    proxy = new StandardNiFiUser(identity, proxy, null);
-                }
+                // Only set the client address for user making the request because we don't know the client address of the proxies
+                String clientAddress = (proxy == null) ? request.getClientAddress() : null;
+                proxy = createUser(identity, proxy, clientAddress, isAnonymous);
             }
 
             return new NiFiAuthenticationToken(new NiFiUserDetails(proxy));
         }
     }
 
-    private Map<String,String> getUserContext(final X509AuthenticationRequestToken request) {
-        final Map<String,String> userContext;
+    /**
+     * Returns a regular user populated with the provided values, or if the user should be anonymous, a well-formed instance of the anonymous user with the provided values.
+     *
+     * @param identity      the user's identity
+     * @param chain         the proxied entities
+     * @param clientAddress the requesting IP address
+     * @param isAnonymous   if true, an anonymous user will be returned (identity will be ignored)
+     * @return the populated user
+     */
+    protected static NiFiUser createUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
+        if (isAnonymous) {
+            return StandardNiFiUser.populateAnonymousUser(chain, clientAddress);
+        } else {
+            return new StandardNiFiUser(identity, chain, clientAddress);
+        }
+    }
+
+    private Map<String, String> getUserContext(final X509AuthenticationRequestToken request) {
+        final Map<String, String> userContext;
         if (!StringUtils.isBlank(request.getClientAddress())) {
             userContext = new HashMap<>();
             userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), request.getClientAddress());

http://git-wip-us.apache.org/repos/asf/nifi/blob/e80ad953/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy
new file mode 100644
index 0000000..460b4eb
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/test/groovy/org/apache/nifi/web/security/ProxiedEntitiesUtilsTest.groovy
@@ -0,0 +1,265 @@
+/*
+ * 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.web.security
+
+import org.apache.nifi.authorization.user.NiFiUser
+import org.junit.After
+import org.junit.Before
+import org.junit.BeforeClass
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+
+@RunWith(JUnit4.class)
+class ProxiedEntitiesUtilsTest {
+    private static final Logger logger = LoggerFactory.getLogger(ProxiedEntitiesUtils.class)
+
+    private static final String SAFE_USER_NAME_ANDY = "alopresto"
+    private static final String SAFE_USER_DN_ANDY = "CN=${SAFE_USER_NAME_ANDY}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_JOHN = "jdoe"
+    private static final String SAFE_USER_DN_JOHN = "CN=${SAFE_USER_NAME_JOHN}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_PROXY_1 = "proxy1.nifi.apache.org"
+    private static final String SAFE_USER_DN_PROXY_1 = "CN=${SAFE_USER_NAME_PROXY_1}, OU=Apache NiFi"
+
+    private static final String SAFE_USER_NAME_PROXY_2 = "proxy2.nifi.apache.org"
+    private static final String SAFE_USER_DN_PROXY_2 = "CN=${SAFE_USER_NAME_PROXY_2}, OU=Apache NiFi"
+
+    private static
+    final String MALICIOUS_USER_NAME_JOHN = "${SAFE_USER_NAME_JOHN}, OU=Apache NiFi><CN=${SAFE_USER_NAME_PROXY_1}"
+    private static final String MALICIOUS_USER_DN_JOHN = "CN=${MALICIOUS_USER_NAME_JOHN}, OU=Apache NiFi"
+
+    private static
+    final String MALICIOUS_USER_NAME_JOHN_ESCAPED = sanitizeDn(MALICIOUS_USER_NAME_JOHN)
+    private static final String MALICIOUS_USER_DN_JOHN_ESCAPED = sanitizeDn(MALICIOUS_USER_DN_JOHN)
+
+    @BeforeClass
+    static void setUpOnce() throws Exception {
+        logger.metaClass.methodMissing = { String name, args ->
+            logger.info("[${name?.toUpperCase()}] ${(args as List).join(" ")}")
+        }
+    }
+
+    @Before
+    void setUp() {
+    }
+
+    @After
+    void tearDown() {
+    }
+
+    private static String sanitizeDn(String dn = "") {
+        dn.replaceAll(/>/, '\\\\>').replaceAll('<', '\\\\<')
+    }
+
+    private static String printUnicodeString(final String raw) {
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < raw.size(); i++) {
+            int codePoint = Character.codePointAt(raw, i)
+            int charCount = Character.charCount(codePoint)
+            if (charCount > 1) {
+                i += charCount - 1 // 2.
+                if (i >= raw.length()) {
+                    throw new IllegalArgumentException("Code point indicated more characters than available")
+                }
+            }
+            sb.append(String.format("\\u%04x ", codePoint))
+        }
+        return sb.toString().trim()
+    }
+
+    @Test
+    void testSanitizeDnShouldHandleFuzzing() throws Exception {
+        // Arrange
+        final String DESIRED_NAME = SAFE_USER_NAME_JOHN
+        logger.info("  Desired name: ${DESIRED_NAME} |  ${printUnicodeString(DESIRED_NAME)}")
+
+        // Contains various attempted >< escapes, trailing NULL, and BACKSPACE + 'n'
+        final List MALICIOUS_NAMES = [MALICIOUS_USER_NAME_JOHN,
+                                      SAFE_USER_NAME_JOHN + ">",
+                                      SAFE_USER_NAME_JOHN + "><>",
+                                      SAFE_USER_NAME_JOHN + "\\>",
+                                      SAFE_USER_NAME_JOHN + "\u003e",
+                                      SAFE_USER_NAME_JOHN + "\u005c\u005c\u003e",
+                                      SAFE_USER_NAME_JOHN + "\u0000",
+                                      SAFE_USER_NAME_JOHN + "\u0008n"]
+
+        // Act
+        MALICIOUS_NAMES.each { String name ->
+            logger.info("      Raw name: ${name} | ${printUnicodeString(name)}")
+            String sanitizedName = ProxiedEntitiesUtils.sanitizeDn(name)
+            logger.info("Sanitized name: ${sanitizedName} | ${printUnicodeString(sanitizedName)}")
+
+            // Assert
+            assert sanitizedName != DESIRED_NAME
+        }
+    }
+
+    @Test
+    void testShouldFormatProxyDn() throws Exception {
+        // Arrange
+        final String DN = SAFE_USER_DN_JOHN
+        logger.info(" Provided proxy DN: ${DN}")
+
+        final String EXPECTED_PROXY_DN = "<${DN}>"
+        logger.info(" Expected proxy DN: ${EXPECTED_PROXY_DN}")
+
+        // Act
+        String forjohnedProxyDn = ProxiedEntitiesUtils.formatProxyDn(DN)
+        logger.info("Forjohned proxy DN: ${forjohnedProxyDn}")
+
+        // Assert
+        assert forjohnedProxyDn == EXPECTED_PROXY_DN
+    }
+
+    @Test
+    void testFormatProxyDnShouldHandleMaliciousInput() throws Exception {
+        // Arrange
+        final String DN = MALICIOUS_USER_DN_JOHN
+        logger.info(" Provided proxy DN: ${DN}")
+
+        final String SANITIZED_DN = sanitizeDn(DN)
+        final String EXPECTED_PROXY_DN = "<${SANITIZED_DN}>"
+        logger.info(" Expected proxy DN: ${EXPECTED_PROXY_DN}")
+
+        // Act
+        String forjohnedProxyDn = ProxiedEntitiesUtils.formatProxyDn(DN)
+        logger.info("Forjohned proxy DN: ${forjohnedProxyDn}")
+
+        // Assert
+        assert forjohnedProxyDn == EXPECTED_PROXY_DN
+    }
+
+    @Test
+    void testShouldBuildProxyChain() throws Exception {
+        // Arrange
+        def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1 }, getChain: { -> null }, isAnonymous: { -> false}] as NiFiUser
+        def mockJohn = [getIdentity: { -> SAFE_USER_NAME_JOHN }, getChain: { -> mockProxy1 }, isAnonymous: { -> false}] as NiFiUser
+
+        // Act
+        String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(mockJohn)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == "<${SAFE_USER_NAME_JOHN}><${SAFE_USER_NAME_PROXY_1}>" as String
+    }
+
+    @Test
+    void testBuildProxyChainFromNullUserShouldBeAnonymous() throws Exception {
+        // Arrange
+
+        // Act
+        String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(null)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == "<>"
+    }
+
+    @Test
+    void testBuildProxyChainFromAnonymousUserShouldBeAnonymous() throws Exception {
+        // Arrange
+        def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1 }, getChain: { -> null }, isAnonymous: { -> false}] as NiFiUser
+        def mockAnonymous = [getIdentity: { -> "anonymous" }, getChain: { -> mockProxy1 }, isAnonymous: { -> true}] as NiFiUser
+
+        // Act
+        String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(mockAnonymous)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == "<><${SAFE_USER_NAME_PROXY_1}>" as String
+    }
+
+    @Test
+    void testBuildProxyChainShouldHandleMaliciousUser() throws Exception {
+        // Arrange
+        def mockProxy1 = [getIdentity: { -> SAFE_USER_NAME_PROXY_1 }, getChain: { -> null }, isAnonymous: { -> false}] as NiFiUser
+        def mockJohn = [getIdentity: { -> MALICIOUS_USER_NAME_JOHN }, getChain: { -> mockProxy1 }, isAnonymous: { -> false}] as NiFiUser
+
+        // Act
+        String proxiedEntitiesChain = ProxiedEntitiesUtils.buildProxiedEntitiesChainString(mockJohn)
+        logger.info("Proxied entities chain: ${proxiedEntitiesChain}")
+
+        // Assert
+        assert proxiedEntitiesChain == "<${MALICIOUS_USER_NAME_JOHN_ESCAPED}><${SAFE_USER_NAME_PROXY_1}>" as String
+    }
+
+    @Test
+    void testShouldTokenizeProxiedEntitiesChainWithUserNames() throws Exception {
+        // Arrange
+        final List NAMES = [SAFE_USER_NAME_JOHN, SAFE_USER_NAME_PROXY_1, SAFE_USER_NAME_PROXY_2]
+        final String RAW_PROXY_CHAIN = "<${NAMES.join("><")}>"
+        logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}")
+
+        // Act
+        def tokenizedNames = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN)
+        logger.info("Tokenized proxy chain: ${tokenizedNames}")
+
+        // Assert
+        assert tokenizedNames == NAMES
+    }
+
+    @Test
+    void testShouldTokenizeProxiedEntitiesChainWithDNs() throws Exception {
+        // Arrange
+        final List DNS = [SAFE_USER_DN_JOHN, SAFE_USER_DN_PROXY_1, SAFE_USER_DN_PROXY_2]
+        final String RAW_PROXY_CHAIN = "<${DNS.join("><")}>"
+        logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}")
+
+        // Act
+        def tokenizedDns = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN)
+        logger.info("Tokenized proxy chain: ${tokenizedDns.collect { "\"${it}\"" }}")
+
+        // Assert
+        assert tokenizedDns == DNS
+    }
+
+    @Test
+    void testShouldTokenizeProxiedEntitiesChainWithAnonymousUser() throws Exception {
+        // Arrange
+        final List NAMES = ["", SAFE_USER_NAME_PROXY_1, SAFE_USER_NAME_PROXY_2]
+        final String RAW_PROXY_CHAIN = "<${NAMES.join("><")}>"
+        logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}")
+
+        // Act
+        def tokenizedNames = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN)
+        logger.info("Tokenized proxy chain: ${tokenizedNames}")
+
+        // Assert
+        assert tokenizedNames == NAMES
+    }
+
+    @Test
+    void testTokenizeProxiedEntitiesChainShouldHandleMaliciousUser() throws Exception {
+        // Arrange
+        final List NAMES = [MALICIOUS_USER_NAME_JOHN, SAFE_USER_NAME_PROXY_1, SAFE_USER_NAME_PROXY_2]
+        final String RAW_PROXY_CHAIN = "<${NAMES.collect { sanitizeDn(it) }.join("><")}>"
+        logger.info(" Provided proxy chain: ${RAW_PROXY_CHAIN}")
+
+        // Act
+        def tokenizedNames = ProxiedEntitiesUtils.tokenizeProxiedEntitiesChain(RAW_PROXY_CHAIN)
+        logger.info("Tokenized proxy chain: ${tokenizedNames.collect { "\"${it}\"" }}")
+
+        // Assert
+        assert tokenizedNames == NAMES
+        assert tokenizedNames.size() == NAMES.size()
+        assert !tokenizedNames.contains(SAFE_USER_NAME_JOHN)
+    }
+}


Mime
View raw message