NIFI-1901: - Building component based access control tests for Connections, 
Funnels, Labels, Input Ports, Output Ports, Processors, and Process Groups. - 
Restoring Access Token Endpoint tests.

This closes #518

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/fbd299e8
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/fbd299e8
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/fbd299e8

Branch: refs/heads/master
Commit: fbd299e885499170b550d17662cc482f550e0b3e
Parents: c2b26eb
Author: Matt Gilman <[email protected]>
Authored: Mon Jun 13 15:39:44 2016 -0400
Committer: Bryan Bende <[email protected]>
Committed: Fri Jun 17 10:16:09 2016 -0400

----------------------------------------------------------------------
 .travis.yml                                     |   2 +-
 .../org/apache/nifi/util/NiFiProperties.java    |   4 +-
 .../authorization/AuthorizerFactoryBean.java    |   2 +-
 .../nifi/web/api/entity/ComponentEntity.java    |  17 -
 .../nifi-web/nifi-web-api/pom.xml               |   5 +
 .../apache/nifi/web/api/ConnectionResource.java |  46 +-
 .../nifi/web/api/ControllerServiceResource.java |  65 +--
 .../org/apache/nifi/web/api/FunnelResource.java |  42 +-
 .../apache/nifi/web/api/ProcessorResource.java  |  60 +--
 .../nifi/web/api/ReportingTaskResource.java     |   8 +-
 .../apache/nifi/integration/NiFiWebApiTest.java |  54 +-
 .../accesscontrol/AccessControlHelper.java      | 120 +++++
 .../accesscontrol/AccessTokenEndpointTest.java  |   5 +-
 .../ConnectionAccessControlTest.java            | 427 ++++++++++++++++
 .../accesscontrol/FunnelAccessControlTest.java  | 361 ++++++++++++++
 .../InputPortAccessControlTest.java             | 405 +++++++++++++++
 .../accesscontrol/LabelAccessControlTest.java   | 402 +++++++++++++++
 .../OutputPortAccessControlTest.java            | 405 +++++++++++++++
 .../ProcessGroupAccessControlTest.java          | 405 +++++++++++++++
 .../ProcessorAccessControlTest.java             | 489 +++++++++++++++++++
 .../integration/util/NiFiTestAuthorizer.java    |  48 +-
 .../integration/util/SourceTestProcessor.java   |   9 +-
 .../access-control/authority-providers.xml      |  24 -
 .../resources/access-control/authorizers.xml    |  24 +
 .../resources/access-control/nifi.properties    |   6 +-
 .../LoginIdentityProviderFactoryBean.java       |   2 +-
 26 files changed, 3261 insertions(+), 176 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/.travis.yml
----------------------------------------------------------------------
diff --git a/.travis.yml b/.travis.yml
index 811a4c2..db579ce 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -14,4 +14,4 @@ before_install:
   - sed -e "s/^\\(127\\.0\\.0\\.1.*\\)/\\1 $(hostname | cut -c1-63)/" 
/etc/hosts | sudo tee /etc/hosts
   - sed -i.bak -e 
's|https://nexus.codehaus.org/snapshots/|https://oss.sonatype.org/content/repositories/codehaus-snapshots/|g'
 ~/.m2/settings.xml
 
-script: mvn clean install -Pcontrib-check
+script: mvn clean install -T4

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/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 ea3b959..73a54c5 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
@@ -508,7 +508,7 @@ public class NiFiProperties extends Properties {
     /**
      * @return the user authorizers file
      */
-    public File getAuthorizerConfiguraitonFile() {
+    public File getAuthorizerConfigurationFile() {
         final String value = getProperty(AUTHORIZER_CONFIGURATION_FILE);
         if (StringUtils.isBlank(value)) {
             return new File(DEFAULT_AUTHORIZER_CONFIGURATION_FILE);
@@ -520,7 +520,7 @@ public class NiFiProperties extends Properties {
     /**
      * @return the user authorities file
      */
-    public File getLoginIdentityProviderConfiguraitonFile() {
+    public File getLoginIdentityProviderConfigurationFile() {
         final String value = 
getProperty(LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);
         if (StringUtils.isBlank(value)) {
             return new 
File(DEFAULT_LOGIN_IDENTITY_PROVIDER_CONFIGURATION_FILE);

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
index 378c805..e4d7318 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java
@@ -126,7 +126,7 @@ public class AuthorizerFactoryBean implements FactoryBean, 
DisposableBean, Autho
     }
 
     private Authorizers loadAuthorizersConfiguration() throws Exception {
-        final File authorizersConfigurationFile = 
properties.getAuthorizerConfiguraitonFile();
+        final File authorizersConfigurationFile = 
properties.getAuthorizerConfigurationFile();
 
         // load the authorizers from the specified file
         if (authorizersConfigurationFile.exists()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentEntity.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentEntity.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentEntity.java
index 2f7798e..231ce53 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentEntity.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentEntity.java
@@ -31,7 +31,6 @@ public class ComponentEntity extends Entity {
 
     private RevisionDTO revision;
     private String id;
-    private String uri;
     private PositionDTO position;
     private AccessPolicyDTO accessPolicy;
 
@@ -66,22 +65,6 @@ public class ComponentEntity extends Entity {
     }
 
     /**
-     * The uri for linking to this component in this NiFi.
-     *
-     * @return The uri
-     */
-    @ApiModelProperty(
-        value = "The URI for futures requests to the component."
-    )
-    public String getUri() {
-        return uri;
-    }
-
-    public void setUri(String uri) {
-        this.uri = uri;
-    }
-
-    /**
      * The position of this component in the UI if applicable, null otherwise.
      *
      * @return The position

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
index 6142066..b0d4dda 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/pom.xml
@@ -211,6 +211,11 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-authorizer</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
             <scope>provided</scope>

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
index 9ea31dc..caa7b36 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectionResource.java
@@ -16,8 +16,25 @@
  */
 package org.apache.nifi.web.api;
 
-import java.net.URI;
-import java.util.Set;
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+import com.wordnik.swagger.annotations.ApiParam;
+import com.wordnik.swagger.annotations.ApiResponse;
+import com.wordnik.swagger.annotations.ApiResponses;
+import com.wordnik.swagger.annotations.Authorization;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.UpdateResult;
+import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.FlowFileSummaryDTO;
+import org.apache.nifi.web.api.dto.ListingRequestDTO;
+import org.apache.nifi.web.api.entity.ConnectionEntity;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -33,27 +50,8 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.Authorizer;
-import org.apache.nifi.authorization.RequestAction;
-import org.apache.nifi.authorization.resource.Authorizable;
-import org.apache.nifi.web.NiFiServiceFacade;
-import org.apache.nifi.web.Revision;
-import org.apache.nifi.web.UpdateResult;
-import org.apache.nifi.web.api.dto.ConnectionDTO;
-import org.apache.nifi.web.api.dto.FlowFileSummaryDTO;
-import org.apache.nifi.web.api.dto.ListingRequestDTO;
-import org.apache.nifi.web.api.entity.ConnectionEntity;
-import org.apache.nifi.web.api.request.ClientIdParameter;
-import org.apache.nifi.web.api.request.LongParameter;
-
-import com.wordnik.swagger.annotations.Api;
-import com.wordnik.swagger.annotations.ApiOperation;
-import com.wordnik.swagger.annotations.ApiParam;
-import com.wordnik.swagger.annotations.ApiResponse;
-import com.wordnik.swagger.annotations.ApiResponses;
-import com.wordnik.swagger.annotations.Authorization;
+import java.net.URI;
+import java.util.Set;
 
 /**
  * RESTful endpoint for managing a Connection.
@@ -286,7 +284,7 @@ public class ConnectionResource extends ApplicationResource 
{
 
                 // generate the response
                 if (updateResult.isNew()) {
-                    return 
clusterContext(generateCreatedResponse(URI.create(entity.getUri()), 
entity)).build();
+                    return 
clusterContext(generateCreatedResponse(URI.create(entity.getComponent().getUri()),
 entity)).build();
                 } else {
                     return clusterContext(generateOkResponse(entity)).build();
                 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
index f1dc652..e05552f 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
@@ -16,30 +16,12 @@
  */
 package org.apache.nifi.web.api;
 
-import java.net.URI;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+import com.wordnik.swagger.annotations.ApiParam;
+import com.wordnik.swagger.annotations.ApiResponse;
+import com.wordnik.swagger.annotations.ApiResponses;
+import com.wordnik.swagger.annotations.Authorization;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
@@ -66,12 +48,28 @@ import org.apache.nifi.web.api.request.LongParameter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.wordnik.swagger.annotations.Api;
-import com.wordnik.swagger.annotations.ApiOperation;
-import com.wordnik.swagger.annotations.ApiParam;
-import com.wordnik.swagger.annotations.ApiResponse;
-import com.wordnik.swagger.annotations.ApiResponses;
-import com.wordnik.swagger.annotations.Authorization;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * RESTful endpoint for managing a Controller Service.
@@ -369,14 +367,17 @@ public class ControllerServiceResource extends 
ApplicationResource {
             return replicate(HttpMethod.POST);
         }
 
-        // handle expects request (usually from the cluster manager)
         final boolean validationPhase = isValidationPhase(httpServletRequest);
-        if (validationPhase) {
+        if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) {
             // authorize access
             serviceFacade.authorizeAccess(lookup -> {
                 final Authorizable controllerService = 
lookup.getControllerService(id);
                 controllerService.authorize(authorizer, RequestAction.WRITE);
             });
+        }
+
+        // handle expects request (usually from the cluster manager)
+        if (validationPhase) {
             serviceFacade.verifyCanClearControllerServiceState(id);
             return generateContinueResponse().build();
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
index 7ec395c..acaa2b4 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FunnelResource.java
@@ -16,8 +16,23 @@
  */
 package org.apache.nifi.web.api;
 
-import java.net.URI;
-import java.util.Set;
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+import com.wordnik.swagger.annotations.ApiParam;
+import com.wordnik.swagger.annotations.ApiResponse;
+import com.wordnik.swagger.annotations.ApiResponses;
+import com.wordnik.swagger.annotations.Authorization;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+import org.apache.nifi.web.UpdateResult;
+import org.apache.nifi.web.api.dto.FunnelDTO;
+import org.apache.nifi.web.api.entity.FunnelEntity;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
@@ -33,25 +48,8 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
-
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.authorization.Authorizer;
-import org.apache.nifi.authorization.RequestAction;
-import org.apache.nifi.authorization.resource.Authorizable;
-import org.apache.nifi.web.NiFiServiceFacade;
-import org.apache.nifi.web.Revision;
-import org.apache.nifi.web.UpdateResult;
-import org.apache.nifi.web.api.dto.FunnelDTO;
-import org.apache.nifi.web.api.entity.FunnelEntity;
-import org.apache.nifi.web.api.request.ClientIdParameter;
-import org.apache.nifi.web.api.request.LongParameter;
-
-import com.wordnik.swagger.annotations.Api;
-import com.wordnik.swagger.annotations.ApiOperation;
-import com.wordnik.swagger.annotations.ApiParam;
-import com.wordnik.swagger.annotations.ApiResponse;
-import com.wordnik.swagger.annotations.ApiResponses;
-import com.wordnik.swagger.annotations.Authorization;
+import java.net.URI;
+import java.util.Set;
 
 /**
  * RESTful endpoint for managing a Funnel.
@@ -316,7 +314,7 @@ public class FunnelResource extends ApplicationResource {
             revision,
             lookup -> {
                 final Authorizable funnel = lookup.getFunnel(id);
-                funnel.authorize(authorizer, RequestAction.READ);
+                funnel.authorize(authorizer, RequestAction.WRITE);
             },
             () -> serviceFacade.verifyDeleteFunnel(id),
             () -> {

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
index 912366e..dccb1f3 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
@@ -16,27 +16,12 @@
  */
 package org.apache.nifi.web.api;
 
-import java.net.URI;
-import java.util.List;
-import java.util.Set;
-
-import javax.servlet.ServletContext;
-import javax.servlet.http.HttpServletRequest;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.DELETE;
-import javax.ws.rs.DefaultValue;
-import javax.ws.rs.GET;
-import javax.ws.rs.HttpMethod;
-import javax.ws.rs.POST;
-import javax.ws.rs.PUT;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
-import javax.ws.rs.core.Context;
-import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
-
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+import com.wordnik.swagger.annotations.ApiParam;
+import com.wordnik.swagger.annotations.ApiResponse;
+import com.wordnik.swagger.annotations.ApiResponses;
+import com.wordnik.swagger.annotations.Authorization;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
@@ -57,12 +42,25 @@ import 
org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.LongParameter;
 
-import com.wordnik.swagger.annotations.Api;
-import com.wordnik.swagger.annotations.ApiOperation;
-import com.wordnik.swagger.annotations.ApiParam;
-import com.wordnik.swagger.annotations.ApiResponse;
-import com.wordnik.swagger.annotations.ApiResponses;
-import com.wordnik.swagger.annotations.Authorization;
+import javax.servlet.ServletContext;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+import java.util.Set;
 
 /**
  * RESTful endpoint for managing a Processor.
@@ -377,13 +375,17 @@ public class ProcessorResource extends 
ApplicationResource {
             return replicate(HttpMethod.POST);
         }
 
-        // handle expects request (usually from the cluster manager)
-        if (isValidationPhase(httpServletRequest)) {
+        final boolean isValidationPhase = 
isValidationPhase(httpServletRequest);
+        if (isValidationPhase || !isTwoPhaseRequest(httpServletRequest)) {
             // authorize access
             serviceFacade.authorizeAccess(lookup -> {
                 final Authorizable processor = lookup.getProcessor(id);
                 processor.authorize(authorizer, RequestAction.WRITE);
             });
+        }
+
+        // handle expects request (usually from the cluster manager)
+        if (isValidationPhase) {
             serviceFacade.verifyCanClearProcessorState(id);
             return generateContinueResponse().build();
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
index 66ddfbf..f1fa304 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
@@ -355,13 +355,17 @@ public class ReportingTaskResource extends 
ApplicationResource {
             return replicate(HttpMethod.POST);
         }
 
-        // handle expects request (usually from the cluster manager)
-        if (isValidationPhase(httpServletRequest)) {
+        final boolean isValidationPhase = 
isValidationPhase(httpServletRequest);
+        if (isValidationPhase || !isTwoPhaseRequest(httpServletRequest)) {
             // authorize access
             serviceFacade.authorizeAccess(lookup -> {
                 final Authorizable reportingTask = lookup.getReportingTask(id);
                 reportingTask.authorize(authorizer, RequestAction.WRITE);
             });
+        }
+
+        // handle expects request (usually from the cluster manager)
+        if (isValidationPhase) {
             serviceFacade.verifyCanClearReportingTaskState(id);
             return generateContinueResponse().build();
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/NiFiWebApiTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/NiFiWebApiTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/NiFiWebApiTest.java
index eecc28a..8067491 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/NiFiWebApiTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/NiFiWebApiTest.java
@@ -20,18 +20,19 @@ import com.sun.jersey.api.client.Client;
 import com.sun.jersey.api.client.ClientResponse;
 import com.sun.jersey.api.client.ClientResponse.Status;
 import org.apache.nifi.connectable.ConnectableType;
-import org.apache.nifi.integration.accesscontrol.DfmAccessControlTest;
 import org.apache.nifi.integration.util.NiFiTestUser;
 import org.apache.nifi.integration.util.SourceTestProcessor;
 import org.apache.nifi.integration.util.TerminationTestProcessor;
 import org.apache.nifi.web.api.dto.ConnectableDTO;
 import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.FunnelDTO;
 import org.apache.nifi.web.api.dto.LabelDTO;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.apache.nifi.web.api.dto.ProcessGroupDTO;
 import org.apache.nifi.web.api.dto.ProcessorDTO;
 import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.entity.ConnectionEntity;
+import org.apache.nifi.web.api.entity.FunnelEntity;
 import org.apache.nifi.web.api.entity.LabelEntity;
 import org.apache.nifi.web.api.entity.PortEntity;
 import org.apache.nifi.web.api.entity.ProcessGroupEntity;
@@ -48,11 +49,15 @@ import java.util.Set;
 public class NiFiWebApiTest {
 
     public static void populateFlow(Client client, String baseUrl, String 
clientId) throws Exception {
-        NiFiTestUser dfm = new NiFiTestUser(client, 
DfmAccessControlTest.DFM_USER_DN);
+
+    }
+
+    public static void populateFlow(Client client, String baseUrl, 
NiFiTestUser user, String clientId) throws Exception {
 
         // -----------------------------------------------
-        // Create a local selection processor
+        // Create a source processor
         // -----------------------------------------------
+
         // create the local selection processor
         ProcessorDTO processorDTO = new ProcessorDTO();
         processorDTO.setName("Pick up");
@@ -69,7 +74,7 @@ public class NiFiWebApiTest {
         processorEntity.setComponent(processorDTO);
 
         // add the processor
-        ClientResponse response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/processors", processorEntity);
+        ClientResponse response = user.testPost(baseUrl + 
"/process-groups/root/processors", processorEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -87,6 +92,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Create a termination processor
         // -----------------------------------------------
+
         // create the termination processor
         processorDTO = new ProcessorDTO();
         processorDTO.setName("End");
@@ -98,7 +104,7 @@ public class NiFiWebApiTest {
         processorEntity.setComponent(processorDTO);
 
         // add the processor
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/processors", processorEntity);
+        response = user.testPost(baseUrl + "/process-groups/root/processors", 
processorEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -116,6 +122,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Connect the two processors
         // -----------------------------------------------
+
         ConnectableDTO source = new ConnectableDTO();
         source.setId(localSelectionId);
         source.setType(ConnectableType.PROCESSOR.name());
@@ -140,7 +147,7 @@ public class NiFiWebApiTest {
         connectionEntity.setComponent(connectionDTO);
 
         // add the processor
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/connections", connectionEntity);
+        response = user.testPost(baseUrl + "/process-groups/root/connections", 
connectionEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -153,6 +160,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Create a label
         // -----------------------------------------------
+
         // create the label
         LabelDTO labelDTO = new LabelDTO();
         labelDTO.setLabel("Test label");
@@ -163,7 +171,30 @@ public class NiFiWebApiTest {
         labelEntity.setComponent(labelDTO);
 
         // add the label
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/labels", labelEntity);
+        response = user.testPost(baseUrl + "/process-groups/root/labels", 
labelEntity);
+
+        // ensure a successful response
+        if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
+            // since it was unable to create the component attempt to extract 
an
+            // error message from the response body
+            final String responseEntity = response.getEntity(String.class);
+            throw new Exception("Unable to populate initial flow: " + 
responseEntity);
+        }
+
+        // -----------------------------------------------
+        // Create a funnel
+        // -----------------------------------------------
+
+        // create the funnel
+        FunnelDTO funnelDTO = new FunnelDTO();
+
+        // create the funnel entity
+        FunnelEntity funnelEntity = new FunnelEntity();
+        funnelEntity.setRevision(revision);
+        funnelEntity.setComponent(funnelDTO);
+
+        // add the funnel
+        response = user.testPost(baseUrl + "/process-groups/root/funnels", 
funnelEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -176,6 +207,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Create a process group
         // -----------------------------------------------
+
         // create the process group
         ProcessGroupDTO processGroup = new ProcessGroupDTO();
         processGroup.setName("group name");
@@ -186,7 +218,7 @@ public class NiFiWebApiTest {
         processGroupEntity.setComponent(processGroup);
 
         // add the process group
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/process-group-references", processGroupEntity);
+        response = user.testPost(baseUrl + 
"/process-groups/root/process-groups", processGroupEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -199,6 +231,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Create an input port
         // -----------------------------------------------
+
         // create the input port
         PortDTO inputPort = new PortDTO();
         inputPort.setName("input");
@@ -209,7 +242,7 @@ public class NiFiWebApiTest {
         inputPortEntity.setComponent(inputPort);
 
         // add the input port
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/input-ports", inputPortEntity);
+        response = user.testPost(baseUrl + "/process-groups/root/input-ports", 
inputPortEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {
@@ -222,6 +255,7 @@ public class NiFiWebApiTest {
         // -----------------------------------------------
         // Create a output ports
         // -----------------------------------------------
+
         // create the process group
         PortDTO outputPort = new PortDTO();
         outputPort.setName("output");
@@ -232,7 +266,7 @@ public class NiFiWebApiTest {
         outputPortEntity.setComponent(outputPort);
 
         // add the output port
-        response = dfm.testPost(baseUrl + 
"/controller/process-groups/root/output-ports", outputPortEntity);
+        response = user.testPost(baseUrl + 
"/process-groups/root/output-ports", outputPortEntity);
 
         // ensure a successful response
         if (Status.CREATED.getStatusCode() != 
response.getStatusInfo().getStatusCode()) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java
new file mode 100644
index 0000000..2576253
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessControlHelper.java
@@ -0,0 +1,120 @@
+/*
+ * 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.integration.accesscontrol;
+
+import org.apache.nifi.integration.NiFiWebApiTest;
+import org.apache.nifi.integration.util.NiFiTestAuthorizer;
+import org.apache.nifi.integration.util.NiFiTestServer;
+import org.apache.nifi.integration.util.NiFiTestUser;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.NarClassLoaders;
+import org.apache.nifi.util.NiFiProperties;
+
+import java.io.File;
+
+/**
+ * Access control test for the dfm user.
+ */
+public class AccessControlHelper {
+
+    public static final String NONE_CLIENT_ID = "client-id";
+    public static final String READ_CLIENT_ID = "r-client-id";
+    public static final String WRITE_CLIENT_ID = "w-client-id";
+    public static final String READ_WRITE_CLIENT_ID = "rw-client-id";
+
+    private NiFiTestUser readUser;
+    private NiFiTestUser writeUser;
+    private NiFiTestUser readWriteUser;
+    private NiFiTestUser noneUser;
+
+    private static final String CONTEXT_PATH = "/nifi-api";
+
+    private String flowXmlPath;
+    private NiFiTestServer server;
+    private String baseUrl;
+
+    public AccessControlHelper(final String flowXmlPath) throws Exception {
+        this.flowXmlPath = flowXmlPath;
+
+        // look for the flow.xml and toss it
+        File flow = new File(flowXmlPath);
+        if (flow.exists()) {
+            flow.delete();
+        }
+
+        // configure the location of the nifi properties
+        File nifiPropertiesFile = new 
File("src/test/resources/access-control/nifi.properties");
+        System.setProperty(NiFiProperties.PROPERTIES_FILE_PATH, 
nifiPropertiesFile.getAbsolutePath());
+
+        // update the flow.xml property
+        NiFiProperties props = NiFiProperties.getInstance();
+        props.setProperty(NiFiProperties.FLOW_CONFIGURATION_FILE, flowXmlPath);
+
+        // load extensions
+        NarClassLoaders.load(props);
+        ExtensionManager.discoverExtensions();
+
+        // start the server
+        server = new NiFiTestServer("src/main/webapp", CONTEXT_PATH);
+        server.startServer();
+        server.loadFlow();
+
+        // get the base url
+        baseUrl = server.getBaseUrl() + CONTEXT_PATH;
+
+        // create the users - user purposefully decoupled from clientId (same 
user different browsers tabs)
+        readUser = new NiFiTestUser(server.getClient(), 
NiFiTestAuthorizer.READ_USER_DN);
+        writeUser = new NiFiTestUser(server.getClient(), 
NiFiTestAuthorizer.WRITE_USER_DN);
+        readWriteUser = new NiFiTestUser(server.getClient(), 
NiFiTestAuthorizer.READ_WRITE_USER_DN);
+        noneUser = new NiFiTestUser(server.getClient(), 
NiFiTestAuthorizer.NONE_USER_DN);
+
+        // populate the initial data flow
+        NiFiWebApiTest.populateFlow(server.getClient(), baseUrl, 
readWriteUser, READ_WRITE_CLIENT_ID);
+    }
+
+    public NiFiTestUser getReadUser() {
+        return readUser;
+    }
+
+    public NiFiTestUser getWriteUser() {
+        return writeUser;
+    }
+
+    public NiFiTestUser getReadWriteUser() {
+        return readWriteUser;
+    }
+
+    public NiFiTestUser getNoneUser() {
+        return noneUser;
+    }
+
+    public String getBaseUrl() {
+        return baseUrl;
+    }
+
+    public void cleanup() throws Exception {
+        // shutdown the server
+        server.shutdownServer();
+        server = null;
+
+        // look for the flow.xml and toss it
+        File flow = new File(flowXmlPath);
+        if (flow.exists()) {
+            flow.delete();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
index 0302153..6f30b03 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/AccessTokenEndpointTest.java
@@ -37,7 +37,6 @@ import org.apache.nifi.web.util.WebUtils;
 import org.junit.AfterClass;
 import org.junit.Assert;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 
 import javax.net.ssl.SSLContext;
@@ -48,7 +47,6 @@ import java.util.Map;
 /**
  * Access token endpoint test.
  */
-@Ignore
 public class AccessTokenEndpointTest {
 
     private static final String CLIENT_ID = "token-endpoint-id";
@@ -147,7 +145,7 @@ public class AccessTokenEndpointTest {
     }
 
     private ProcessorDTO createProcessor(final String token) throws Exception {
-        String url = BASE_URL + "/controller/process-groups/root/processors";
+        String url = BASE_URL + "/process-groups/root/processors";
 
         // authorization header
         Map<String, String> headers = new HashMap<>();
@@ -224,7 +222,6 @@ public class AccessTokenEndpointTest {
     public void testRequestAccessUsingToken() throws Exception {
         String accessStatusUrl = BASE_URL + "/access";
         String accessTokenUrl = BASE_URL + "/access/token";
-        String registrationUrl = BASE_URL + "/controller/users";
 
         ClientResponse response = TOKEN_USER.testGet(accessStatusUrl);
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ConnectionAccessControlTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ConnectionAccessControlTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ConnectionAccessControlTest.java
new file mode 100644
index 0000000..ab01ea8
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ConnectionAccessControlTest.java
@@ -0,0 +1,427 @@
+/*
+ * 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.integration.accesscontrol;
+
+import com.sun.jersey.api.client.ClientResponse;
+import org.apache.nifi.connectable.ConnectableType;
+import org.apache.nifi.integration.util.NiFiTestAuthorizer;
+import org.apache.nifi.integration.util.NiFiTestUser;
+import org.apache.nifi.web.api.dto.ConnectableDTO;
+import org.apache.nifi.web.api.dto.ConnectionDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.entity.ConnectionEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Access control test for connections.
+ */
+public class ConnectionAccessControlTest {
+
+    private static final String FLOW_XML_PATH = 
"target/test-classes/access-control/flow-connections.xml";
+
+    private static AccessControlHelper helper;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        helper = new AccessControlHelper(FLOW_XML_PATH);
+    }
+
+    /**
+     * Ensures the READ user can get a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserGetConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ WRITE user can get a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserGetConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the WRITE user can get a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserGetConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the NONE user can get a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserGetConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ user cannot put a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserPutConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        // attempt update the name
+        entity.getRevision().setClientId(READ_CLIENT_ID);
+        entity.getComponent().setName("Updated Name");
+
+        // perform the request
+        final ClientResponse response = updateConnection(helper.getReadUser(), 
entity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ_WRITE user can put a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserPutConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        final String updatedName = "Updated Name";
+
+        // attempt to update the name
+        final long version = entity.getRevision().getVersion();
+        
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
+        entity.getComponent().setName(updatedName);
+
+        // perform the request
+        final ClientResponse response = 
updateConnection(helper.getReadWriteUser(), entity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final ConnectionEntity responseEntity = 
response.getEntity(ConnectionEntity.class);
+
+        // verify
+        assertEquals(READ_WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+        assertEquals(updatedName, responseEntity.getComponent().getName());
+    }
+
+    /**
+     * Ensures the READ_WRITE user can put a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserPutConnectionThroughInheritedPolicy() throws 
Exception {
+        final ConnectionEntity entity = 
createConnection(NiFiTestAuthorizer.NO_POLICY_COMPONENT_NAME);
+
+        final String updatedName = "Updated name";
+
+        // attempt to update the name
+        final long version = entity.getRevision().getVersion();
+        entity.getRevision().setClientId(READ_WRITE_CLIENT_ID);
+        entity.getComponent().setName(updatedName);
+
+        // perform the request
+        final ClientResponse response = 
updateConnection(helper.getReadWriteUser(), entity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final ConnectionEntity responseEntity = 
response.getEntity(ConnectionEntity.class);
+
+        // verify
+        assertEquals(AccessControlHelper.READ_WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+        assertEquals(updatedName, responseEntity.getComponent().getName());
+    }
+
+    /**
+     * Ensures the WRITE user can put a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserPutConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        final String updatedName = "Updated Name";
+
+        // attempt to update the name
+        final ConnectionDTO requestDto = new ConnectionDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setName(updatedName);
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
+
+        final ConnectionEntity requestEntity = new ConnectionEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = 
updateConnection(helper.getWriteUser(), requestEntity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final ConnectionEntity responseEntity = 
response.getEntity(ConnectionEntity.class);
+
+        // verify
+        assertEquals(WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+    }
+
+    /**
+     * Ensures the NONE user cannot put a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserPutConnection() throws Exception {
+        final ConnectionEntity entity = 
getRandomConnection(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        final String updatedName = "Updated Name";
+
+        // attempt to update the name
+        final ConnectionDTO requestDto = new ConnectionDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setName(updatedName);
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
+
+        final ConnectionEntity requestEntity = new ConnectionEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = updateConnection(helper.getNoneUser(), 
requestEntity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ user cannot delete a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserDeleteConnection() throws Exception {
+        verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 
403);
+    }
+
+    /**
+     * Ensures the READ WRITE user can delete a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserDeleteConnection() throws Exception {
+        verifyDelete(helper.getReadWriteUser(), 
AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the WRITE user can delete a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserDeleteConnection() throws Exception {
+        verifyDelete(helper.getWriteUser(), 
AccessControlHelper.WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the NONE user can delete a connection.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserDeleteConnection() throws Exception {
+        verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
+    }
+
+    private ConnectionEntity getRandomConnection(final NiFiTestUser user) 
throws Exception {
+        final String url = helper.getBaseUrl() + "/flow/process-groups/root";
+
+        // get the connections
+        final ClientResponse response = user.testGet(url);
+
+        // ensure the response was successful
+        assertEquals(200, response.getStatus());
+
+        // unmarshal
+        final ProcessGroupFlowEntity flowEntity = 
response.getEntity(ProcessGroupFlowEntity.class);
+        final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
+        final Set<ConnectionEntity> connections = flowDto.getConnections();
+
+        // ensure the correct number of connection
+        assertFalse(connections.isEmpty());
+
+        // use the first connection as the target
+        Iterator<ConnectionEntity> connectionIter = connections.iterator();
+        assertTrue(connectionIter.hasNext());
+        return connectionIter.next();
+    }
+
+    private ClientResponse updateConnection(final NiFiTestUser user, final 
ConnectionEntity entity) throws Exception {
+        final String url = helper.getBaseUrl() + "/connections/" + 
entity.getId();
+
+        // perform the request
+        return user.testPut(url, entity);
+    }
+
+    private ConnectionEntity createConnection(final String name) throws 
Exception {
+        String url = helper.getBaseUrl() + "/process-groups/root/connections";
+
+        // get two processors
+        final ProcessorEntity one = 
ProcessorAccessControlTest.createProcessor(helper, "one");
+        final ProcessorEntity two = 
ProcessorAccessControlTest.createProcessor(helper, "two");
+
+        // create the source connectable
+        ConnectableDTO source = new ConnectableDTO();
+        source.setId(one.getId());
+        source.setType(ConnectableType.PROCESSOR.name());
+
+        // create the target connectable
+        ConnectableDTO target = new ConnectableDTO();
+        target.setId(two.getId());
+        target.setType(ConnectableType.PROCESSOR.name());
+
+        // create the relationships
+        Set<String> relationships = new HashSet<>();
+        relationships.add("success");
+
+        // create the connection
+        ConnectionDTO connection = new ConnectionDTO();
+        connection.setName(name);
+        connection.setSource(source);
+        connection.setDestination(target);
+        connection.setSelectedRelationships(relationships);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(READ_WRITE_CLIENT_ID);
+        revision.setVersion(0L);
+
+        // create the entity body
+        ConnectionEntity entity = new ConnectionEntity();
+        entity.setRevision(revision);
+        entity.setComponent(connection);
+
+        // perform the request
+        ClientResponse response = helper.getReadWriteUser().testPost(url, 
entity);
+
+        // ensure the request is successful
+        assertEquals(201, response.getStatus());
+
+        // get the entity body
+        entity = response.getEntity(ConnectionEntity.class);
+
+        // verify creation
+        connection = entity.getComponent();
+        assertEquals(name, connection.getName());
+
+        // get the connection
+        return entity;
+    }
+
+    private void verifyDelete(final NiFiTestUser user, final String clientId, 
final int responseCode) throws Exception {
+        final ConnectionEntity entity = createConnection("Copy");
+
+        // create the entity body
+        final Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("revision", 
String.valueOf(entity.getRevision().getVersion()));
+        queryParams.put("clientId", clientId);
+
+        // perform the request
+        ClientResponse response = 
user.testDelete(entity.getComponent().getUri(), queryParams);
+
+        // ensure the request is failed with a forbidden status code
+        assertEquals(responseCode, response.getStatus());
+    }
+
+    @AfterClass
+    public static void cleanup() throws Exception {
+        helper.cleanup();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/FunnelAccessControlTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/FunnelAccessControlTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/FunnelAccessControlTest.java
new file mode 100644
index 0000000..26590b9
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/FunnelAccessControlTest.java
@@ -0,0 +1,361 @@
+/*
+ * 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.integration.accesscontrol;
+
+import com.sun.jersey.api.client.ClientResponse;
+import org.apache.nifi.integration.util.NiFiTestUser;
+import org.apache.nifi.web.api.dto.FunnelDTO;
+import org.apache.nifi.web.api.dto.PositionDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.entity.FunnelEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Access control test for funnels.
+ */
+public class FunnelAccessControlTest {
+
+    private static final String FLOW_XML_PATH = 
"target/test-classes/access-control/flow-funnels.xml";
+
+    private static AccessControlHelper helper;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        helper = new AccessControlHelper(FLOW_XML_PATH);
+    }
+
+    /**
+     * Ensures the READ user can get a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserGetFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ WRITE user can get a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserGetFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the WRITE user can get a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserGetFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the NONE user can get a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserGetFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ user cannot put a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserPutFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        // attempt update the position
+        entity.getRevision().setClientId(READ_CLIENT_ID);
+        entity.getComponent().setPosition(new PositionDTO(0.0, 10.0));
+
+        // perform the request
+        final ClientResponse response = updateFunnel(helper.getReadUser(), 
entity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ_WRITE user can put a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserPutFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        final double y = 15.0;
+
+        // attempt to update the position
+        final long version = entity.getRevision().getVersion();
+        
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
+        entity.getComponent().setPosition(new PositionDTO(0.0, y));
+
+        // perform the request
+        final ClientResponse response = 
updateFunnel(helper.getReadWriteUser(), entity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final FunnelEntity responseEntity = 
response.getEntity(FunnelEntity.class);
+
+        // verify
+        assertEquals(READ_WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+        assertEquals(y, 
responseEntity.getComponent().getPosition().getY().doubleValue(), 0);
+    }
+
+    /**
+     * Ensures the WRITE user can put a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserPutFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        final double y = 15.0;
+
+        // attempt to update the position
+        final FunnelDTO requestDto = new FunnelDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setPosition(new PositionDTO(0.0, y));
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
+
+        final FunnelEntity requestEntity = new FunnelEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = updateFunnel(helper.getWriteUser(), 
requestEntity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final FunnelEntity responseEntity = 
response.getEntity(FunnelEntity.class);
+
+        // verify
+        assertEquals(WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+    }
+
+    /**
+     * Ensures the NONE user cannot put a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserPutFunnel() throws Exception {
+        final FunnelEntity entity = getRandomFunnel(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        // attempt to update the position
+        final FunnelDTO requestDto = new FunnelDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setPosition(new PositionDTO(0.0, 15.0));
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
+
+        final FunnelEntity requestEntity = new FunnelEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = updateFunnel(helper.getNoneUser(), 
requestEntity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ user cannot delete a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserDeleteFunnel() throws Exception {
+        verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 
403);
+    }
+
+    /**
+     * Ensures the READ WRITE user can delete a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserDeleteFunnel() throws Exception {
+        verifyDelete(helper.getReadWriteUser(), 
AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the WRITE user can delete a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserDeleteFunnel() throws Exception {
+        verifyDelete(helper.getWriteUser(), 
AccessControlHelper.WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the NONE user can delete a funnel.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserDeleteFunnel() throws Exception {
+        verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
+    }
+
+    private FunnelEntity getRandomFunnel(final NiFiTestUser user) throws 
Exception {
+        final String url = helper.getBaseUrl() + "/flow/process-groups/root";
+
+        // get the flow
+        final ClientResponse response = user.testGet(url);
+
+        // ensure the response was successful
+        assertEquals(200, response.getStatus());
+
+        // unmarshal
+        final ProcessGroupFlowEntity flowEntity = 
response.getEntity(ProcessGroupFlowEntity.class);
+        final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
+        final Set<FunnelEntity> funnels = flowDto.getFunnels();
+
+        // ensure the correct number of funnels
+        assertFalse(funnels.isEmpty());
+
+        // use the first funnel as the target
+        Iterator<FunnelEntity> funnelIter = funnels.iterator();
+        assertTrue(funnelIter.hasNext());
+        return funnelIter.next();
+    }
+
+    private ClientResponse updateFunnel(final NiFiTestUser user, final 
FunnelEntity entity) throws Exception {
+        final String url = helper.getBaseUrl() + "/funnels/" + entity.getId();
+
+        // perform the request
+        return user.testPut(url, entity);
+    }
+
+    private FunnelEntity createFunnel() throws Exception {
+        String url = helper.getBaseUrl() + "/process-groups/root/funnels";
+
+        // create the funnel
+        FunnelDTO funnel = new FunnelDTO();
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(READ_WRITE_CLIENT_ID);
+        revision.setVersion(0L);
+
+        // create the entity body
+        FunnelEntity entity = new FunnelEntity();
+        entity.setRevision(revision);
+        entity.setComponent(funnel);
+
+        // perform the request
+        ClientResponse response = helper.getReadWriteUser().testPost(url, 
entity);
+
+        // ensure the request is successful
+        assertEquals(201, response.getStatus());
+
+        // get the entity body
+        return response.getEntity(FunnelEntity.class);
+    }
+
+    private void verifyDelete(final NiFiTestUser user, final String clientId, 
final int responseCode) throws Exception {
+        final FunnelEntity entity = createFunnel();
+
+        // create the entity body
+        final Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("revision", 
String.valueOf(entity.getRevision().getVersion()));
+        queryParams.put("clientId", clientId);
+
+        // perform the request
+        ClientResponse response = 
user.testDelete(entity.getComponent().getUri(), queryParams);
+
+        // ensure the request is failed with a forbidden status code
+        assertEquals(responseCode, response.getStatus());
+    }
+
+    @AfterClass
+    public static void cleanup() throws Exception {
+        helper.cleanup();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/fbd299e8/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/InputPortAccessControlTest.java
----------------------------------------------------------------------
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/InputPortAccessControlTest.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/InputPortAccessControlTest.java
new file mode 100644
index 0000000..9f9a966
--- /dev/null
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/InputPortAccessControlTest.java
@@ -0,0 +1,405 @@
+/*
+ * 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.integration.accesscontrol;
+
+import com.sun.jersey.api.client.ClientResponse;
+import org.apache.nifi.integration.util.NiFiTestAuthorizer;
+import org.apache.nifi.integration.util.NiFiTestUser;
+import org.apache.nifi.web.api.dto.PortDTO;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.flow.FlowDTO;
+import org.apache.nifi.web.api.entity.PortEntity;
+import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
+import static 
org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Access control test for input ports.
+ */
+public class InputPortAccessControlTest {
+
+    private static final String FLOW_XML_PATH = 
"target/test-classes/access-control/flow-input-ports.xml";
+
+    private static AccessControlHelper helper;
+    private static int count = 0;
+
+    @BeforeClass
+    public static void setup() throws Exception {
+        helper = new AccessControlHelper(FLOW_XML_PATH);
+    }
+
+    /**
+     * Ensures the READ user can get an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserGetInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ WRITE user can get an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserGetInputPort() throws Exception {
+        final PortEntity entity = 
getRandomInputPort(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the WRITE user can get an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserGetInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the NONE user can get an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserGetInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+    }
+
+    /**
+     * Ensures the READ user cannot put an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserPutInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getReadUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        // attempt update the name
+        entity.getRevision().setClientId(READ_CLIENT_ID);
+        entity.getComponent().setName("Updated Name" + count++);
+
+        // perform the request
+        final ClientResponse response = updateInputPort(helper.getReadUser(), 
entity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ_WRITE user can put an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserPutInputPort() throws Exception {
+        final PortEntity entity = 
getRandomInputPort(helper.getReadWriteUser());
+        assertTrue(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNotNull(entity.getComponent());
+
+        final String updatedName = "Updated Name" + count++;
+
+        // attempt to update the name
+        final long version = entity.getRevision().getVersion();
+        
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
+        entity.getComponent().setName(updatedName);
+
+        // perform the request
+        final ClientResponse response = 
updateInputPort(helper.getReadWriteUser(), entity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final PortEntity responseEntity = response.getEntity(PortEntity.class);
+
+        // verify
+        assertEquals(READ_WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+        assertEquals(updatedName, responseEntity.getComponent().getName());
+    }
+
+    /**
+     * Ensures the READ_WRITE user can put an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserPutInputPortThroughInheritedPolicy() throws 
Exception {
+        final PortEntity entity = 
createInputPort(NiFiTestAuthorizer.NO_POLICY_COMPONENT_NAME);
+
+        final String updatedName = "Updated name" + count++;
+
+        // attempt to update the name
+        final long version = entity.getRevision().getVersion();
+        entity.getRevision().setClientId(READ_WRITE_CLIENT_ID);
+        entity.getComponent().setName(updatedName);
+
+        // perform the request
+        final ClientResponse response = 
updateInputPort(helper.getReadWriteUser(), entity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final PortEntity responseEntity = response.getEntity(PortEntity.class);
+
+        // verify
+        assertEquals(AccessControlHelper.READ_WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+        assertEquals(updatedName, responseEntity.getComponent().getName());
+    }
+
+    /**
+     * Ensures the WRITE user can put an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserPutInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getWriteUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertTrue(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        final String updatedName = "Updated Name" + count++;
+
+        // attempt to update the name
+        final PortDTO requestDto = new PortDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setName(updatedName);
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
+
+        final PortEntity requestEntity = new PortEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = updateInputPort(helper.getWriteUser(), 
requestEntity);
+
+        // ensure successful response
+        assertEquals(200, response.getStatus());
+
+        // get the response
+        final PortEntity responseEntity = response.getEntity(PortEntity.class);
+
+        // verify
+        assertEquals(WRITE_CLIENT_ID, 
responseEntity.getRevision().getClientId());
+        assertEquals(version + 1, 
responseEntity.getRevision().getVersion().longValue());
+    }
+
+    /**
+     * Ensures the NONE user cannot put an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserPutInputPort() throws Exception {
+        final PortEntity entity = getRandomInputPort(helper.getNoneUser());
+        assertFalse(entity.getAccessPolicy().getCanRead());
+        assertFalse(entity.getAccessPolicy().getCanWrite());
+        assertNull(entity.getComponent());
+
+        final String updatedName = "Updated Name" + count++;
+
+        // attempt to update the name
+        final PortDTO requestDto = new PortDTO();
+        requestDto.setId(entity.getId());
+        requestDto.setName(updatedName);
+
+        final long version = entity.getRevision().getVersion();
+        final RevisionDTO requestRevision = new RevisionDTO();
+        requestRevision.setVersion(version);
+        requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
+
+        final PortEntity requestEntity = new PortEntity();
+        requestEntity.setId(entity.getId());
+        requestEntity.setRevision(requestRevision);
+        requestEntity.setComponent(requestDto);
+
+        // perform the request
+        final ClientResponse response = updateInputPort(helper.getNoneUser(), 
requestEntity);
+
+        // ensure forbidden response
+        assertEquals(403, response.getStatus());
+    }
+
+    /**
+     * Ensures the READ user cannot delete an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadUserDeleteInputPort() throws Exception {
+        verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 
403);
+    }
+
+    /**
+     * Ensures the READ WRITE user can delete an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testReadWriteUserDeleteInputPort() throws Exception {
+        verifyDelete(helper.getReadWriteUser(), 
AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the WRITE user can delete an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testWriteUserDeleteInputPort() throws Exception {
+        verifyDelete(helper.getWriteUser(), 
AccessControlHelper.WRITE_CLIENT_ID, 200);
+    }
+
+    /**
+     * Ensures the NONE user can delete an input port.
+     *
+     * @throws Exception ex
+     */
+    @Test
+    public void testNoneUserDeleteInputPort() throws Exception {
+        verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
+    }
+
+    private PortEntity getRandomInputPort(final NiFiTestUser user) throws 
Exception {
+        final String url = helper.getBaseUrl() + "/flow/process-groups/root";
+
+        // get the input ports
+        final ClientResponse response = user.testGet(url);
+
+        // ensure the response was successful
+        assertEquals(200, response.getStatus());
+
+        // unmarshal
+        final ProcessGroupFlowEntity flowEntity = 
response.getEntity(ProcessGroupFlowEntity.class);
+        final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
+        final Set<PortEntity> inputPorts = flowDto.getInputPorts();
+
+        // ensure the correct number of input ports
+        assertFalse(inputPorts.isEmpty());
+
+        // use the first input port as the target
+        Iterator<PortEntity> inputPortIter = inputPorts.iterator();
+        assertTrue(inputPortIter.hasNext());
+        return inputPortIter.next();
+    }
+
+    private ClientResponse updateInputPort(final NiFiTestUser user, final 
PortEntity entity) throws Exception {
+        final String url = helper.getBaseUrl() + "/input-ports/" + 
entity.getId();
+
+        // perform the request
+        return user.testPut(url, entity);
+    }
+
+    private PortEntity createInputPort(final String name) throws Exception {
+        String url = helper.getBaseUrl() + "/process-groups/root/input-ports";
+
+        final String updatedName = name + count++;
+
+        // create the input port
+        PortDTO inputPort = new PortDTO();
+        inputPort.setName(updatedName);
+
+        // create the revision
+        final RevisionDTO revision = new RevisionDTO();
+        revision.setClientId(READ_WRITE_CLIENT_ID);
+        revision.setVersion(0L);
+
+        // create the entity body
+        PortEntity entity = new PortEntity();
+        entity.setRevision(revision);
+        entity.setComponent(inputPort);
+
+        // perform the request
+        ClientResponse response = helper.getReadWriteUser().testPost(url, 
entity);
+
+        // ensure the request is successful
+        assertEquals(201, response.getStatus());
+
+        // get the entity body
+        entity = response.getEntity(PortEntity.class);
+
+        // verify creation
+        inputPort = entity.getComponent();
+        assertEquals(updatedName, inputPort.getName());
+
+        // get the input port
+        return entity;
+    }
+
+    private void verifyDelete(final NiFiTestUser user, final String clientId, 
final int responseCode) throws Exception {
+        final PortEntity entity = createInputPort("Copy");
+
+        // create the entity body
+        final Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("revision", 
String.valueOf(entity.getRevision().getVersion()));
+        queryParams.put("clientId", clientId);
+
+        // perform the request
+        ClientResponse response = 
user.testDelete(entity.getComponent().getUri(), queryParams);
+
+        // ensure the request is failed with a forbidden status code
+        assertEquals(responseCode, response.getStatus());
+    }
+
+    @AfterClass
+    public static void cleanup() throws Exception {
+        helper.cleanup();
+    }
+}

Reply via email to