diff --git a/.travis.yml b/.travis.yml index 811a4c2d0a..db579ce0f5 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 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 ea3b9593e8..73a54c59e1 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); 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 378c805ee5..e4d7318a87 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()) { 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 2f7798ee73..231ce5339d 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; @@ -65,22 +64,6 @@ public class ComponentEntity extends Entity { this.id = id; } - /** - * 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. * 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 6142066d69..b0d4dda454 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 @@ -210,6 +210,11 @@ nifi-framework-authorization provided + + org.apache.nifi + nifi-authorizer + provided + javax.servlet javax.servlet-api 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 9ea31dc8e0..caa7b36729 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(); } 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 f1dc6520da..e05552ff3e 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(); } 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 7ec395c427..acaa2b4baf 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), () -> { 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 912366e952..dccb1f3163 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(); } 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 66ddfbf1ff..f1fa30454b 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(); } 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 eecc28ac5f..8067491fb6 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()) { 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 0000000000..2576253735 --- /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(); + } + } +} 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 0302153470..6f30b03539 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 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); 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 0000000000..ab01ea87be --- /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 connections = flowDto.getConnections(); + + // ensure the correct number of connection + assertFalse(connections.isEmpty()); + + // use the first connection as the target + Iterator 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 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 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(); + } +} 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 0000000000..26590b9a06 --- /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 funnels = flowDto.getFunnels(); + + // ensure the correct number of funnels + assertFalse(funnels.isEmpty()); + + // use the first funnel as the target + Iterator 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 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(); + } +} 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 0000000000..9f9a966d0c --- /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 inputPorts = flowDto.getInputPorts(); + + // ensure the correct number of input ports + assertFalse(inputPorts.isEmpty()); + + // use the first input port as the target + Iterator 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 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(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/LabelAccessControlTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/LabelAccessControlTest.java new file mode 100644 index 0000000000..ebfc3c2a59 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/LabelAccessControlTest.java @@ -0,0 +1,402 @@ +/* + * 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.LabelDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.dto.flow.FlowDTO; +import org.apache.nifi.web.api.entity.LabelEntity; +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 labels. + */ +public class LabelAccessControlTest { + + private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-labels.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 label. + * + * @throws Exception ex + */ + @Test + public void testReadUserGetLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the READ WRITE user can get a label. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserGetLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getReadWriteUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the WRITE user can get a label. + * + * @throws Exception ex + */ + @Test + public void testWriteUserGetLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the NONE user can get a label. + * + * @throws Exception ex + */ + @Test + public void testNoneUserGetLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the READ user cannot put a label. + * + * @throws Exception ex + */ + @Test + public void testReadUserPutLabel() throws Exception { + final LabelEntity entity = getRandomLabel(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().setLabel("Updated Label"); + + // perform the request + final ClientResponse response = updateLabel(helper.getReadUser(), entity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ_WRITE user can put a label. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getReadWriteUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + + final String updatedLabel = "Updated Name"; + + // attempt to update the name + final long version = entity.getRevision().getVersion(); + entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID); + entity.getComponent().setLabel(updatedLabel); + + // perform the request + final ClientResponse response = updateLabel(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final LabelEntity responseEntity = response.getEntity(LabelEntity.class); + + // verify + assertEquals(READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId()); + assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue()); + assertEquals(updatedLabel, responseEntity.getComponent().getLabel()); + } + + /** + * Ensures the READ_WRITE user can put a label. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutLabelThroughInheritedPolicy() throws Exception { + final LabelEntity entity = createLabel(NiFiTestAuthorizer.NO_POLICY_COMPONENT_NAME); + + final String updatedLabel = "Updated name"; + + // attempt to update the name + final long version = entity.getRevision().getVersion(); + entity.getRevision().setClientId(READ_WRITE_CLIENT_ID); + entity.getComponent().setLabel(updatedLabel); + + // perform the request + final ClientResponse response = updateLabel(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final LabelEntity responseEntity = response.getEntity(LabelEntity.class); + + // verify + assertEquals(AccessControlHelper.READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId()); + assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue()); + assertEquals(updatedLabel, responseEntity.getComponent().getLabel()); + } + + /** + * Ensures the WRITE user can put a label. + * + * @throws Exception ex + */ + @Test + public void testWriteUserPutLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + + final String updatedLabel = "Updated Name"; + + // attempt to update the label + final LabelDTO requestDto = new LabelDTO(); + requestDto.setId(entity.getId()); + requestDto.setLabel(updatedLabel); + + final long version = entity.getRevision().getVersion(); + final RevisionDTO requestRevision = new RevisionDTO(); + requestRevision.setVersion(version); + requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID); + + final LabelEntity requestEntity = new LabelEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateLabel(helper.getWriteUser(), requestEntity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final LabelEntity responseEntity = response.getEntity(LabelEntity.class); + + // verify + assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId()); + assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue()); + } + + /** + * Ensures the NONE user cannot put a label. + * + * @throws Exception ex + */ + @Test + public void testNoneUserPutLabel() throws Exception { + final LabelEntity entity = getRandomLabel(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + + final String updatedName = "Updated Name"; + + // attempt to update the name + final LabelDTO requestDto = new LabelDTO(); + requestDto.setId(entity.getId()); + requestDto.setLabel(updatedName); + + final long version = entity.getRevision().getVersion(); + final RevisionDTO requestRevision = new RevisionDTO(); + requestRevision.setVersion(version); + requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID); + + final LabelEntity requestEntity = new LabelEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateLabel(helper.getNoneUser(), requestEntity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ user cannot delete a label. + * + * @throws Exception ex + */ + @Test + public void testReadUserDeleteLabel() throws Exception { + verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403); + } + + /** + * Ensures the READ WRITE user can delete a label. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserDeleteLabel() throws Exception { + verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the WRITE user can delete a label. + * + * @throws Exception ex + */ + @Test + public void testWriteUserDeleteLabel() throws Exception { + verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the NONE user can delete a label. + * + * @throws Exception ex + */ + @Test + public void testNoneUserDeleteLabel() throws Exception { + verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403); + } + + private LabelEntity getRandomLabel(final NiFiTestUser user) throws Exception { + final String url = helper.getBaseUrl() + "/flow/process-groups/root"; + + // get the labels + 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 labels = flowDto.getLabels(); + + // ensure the correct number of labels + assertFalse(labels.isEmpty()); + + // use the first label as the target + Iterator labelIter = labels.iterator(); + assertTrue(labelIter.hasNext()); + return labelIter.next(); + } + + private ClientResponse updateLabel(final NiFiTestUser user, final LabelEntity entity) throws Exception { + final String url = helper.getBaseUrl() + "/labels/" + entity.getId(); + + // perform the request + return user.testPut(url, entity); + } + + private LabelEntity createLabel(final String name) throws Exception { + String url = helper.getBaseUrl() + "/process-groups/root/labels"; + + // create the label + LabelDTO label = new LabelDTO(); + label.setLabel(name); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(READ_WRITE_CLIENT_ID); + revision.setVersion(0L); + + // create the entity body + LabelEntity entity = new LabelEntity(); + entity.setRevision(revision); + entity.setComponent(label); + + // 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(LabelEntity.class); + + // verify creation + label = entity.getComponent(); + assertEquals(name, label.getLabel()); + + // get the label id + return entity; + } + + private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception { + final LabelEntity entity = createLabel("Copy"); + + // create the entity body + final Map 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(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OutputPortAccessControlTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OutputPortAccessControlTest.java new file mode 100644 index 0000000000..900b11ff92 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/OutputPortAccessControlTest.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 output ports. + */ +public class OutputPortAccessControlTest { + + private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-output-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 output port. + * + * @throws Exception ex + */ + @Test + public void testReadUserGetOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the READ WRITE user can get an output port. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserGetOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(helper.getReadWriteUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the WRITE user can get an output port. + * + * @throws Exception ex + */ + @Test + public void testWriteUserGetOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the NONE user can get an output port. + * + * @throws Exception ex + */ + @Test + public void testNoneUserGetOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the READ user cannot put an output port. + * + * @throws Exception ex + */ + @Test + public void testReadUserPutOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(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 = updateOutputPort(helper.getReadUser(), entity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ_WRITE user can put an output port. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(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 = updateOutputPort(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 output port. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutOutputPortThroughInheritedPolicy() throws Exception { + final PortEntity entity = createOutputPort(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 = updateOutputPort(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 output port. + * + * @throws Exception ex + */ + @Test + public void testWriteUserPutOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(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 = updateOutputPort(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 output port. + * + * @throws Exception ex + */ + @Test + public void testNoneUserPutOutputPort() throws Exception { + final PortEntity entity = getRandomOutputPort(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 = updateOutputPort(helper.getNoneUser(), requestEntity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ user cannot delete an output port. + * + * @throws Exception ex + */ + @Test + public void testReadUserDeleteOutputPort() throws Exception { + verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403); + } + + /** + * Ensures the READ WRITE user can delete an output port. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserDeleteOutputPort() throws Exception { + verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the WRITE user can delete an Output port. + * + * @throws Exception ex + */ + @Test + public void testWriteUserDeleteOutputPort() throws Exception { + verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the NONE user can delete an Output port. + * + * @throws Exception ex + */ + @Test + public void testNoneUserDeleteOutputPort() throws Exception { + verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403); + } + + private PortEntity getRandomOutputPort(final NiFiTestUser user) throws Exception { + final String url = helper.getBaseUrl() + "/flow/process-groups/root"; + + // get the output 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 outputPorts = flowDto.getOutputPorts(); + + // ensure the correct number of output ports + assertFalse(outputPorts.isEmpty()); + + // use the first output port as the target + Iterator outputPortIter = outputPorts.iterator(); + assertTrue(outputPortIter.hasNext()); + return outputPortIter.next(); + } + + private ClientResponse updateOutputPort(final NiFiTestUser user, final PortEntity entity) throws Exception { + final String url = helper.getBaseUrl() + "/output-ports/" + entity.getId(); + + // perform the request + return user.testPut(url, entity); + } + + private PortEntity createOutputPort(final String name) throws Exception { + String url = helper.getBaseUrl() + "/process-groups/root/output-ports"; + + final String updatedName = name + count++; + + // create the output port + PortDTO outputPort = new PortDTO(); + outputPort.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(outputPort); + + // 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 + outputPort = entity.getComponent(); + assertEquals(updatedName, outputPort.getName()); + + // get the output port + return entity; + } + + private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception { + final PortEntity entity = createOutputPort("Copy"); + + // create the entity body + final Map 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(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessGroupAccessControlTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessGroupAccessControlTest.java new file mode 100644 index 0000000000..b3179b1884 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessGroupAccessControlTest.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.ProcessGroupDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.dto.flow.FlowDTO; +import org.apache.nifi.web.api.entity.ProcessGroupEntity; +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 process groups. + */ +public class ProcessGroupAccessControlTest { + + private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-processors.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 a process group. + * + * @throws Exception ex + */ + @Test + public void testReadUserGetProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the READ WRITE user can get a process group. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserGetProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(helper.getReadWriteUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the WRITE user can get a process group. + * + * @throws Exception ex + */ + @Test + public void testWriteUserGetProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the NONE user can get a process group. + * + * @throws Exception ex + */ + @Test + public void testNoneUserGetProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the READ user cannot put a processor. + * + * @throws Exception ex + */ + @Test + public void testReadUserPutProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(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 = updateProcessGroup(helper.getReadUser(), entity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ_WRITE user can put a process group. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(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 = updateProcessGroup(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessGroupEntity responseEntity = response.getEntity(ProcessGroupEntity.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 process grup. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutProcessGroupThroughInheritedPolicy() throws Exception { + final ProcessGroupEntity entity = createProcessGroup(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 = updateProcessGroup(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessGroupEntity responseEntity = response.getEntity(ProcessGroupEntity.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 process group. + * + * @throws Exception ex + */ + @Test + public void testWriteUserPutProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(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 ProcessGroupDTO requestDto = new ProcessGroupDTO(); + 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 ProcessGroupEntity requestEntity = new ProcessGroupEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateProcessGroup(helper.getWriteUser(), requestEntity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessGroupEntity responseEntity = response.getEntity(ProcessGroupEntity.class); + + // verify + assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId()); + assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue()); + } + + /** + * Ensures the NONE user cannot put a process group. + * + * @throws Exception ex + */ + @Test + public void testNoneUserPutProcessGroup() throws Exception { + final ProcessGroupEntity entity = getRandomProcessGroup(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 ProcessGroupDTO requestDto = new ProcessGroupDTO(); + 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 ProcessGroupEntity requestEntity = new ProcessGroupEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateProcessGroup(helper.getNoneUser(), requestEntity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ user cannot delete a process group. + * + * @throws Exception ex + */ + @Test + public void testReadUserDeleteProcessGroup() throws Exception { + verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403); + } + + /** + * Ensures the READ WRITE user can delete a process group. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserDeleteProcessGroup() throws Exception { + verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the WRITE user can delete a process group. + * + * @throws Exception ex + */ + @Test + public void testWriteUserDeleteProcessGroup() throws Exception { + verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the NONE user can delete a process group. + * + * @throws Exception ex + */ + @Test + public void testNoneUserDeleteProcessGroup() throws Exception { + verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403); + } + + private ProcessGroupEntity getRandomProcessGroup(final NiFiTestUser user) throws Exception { + final String url = helper.getBaseUrl() + "/flow/process-groups/root"; + + // get the process groups + 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 processGroups = flowDto.getProcessGroups(); + + // ensure the correct number of process groups + assertFalse(processGroups.isEmpty()); + + // use the first process group as the target + Iterator processGroupIter = processGroups.iterator(); + assertTrue(processGroupIter.hasNext()); + return processGroupIter.next(); + } + + private ClientResponse updateProcessGroup(final NiFiTestUser user, final ProcessGroupEntity entity) throws Exception { + final String url = helper.getBaseUrl() + "/process-groups/" + entity.getId(); + + // perform the request + return user.testPut(url, entity); + } + + private ProcessGroupEntity createProcessGroup(final String name) throws Exception { + String url = helper.getBaseUrl() + "/process-groups/root/process-groups"; + + final String updatedName = name + count++; + + // create the process group + ProcessGroupDTO processor = new ProcessGroupDTO(); + processor.setName(updatedName); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(READ_WRITE_CLIENT_ID); + revision.setVersion(0L); + + // create the entity body + ProcessGroupEntity entity = new ProcessGroupEntity(); + entity.setRevision(revision); + entity.setComponent(processor); + + // 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(ProcessGroupEntity.class); + + // verify creation + processor = entity.getComponent(); + assertEquals(updatedName, processor.getName()); + + // get the processor + return entity; + } + + private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception { + final ProcessGroupEntity entity = createProcessGroup("Copy"); + + // create the entity body + final Map 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(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessorAccessControlTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessorAccessControlTest.java new file mode 100644 index 0000000000..80b6b764f5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/accesscontrol/ProcessorAccessControlTest.java @@ -0,0 +1,489 @@ +/* + * 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.integration.util.SourceTestProcessor; +import org.apache.nifi.web.api.dto.ProcessorDTO; +import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.dto.flow.FlowDTO; +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.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 processors. + */ +public class ProcessorAccessControlTest { + + private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-processors.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 processor. + * + * @throws Exception ex + */ + @Test + public void testReadUserGetProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the READ WRITE user can get a processor. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserGetProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadWriteUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + } + + /** + * Ensures the WRITE user can get a processor. + * + * @throws Exception ex + */ + @Test + public void testWriteUserGetProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the NONE user can get a processor. + * + * @throws Exception ex + */ + @Test + public void testNoneUserGetProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + } + + /** + * Ensures the READ user cannot put a processor. + * + * @throws Exception ex + */ + @Test + public void testReadUserPutProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(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 = updateProcessor(helper.getReadUser(), entity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ_WRITE user can put a processor. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(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 = updateProcessor(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessorEntity responseEntity = response.getEntity(ProcessorEntity.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 processor. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserPutProcessorThroughInheritedPolicy() throws Exception { + final ProcessorEntity entity = createProcessor(helper, 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 = updateProcessor(helper.getReadWriteUser(), entity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessorEntity responseEntity = response.getEntity(ProcessorEntity.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 processor. + * + * @throws Exception ex + */ + @Test + public void testWriteUserPutProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getWriteUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertTrue(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + + final String updatedName = "Updated Name"; + + // attempt to update the name + final ProcessorDTO requestDto = new ProcessorDTO(); + 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 ProcessorEntity requestEntity = new ProcessorEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateProcessor(helper.getWriteUser(), requestEntity); + + // ensure successful response + assertEquals(200, response.getStatus()); + + // get the response + final ProcessorEntity responseEntity = response.getEntity(ProcessorEntity.class); + + // verify + assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId()); + assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue()); + } + + /** + * Ensures the NONE user cannot put a processor. + * + * @throws Exception ex + */ + @Test + public void testNoneUserPutProcessor() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getNoneUser()); + assertFalse(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNull(entity.getComponent()); + + final String updatedName = "Updated Name"; + + // attempt to update the name + final ProcessorDTO requestDto = new ProcessorDTO(); + 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 ProcessorEntity requestEntity = new ProcessorEntity(); + requestEntity.setId(entity.getId()); + requestEntity.setRevision(requestRevision); + requestEntity.setComponent(requestDto); + + // perform the request + final ClientResponse response = updateProcessor(helper.getNoneUser(), requestEntity); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ user cannot clear state. + * + * @throws Exception ex + */ + @Test + public void testReadUserClearState() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + + final String url = helper.getBaseUrl() + "/processors/" + entity.getId() + "/state/clear-requests"; + + // perform the request + final ClientResponse response = helper.getReadUser().testPost(url); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the NONE user cannot clear state. + * + * @throws Exception ex + */ + @Test + public void testNoneUserClearState() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + + final String url = helper.getBaseUrl() + "/processors/" + entity.getId() + "/state/clear-requests"; + + // perform the request + final ClientResponse response = helper.getNoneUser().testPost(url); + + // ensure forbidden response + assertEquals(403, response.getStatus()); + } + + /** + * Ensures the READ WRITE user can clear state. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserClearState() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + + final String url = helper.getBaseUrl() + "/processors/" + entity.getId() + "/state/clear-requests"; + + // perform the request + final ClientResponse response = helper.getReadWriteUser().testPost(url); + + // ensure ok response + assertEquals(200, response.getStatus()); + } + + /** + * Ensures the WRITE user can clear state. + * + * @throws Exception ex + */ + @Test + public void testWriteUserClearState() throws Exception { + final ProcessorEntity entity = getRandomProcessor(helper.getReadUser()); + assertTrue(entity.getAccessPolicy().getCanRead()); + assertFalse(entity.getAccessPolicy().getCanWrite()); + assertNotNull(entity.getComponent()); + + final String url = helper.getBaseUrl() + "/processors/" + entity.getId() + "/state/clear-requests"; + + // perform the request + final ClientResponse response = helper.getWriteUser().testPost(url); + + // ensure ok response + assertEquals(200, response.getStatus()); + } + + /** + * Ensures the READ user cannot delete a processor. + * + * @throws Exception ex + */ + @Test + public void testReadUserDeleteProcessor() throws Exception { + verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403); + } + + /** + * Ensures the READ WRITE user can delete a processor. + * + * @throws Exception ex + */ + @Test + public void testReadWriteUserDeleteProcessor() throws Exception { + verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the WRITE user can delete a processor. + * + * @throws Exception ex + */ + @Test + public void testWriteUserDeleteProcessor() throws Exception { + verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200); + } + + /** + * Ensures the NONE user can delete a processor. + * + * @throws Exception ex + */ + @Test + public void testNoneUserDeleteProcessor() throws Exception { + verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403); + } + + private ProcessorEntity getRandomProcessor(final NiFiTestUser user) throws Exception { + final String url = helper.getBaseUrl() + "/flow/process-groups/root"; + + // get the processors + 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 processors = flowDto.getProcessors(); + + // ensure the correct number of processors + assertFalse(processors.isEmpty()); + + // use the first processor as the target + Iterator processorIter = processors.iterator(); + assertTrue(processorIter.hasNext()); + return processorIter.next(); + } + + private ClientResponse updateProcessor(final NiFiTestUser user, final ProcessorEntity entity) throws Exception { + final String url = helper.getBaseUrl() + "/processors/" + entity.getId(); + + // perform the request + return user.testPut(url, entity); + } + + public static ProcessorEntity createProcessor(final AccessControlHelper ach, final String name) throws Exception { + String url = ach.getBaseUrl() + "/process-groups/root/processors"; + + // create the processor + ProcessorDTO processor = new ProcessorDTO(); + processor.setName(name); + processor.setType(SourceTestProcessor.class.getName()); + + // create the revision + final RevisionDTO revision = new RevisionDTO(); + revision.setClientId(READ_WRITE_CLIENT_ID); + revision.setVersion(0L); + + // create the entity body + ProcessorEntity entity = new ProcessorEntity(); + entity.setRevision(revision); + entity.setComponent(processor); + + // perform the request + ClientResponse response = ach.getReadWriteUser().testPost(url, entity); + + // ensure the request is successful + assertEquals(201, response.getStatus()); + + // get the entity body + entity = response.getEntity(ProcessorEntity.class); + + // verify creation + processor = entity.getComponent(); + assertEquals(name, processor.getName()); + assertEquals("org.apache.nifi.integration.util.SourceTestProcessor", processor.getType()); + + // get the processor + return entity; + } + + private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception { + final ProcessorEntity entity = createProcessor(helper, "Copy"); + + // create the entity body + final Map 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(); + } +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java index 5795b6915a..419235f2f7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/NiFiTestAuthorizer.java @@ -21,14 +21,26 @@ import org.apache.nifi.authorization.AuthorizationResult; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.AuthorizerConfigurationContext; import org.apache.nifi.authorization.AuthorizerInitializationContext; +import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.authorization.resource.ResourceFactory; /** * */ public class NiFiTestAuthorizer implements Authorizer { + public static final String NO_POLICY_COMPONENT_NAME = "No policies"; + + public static final String PROXY_DN = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US"; + + public static final String NONE_USER_DN = "none@nifi"; + public static final String READ_USER_DN = "read@nifi"; + public static final String WRITE_USER_DN = "write@nifi"; + public static final String READ_WRITE_USER_DN = "readwrite@nifi"; + + public static final String TOKEN_USER = "user@nifi"; /** * Creates a new FileAuthorizationProvider. @@ -46,7 +58,41 @@ public class NiFiTestAuthorizer implements Authorizer { @Override public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { - return AuthorizationResult.approved(); + // allow proxy + if (ResourceFactory.getProxyResource().getIdentifier().equals(request.getResource().getIdentifier()) && PROXY_DN.equals(request.getIdentity())) { + return AuthorizationResult.approved(); + } + + // allow flow + if (ResourceFactory.getFlowResource().getIdentifier().equals(request.getResource().getIdentifier())) { + return AuthorizationResult.approved(); + } + + // no policy to test inheritance + if (NO_POLICY_COMPONENT_NAME.equals(request.getResource().getName())) { + return AuthorizationResult.resourceNotFound(); + } + + // allow the token user + if (TOKEN_USER.equals(request.getIdentity())) { + return AuthorizationResult.approved(); + } + + // read access + if (READ_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity())) { + if (RequestAction.READ.equals(request.getAction())) { + return AuthorizationResult.approved(); + } + } + + // write access + if (WRITE_USER_DN.equals(request.getIdentity()) || READ_WRITE_USER_DN.equals(request.getIdentity())) { + if (RequestAction.WRITE.equals(request.getAction())) { + return AuthorizationResult.approved(); + } + } + + return AuthorizationResult.denied(); } @Override diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/SourceTestProcessor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/SourceTestProcessor.java index 0309ebb559..2fe6b46044 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/SourceTestProcessor.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/integration/util/SourceTestProcessor.java @@ -16,9 +16,8 @@ */ package org.apache.nifi.integration.util; -import java.util.HashSet; -import java.util.Set; - +import org.apache.nifi.annotation.behavior.Stateful; +import org.apache.nifi.components.state.Scope; import org.apache.nifi.processor.AbstractSessionFactoryProcessor; import org.apache.nifi.processor.ProcessContext; import org.apache.nifi.processor.ProcessSessionFactory; @@ -26,6 +25,10 @@ import org.apache.nifi.processor.ProcessorInitializationContext; import org.apache.nifi.processor.Relationship; import org.apache.nifi.processor.exception.ProcessException; +import java.util.HashSet; +import java.util.Set; + +@Stateful(scopes = Scope.LOCAL, description = "") public class SourceTestProcessor extends AbstractSessionFactoryProcessor { public SourceTestProcessor() { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authority-providers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authorizers.xml similarity index 93% rename from nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authority-providers.xml rename to nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authorizers.xml index a3fb0888fc..18161d6118 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authority-providers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/authorizers.xml @@ -16,9 +16,9 @@ - - + + test-provider org.apache.nifi.integration.util.NiFiTestAuthorizer - - \ No newline at end of file + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties index a41e3b1e62..4df1e3d9db 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/resources/access-control/nifi.properties @@ -21,7 +21,7 @@ nifi.flowcontroller.autoResumeState=true nifi.flowcontroller.graceful.shutdown.period=10 sec nifi.flowservice.writedelay.interval=2 sec -nifi.authority.provider.configuration.file=target/test-classes/access-control/authority-providers.xml +nifi.authorizer.configuration.file=target/test-classes/access-control/authorizers.xml nifi.login.identity.provider.configuration.file=target/test-classes/access-control/login-identity-providers.xml nifi.templates.directory=target/test-classes/access-control/templates nifi.ui.banner.text=TEST BANNER @@ -82,7 +82,7 @@ nifi.web.war.directory=target/test-classes/lib nifi.web.http.host= nifi.web.http.port= nifi.web.https.host= -nifi.web.https.port= +nifi.web.https.port=8443 nifi.web.jetty.working.directory=target/test-classes/access-control/jetty # security properties # @@ -99,7 +99,7 @@ nifi.security.truststoreType=JKS nifi.security.truststorePasswd=localtest nifi.security.needClientAuth=true nifi.security.user.login.identity.provider=test-provider -nifi.security.user.authorizer= +nifi.security.user.authorizer=test-provider # cluster common properties (cluster manager and nodes must have same values) # nifi.cluster.protocol.heartbeat.interval=5 sec diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java index 2ee187adf4..26fae82d39 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-security/src/main/java/org/apache/nifi/web/security/spring/LoginIdentityProviderFactoryBean.java @@ -119,7 +119,7 @@ public class LoginIdentityProviderFactoryBean implements FactoryBean, Disposable } private LoginIdentityProviders loadLoginIdentityProvidersConfiguration() throws Exception { - final File loginIdentityProvidersConfigurationFile = properties.getLoginIdentityProviderConfiguraitonFile(); + final File loginIdentityProvidersConfigurationFile = properties.getLoginIdentityProviderConfigurationFile(); // load the users from the specified file if (loginIdentityProvidersConfigurationFile.exists()) {