mirror of https://github.com/apache/nifi.git
NIFI-1901: - Building component based access control tests for Connections, Funnels, Labels, Input Ports, Output Ports, Processors, and Process Groups. - Restoring Access Token Endpoint tests.
This closes #518 Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
parent
c2b26eb311
commit
fbd299e885
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -210,6 +210,11 @@
|
|||
<artifactId>nifi-framework-authorization</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.nifi</groupId>
|
||||
<artifactId>nifi-authorizer</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
() -> {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,6 @@ import org.apache.nifi.web.util.WebUtils;
|
|||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
@ -48,7 +47,6 @@ import java.util.Map;
|
|||
/**
|
||||
* Access token endpoint test.
|
||||
*/
|
||||
@Ignore
|
||||
public class AccessTokenEndpointTest {
|
||||
|
||||
private static final String CLIENT_ID = "token-endpoint-id";
|
||||
|
@ -147,7 +145,7 @@ public class AccessTokenEndpointTest {
|
|||
}
|
||||
|
||||
private ProcessorDTO createProcessor(final String token) throws Exception {
|
||||
String url = BASE_URL + "/controller/process-groups/root/processors";
|
||||
String url = BASE_URL + "/process-groups/root/processors";
|
||||
|
||||
// authorization header
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
@ -224,7 +222,6 @@ public class AccessTokenEndpointTest {
|
|||
public void testRequestAccessUsingToken() throws Exception {
|
||||
String accessStatusUrl = BASE_URL + "/access";
|
||||
String accessTokenUrl = BASE_URL + "/access/token";
|
||||
String registrationUrl = BASE_URL + "/controller/users";
|
||||
|
||||
ClientResponse response = TOKEN_USER.testGet(accessStatusUrl);
|
||||
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.integration.accesscontrol;
|
||||
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import org.apache.nifi.connectable.ConnectableType;
|
||||
import org.apache.nifi.integration.util.NiFiTestAuthorizer;
|
||||
import org.apache.nifi.integration.util.NiFiTestUser;
|
||||
import org.apache.nifi.web.api.dto.ConnectableDTO;
|
||||
import org.apache.nifi.web.api.dto.ConnectionDTO;
|
||||
import org.apache.nifi.web.api.dto.RevisionDTO;
|
||||
import org.apache.nifi.web.api.dto.flow.FlowDTO;
|
||||
import org.apache.nifi.web.api.entity.ConnectionEntity;
|
||||
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
|
||||
import org.apache.nifi.web.api.entity.ProcessorEntity;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Access control test for connections.
|
||||
*/
|
||||
public class ConnectionAccessControlTest {
|
||||
|
||||
private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-connections.xml";
|
||||
|
||||
private static AccessControlHelper helper;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
helper = new AccessControlHelper(FLOW_XML_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user can get a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserGetConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can get a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserGetConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can get a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserGetConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can get a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserGetConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot put a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserPutConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
// attempt update the name
|
||||
entity.getRevision().setClientId(READ_CLIENT_ID);
|
||||
entity.getComponent().setName("Updated Name");
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateConnection(helper.getReadUser(), entity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ_WRITE user can put a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserPutConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name";
|
||||
|
||||
// attempt to update the name
|
||||
final long version = entity.getRevision().getVersion();
|
||||
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
|
||||
entity.getComponent().setName(updatedName);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateConnection(helper.getReadWriteUser(), entity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final ConnectionEntity responseEntity = response.getEntity(ConnectionEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
assertEquals(updatedName, responseEntity.getComponent().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ_WRITE user can put a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserPutConnectionThroughInheritedPolicy() throws Exception {
|
||||
final ConnectionEntity entity = createConnection(NiFiTestAuthorizer.NO_POLICY_COMPONENT_NAME);
|
||||
|
||||
final String updatedName = "Updated name";
|
||||
|
||||
// attempt to update the name
|
||||
final long version = entity.getRevision().getVersion();
|
||||
entity.getRevision().setClientId(READ_WRITE_CLIENT_ID);
|
||||
entity.getComponent().setName(updatedName);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateConnection(helper.getReadWriteUser(), entity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final ConnectionEntity responseEntity = response.getEntity(ConnectionEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(AccessControlHelper.READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
assertEquals(updatedName, responseEntity.getComponent().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can put a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserPutConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name";
|
||||
|
||||
// attempt to update the name
|
||||
final ConnectionDTO requestDto = new ConnectionDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setName(updatedName);
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
|
||||
|
||||
final ConnectionEntity requestEntity = new ConnectionEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateConnection(helper.getWriteUser(), requestEntity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final ConnectionEntity responseEntity = response.getEntity(ConnectionEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user cannot put a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserPutConnection() throws Exception {
|
||||
final ConnectionEntity entity = getRandomConnection(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name";
|
||||
|
||||
// attempt to update the name
|
||||
final ConnectionDTO requestDto = new ConnectionDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setName(updatedName);
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
|
||||
|
||||
final ConnectionEntity requestEntity = new ConnectionEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateConnection(helper.getNoneUser(), requestEntity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot delete a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserDeleteConnection() throws Exception {
|
||||
verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can delete a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserDeleteConnection() throws Exception {
|
||||
verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can delete a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserDeleteConnection() throws Exception {
|
||||
verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can delete a connection.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserDeleteConnection() throws Exception {
|
||||
verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
private ConnectionEntity getRandomConnection(final NiFiTestUser user) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/flow/process-groups/root";
|
||||
|
||||
// get the connections
|
||||
final ClientResponse response = user.testGet(url);
|
||||
|
||||
// ensure the response was successful
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// unmarshal
|
||||
final ProcessGroupFlowEntity flowEntity = response.getEntity(ProcessGroupFlowEntity.class);
|
||||
final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
|
||||
final Set<ConnectionEntity> connections = flowDto.getConnections();
|
||||
|
||||
// ensure the correct number of connection
|
||||
assertFalse(connections.isEmpty());
|
||||
|
||||
// use the first connection as the target
|
||||
Iterator<ConnectionEntity> connectionIter = connections.iterator();
|
||||
assertTrue(connectionIter.hasNext());
|
||||
return connectionIter.next();
|
||||
}
|
||||
|
||||
private ClientResponse updateConnection(final NiFiTestUser user, final ConnectionEntity entity) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/connections/" + entity.getId();
|
||||
|
||||
// perform the request
|
||||
return user.testPut(url, entity);
|
||||
}
|
||||
|
||||
private ConnectionEntity createConnection(final String name) throws Exception {
|
||||
String url = helper.getBaseUrl() + "/process-groups/root/connections";
|
||||
|
||||
// get two processors
|
||||
final ProcessorEntity one = ProcessorAccessControlTest.createProcessor(helper, "one");
|
||||
final ProcessorEntity two = ProcessorAccessControlTest.createProcessor(helper, "two");
|
||||
|
||||
// create the source connectable
|
||||
ConnectableDTO source = new ConnectableDTO();
|
||||
source.setId(one.getId());
|
||||
source.setType(ConnectableType.PROCESSOR.name());
|
||||
|
||||
// create the target connectable
|
||||
ConnectableDTO target = new ConnectableDTO();
|
||||
target.setId(two.getId());
|
||||
target.setType(ConnectableType.PROCESSOR.name());
|
||||
|
||||
// create the relationships
|
||||
Set<String> relationships = new HashSet<>();
|
||||
relationships.add("success");
|
||||
|
||||
// create the connection
|
||||
ConnectionDTO connection = new ConnectionDTO();
|
||||
connection.setName(name);
|
||||
connection.setSource(source);
|
||||
connection.setDestination(target);
|
||||
connection.setSelectedRelationships(relationships);
|
||||
|
||||
// create the revision
|
||||
final RevisionDTO revision = new RevisionDTO();
|
||||
revision.setClientId(READ_WRITE_CLIENT_ID);
|
||||
revision.setVersion(0L);
|
||||
|
||||
// create the entity body
|
||||
ConnectionEntity entity = new ConnectionEntity();
|
||||
entity.setRevision(revision);
|
||||
entity.setComponent(connection);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = helper.getReadWriteUser().testPost(url, entity);
|
||||
|
||||
// ensure the request is successful
|
||||
assertEquals(201, response.getStatus());
|
||||
|
||||
// get the entity body
|
||||
entity = response.getEntity(ConnectionEntity.class);
|
||||
|
||||
// verify creation
|
||||
connection = entity.getComponent();
|
||||
assertEquals(name, connection.getName());
|
||||
|
||||
// get the connection
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception {
|
||||
final ConnectionEntity entity = createConnection("Copy");
|
||||
|
||||
// create the entity body
|
||||
final Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.integration.accesscontrol;
|
||||
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import org.apache.nifi.integration.util.NiFiTestUser;
|
||||
import org.apache.nifi.web.api.dto.FunnelDTO;
|
||||
import org.apache.nifi.web.api.dto.PositionDTO;
|
||||
import org.apache.nifi.web.api.dto.RevisionDTO;
|
||||
import org.apache.nifi.web.api.dto.flow.FlowDTO;
|
||||
import org.apache.nifi.web.api.entity.FunnelEntity;
|
||||
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Access control test for funnels.
|
||||
*/
|
||||
public class FunnelAccessControlTest {
|
||||
|
||||
private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-funnels.xml";
|
||||
|
||||
private static AccessControlHelper helper;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
helper = new AccessControlHelper(FLOW_XML_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user can get a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserGetFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can get a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserGetFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can get a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserGetFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can get a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserGetFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot put a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserPutFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
// attempt update the position
|
||||
entity.getRevision().setClientId(READ_CLIENT_ID);
|
||||
entity.getComponent().setPosition(new PositionDTO(0.0, 10.0));
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateFunnel(helper.getReadUser(), entity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ_WRITE user can put a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserPutFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
final double y = 15.0;
|
||||
|
||||
// attempt to update the position
|
||||
final long version = entity.getRevision().getVersion();
|
||||
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
|
||||
entity.getComponent().setPosition(new PositionDTO(0.0, y));
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateFunnel(helper.getReadWriteUser(), entity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final FunnelEntity responseEntity = response.getEntity(FunnelEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
assertEquals(y, responseEntity.getComponent().getPosition().getY().doubleValue(), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can put a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserPutFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
final double y = 15.0;
|
||||
|
||||
// attempt to update the position
|
||||
final FunnelDTO requestDto = new FunnelDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setPosition(new PositionDTO(0.0, y));
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
|
||||
|
||||
final FunnelEntity requestEntity = new FunnelEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateFunnel(helper.getWriteUser(), requestEntity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final FunnelEntity responseEntity = response.getEntity(FunnelEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user cannot put a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserPutFunnel() throws Exception {
|
||||
final FunnelEntity entity = getRandomFunnel(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
// attempt to update the position
|
||||
final FunnelDTO requestDto = new FunnelDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setPosition(new PositionDTO(0.0, 15.0));
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
|
||||
|
||||
final FunnelEntity requestEntity = new FunnelEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateFunnel(helper.getNoneUser(), requestEntity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot delete a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserDeleteFunnel() throws Exception {
|
||||
verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can delete a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserDeleteFunnel() throws Exception {
|
||||
verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can delete a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserDeleteFunnel() throws Exception {
|
||||
verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can delete a funnel.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserDeleteFunnel() throws Exception {
|
||||
verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
private FunnelEntity getRandomFunnel(final NiFiTestUser user) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/flow/process-groups/root";
|
||||
|
||||
// get the flow
|
||||
final ClientResponse response = user.testGet(url);
|
||||
|
||||
// ensure the response was successful
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// unmarshal
|
||||
final ProcessGroupFlowEntity flowEntity = response.getEntity(ProcessGroupFlowEntity.class);
|
||||
final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
|
||||
final Set<FunnelEntity> funnels = flowDto.getFunnels();
|
||||
|
||||
// ensure the correct number of funnels
|
||||
assertFalse(funnels.isEmpty());
|
||||
|
||||
// use the first funnel as the target
|
||||
Iterator<FunnelEntity> funnelIter = funnels.iterator();
|
||||
assertTrue(funnelIter.hasNext());
|
||||
return funnelIter.next();
|
||||
}
|
||||
|
||||
private ClientResponse updateFunnel(final NiFiTestUser user, final FunnelEntity entity) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/funnels/" + entity.getId();
|
||||
|
||||
// perform the request
|
||||
return user.testPut(url, entity);
|
||||
}
|
||||
|
||||
private FunnelEntity createFunnel() throws Exception {
|
||||
String url = helper.getBaseUrl() + "/process-groups/root/funnels";
|
||||
|
||||
// create the funnel
|
||||
FunnelDTO funnel = new FunnelDTO();
|
||||
|
||||
// create the revision
|
||||
final RevisionDTO revision = new RevisionDTO();
|
||||
revision.setClientId(READ_WRITE_CLIENT_ID);
|
||||
revision.setVersion(0L);
|
||||
|
||||
// create the entity body
|
||||
FunnelEntity entity = new FunnelEntity();
|
||||
entity.setRevision(revision);
|
||||
entity.setComponent(funnel);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = helper.getReadWriteUser().testPost(url, entity);
|
||||
|
||||
// ensure the request is successful
|
||||
assertEquals(201, response.getStatus());
|
||||
|
||||
// get the entity body
|
||||
return response.getEntity(FunnelEntity.class);
|
||||
}
|
||||
|
||||
private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception {
|
||||
final FunnelEntity entity = createFunnel();
|
||||
|
||||
// create the entity body
|
||||
final Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,405 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.nifi.integration.accesscontrol;
|
||||
|
||||
import com.sun.jersey.api.client.ClientResponse;
|
||||
import org.apache.nifi.integration.util.NiFiTestAuthorizer;
|
||||
import org.apache.nifi.integration.util.NiFiTestUser;
|
||||
import org.apache.nifi.web.api.dto.PortDTO;
|
||||
import org.apache.nifi.web.api.dto.RevisionDTO;
|
||||
import org.apache.nifi.web.api.dto.flow.FlowDTO;
|
||||
import org.apache.nifi.web.api.entity.PortEntity;
|
||||
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.NONE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.READ_WRITE_CLIENT_ID;
|
||||
import static org.apache.nifi.integration.accesscontrol.AccessControlHelper.WRITE_CLIENT_ID;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Access control test for input ports.
|
||||
*/
|
||||
public class InputPortAccessControlTest {
|
||||
|
||||
private static final String FLOW_XML_PATH = "target/test-classes/access-control/flow-input-ports.xml";
|
||||
|
||||
private static AccessControlHelper helper;
|
||||
private static int count = 0;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() throws Exception {
|
||||
helper = new AccessControlHelper(FLOW_XML_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user can get an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserGetInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can get an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserGetInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can get an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserGetInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can get an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserGetInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot put an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserPutInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getReadUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
// attempt update the name
|
||||
entity.getRevision().setClientId(READ_CLIENT_ID);
|
||||
entity.getComponent().setName("Updated Name" + count++);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateInputPort(helper.getReadUser(), entity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ_WRITE user can put an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserPutInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getReadWriteUser());
|
||||
assertTrue(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNotNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name" + count++;
|
||||
|
||||
// attempt to update the name
|
||||
final long version = entity.getRevision().getVersion();
|
||||
entity.getRevision().setClientId(AccessControlHelper.READ_WRITE_CLIENT_ID);
|
||||
entity.getComponent().setName(updatedName);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateInputPort(helper.getReadWriteUser(), entity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final PortEntity responseEntity = response.getEntity(PortEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
assertEquals(updatedName, responseEntity.getComponent().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ_WRITE user can put an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserPutInputPortThroughInheritedPolicy() throws Exception {
|
||||
final PortEntity entity = createInputPort(NiFiTestAuthorizer.NO_POLICY_COMPONENT_NAME);
|
||||
|
||||
final String updatedName = "Updated name" + count++;
|
||||
|
||||
// attempt to update the name
|
||||
final long version = entity.getRevision().getVersion();
|
||||
entity.getRevision().setClientId(READ_WRITE_CLIENT_ID);
|
||||
entity.getComponent().setName(updatedName);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateInputPort(helper.getReadWriteUser(), entity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final PortEntity responseEntity = response.getEntity(PortEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(AccessControlHelper.READ_WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
assertEquals(updatedName, responseEntity.getComponent().getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can put an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserPutInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getWriteUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertTrue(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name" + count++;
|
||||
|
||||
// attempt to update the name
|
||||
final PortDTO requestDto = new PortDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setName(updatedName);
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.WRITE_CLIENT_ID);
|
||||
|
||||
final PortEntity requestEntity = new PortEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateInputPort(helper.getWriteUser(), requestEntity);
|
||||
|
||||
// ensure successful response
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// get the response
|
||||
final PortEntity responseEntity = response.getEntity(PortEntity.class);
|
||||
|
||||
// verify
|
||||
assertEquals(WRITE_CLIENT_ID, responseEntity.getRevision().getClientId());
|
||||
assertEquals(version + 1, responseEntity.getRevision().getVersion().longValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user cannot put an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserPutInputPort() throws Exception {
|
||||
final PortEntity entity = getRandomInputPort(helper.getNoneUser());
|
||||
assertFalse(entity.getAccessPolicy().getCanRead());
|
||||
assertFalse(entity.getAccessPolicy().getCanWrite());
|
||||
assertNull(entity.getComponent());
|
||||
|
||||
final String updatedName = "Updated Name" + count++;
|
||||
|
||||
// attempt to update the name
|
||||
final PortDTO requestDto = new PortDTO();
|
||||
requestDto.setId(entity.getId());
|
||||
requestDto.setName(updatedName);
|
||||
|
||||
final long version = entity.getRevision().getVersion();
|
||||
final RevisionDTO requestRevision = new RevisionDTO();
|
||||
requestRevision.setVersion(version);
|
||||
requestRevision.setClientId(AccessControlHelper.NONE_CLIENT_ID);
|
||||
|
||||
final PortEntity requestEntity = new PortEntity();
|
||||
requestEntity.setId(entity.getId());
|
||||
requestEntity.setRevision(requestRevision);
|
||||
requestEntity.setComponent(requestDto);
|
||||
|
||||
// perform the request
|
||||
final ClientResponse response = updateInputPort(helper.getNoneUser(), requestEntity);
|
||||
|
||||
// ensure forbidden response
|
||||
assertEquals(403, response.getStatus());
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ user cannot delete an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadUserDeleteInputPort() throws Exception {
|
||||
verifyDelete(helper.getReadUser(), AccessControlHelper.READ_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the READ WRITE user can delete an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testReadWriteUserDeleteInputPort() throws Exception {
|
||||
verifyDelete(helper.getReadWriteUser(), AccessControlHelper.READ_WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the WRITE user can delete an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testWriteUserDeleteInputPort() throws Exception {
|
||||
verifyDelete(helper.getWriteUser(), AccessControlHelper.WRITE_CLIENT_ID, 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the NONE user can delete an input port.
|
||||
*
|
||||
* @throws Exception ex
|
||||
*/
|
||||
@Test
|
||||
public void testNoneUserDeleteInputPort() throws Exception {
|
||||
verifyDelete(helper.getNoneUser(), NONE_CLIENT_ID, 403);
|
||||
}
|
||||
|
||||
private PortEntity getRandomInputPort(final NiFiTestUser user) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/flow/process-groups/root";
|
||||
|
||||
// get the input ports
|
||||
final ClientResponse response = user.testGet(url);
|
||||
|
||||
// ensure the response was successful
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
// unmarshal
|
||||
final ProcessGroupFlowEntity flowEntity = response.getEntity(ProcessGroupFlowEntity.class);
|
||||
final FlowDTO flowDto = flowEntity.getProcessGroupFlow().getFlow();
|
||||
final Set<PortEntity> inputPorts = flowDto.getInputPorts();
|
||||
|
||||
// ensure the correct number of input ports
|
||||
assertFalse(inputPorts.isEmpty());
|
||||
|
||||
// use the first input port as the target
|
||||
Iterator<PortEntity> inputPortIter = inputPorts.iterator();
|
||||
assertTrue(inputPortIter.hasNext());
|
||||
return inputPortIter.next();
|
||||
}
|
||||
|
||||
private ClientResponse updateInputPort(final NiFiTestUser user, final PortEntity entity) throws Exception {
|
||||
final String url = helper.getBaseUrl() + "/input-ports/" + entity.getId();
|
||||
|
||||
// perform the request
|
||||
return user.testPut(url, entity);
|
||||
}
|
||||
|
||||
private PortEntity createInputPort(final String name) throws Exception {
|
||||
String url = helper.getBaseUrl() + "/process-groups/root/input-ports";
|
||||
|
||||
final String updatedName = name + count++;
|
||||
|
||||
// create the input port
|
||||
PortDTO inputPort = new PortDTO();
|
||||
inputPort.setName(updatedName);
|
||||
|
||||
// create the revision
|
||||
final RevisionDTO revision = new RevisionDTO();
|
||||
revision.setClientId(READ_WRITE_CLIENT_ID);
|
||||
revision.setVersion(0L);
|
||||
|
||||
// create the entity body
|
||||
PortEntity entity = new PortEntity();
|
||||
entity.setRevision(revision);
|
||||
entity.setComponent(inputPort);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = helper.getReadWriteUser().testPost(url, entity);
|
||||
|
||||
// ensure the request is successful
|
||||
assertEquals(201, response.getStatus());
|
||||
|
||||
// get the entity body
|
||||
entity = response.getEntity(PortEntity.class);
|
||||
|
||||
// verify creation
|
||||
inputPort = entity.getComponent();
|
||||
assertEquals(updatedName, inputPort.getName());
|
||||
|
||||
// get the input port
|
||||
return entity;
|
||||
}
|
||||
|
||||
private void verifyDelete(final NiFiTestUser user, final String clientId, final int responseCode) throws Exception {
|
||||
final PortEntity entity = createInputPort("Copy");
|
||||
|
||||
// create the entity body
|
||||
final Map<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -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<LabelEntity> labels = flowDto.getLabels();
|
||||
|
||||
// ensure the correct number of labels
|
||||
assertFalse(labels.isEmpty());
|
||||
|
||||
// use the first label as the target
|
||||
Iterator<LabelEntity> 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<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -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<PortEntity> outputPorts = flowDto.getOutputPorts();
|
||||
|
||||
// ensure the correct number of output ports
|
||||
assertFalse(outputPorts.isEmpty());
|
||||
|
||||
// use the first output port as the target
|
||||
Iterator<PortEntity> 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<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -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<ProcessGroupEntity> processGroups = flowDto.getProcessGroups();
|
||||
|
||||
// ensure the correct number of process groups
|
||||
assertFalse(processGroups.isEmpty());
|
||||
|
||||
// use the first process group as the target
|
||||
Iterator<ProcessGroupEntity> 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<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -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<ProcessorEntity> processors = flowDto.getProcessors();
|
||||
|
||||
// ensure the correct number of processors
|
||||
assertFalse(processors.isEmpty());
|
||||
|
||||
// use the first processor as the target
|
||||
Iterator<ProcessorEntity> 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<String, String> queryParams = new HashMap<>();
|
||||
queryParams.put("revision", String.valueOf(entity.getRevision().getVersion()));
|
||||
queryParams.put("clientId", clientId);
|
||||
|
||||
// perform the request
|
||||
ClientResponse response = user.testDelete(entity.getComponent().getUri(), queryParams);
|
||||
|
||||
// ensure the request is failed with a forbidden status code
|
||||
assertEquals(responseCode, response.getStatus());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() throws Exception {
|
||||
helper.cleanup();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
<!--
|
||||
This file lists all authority providers to use when running securely.
|
||||
-->
|
||||
<authorityProviders>
|
||||
<provider>
|
||||
<authorizers>
|
||||
<authorizer>
|
||||
<identifier>test-provider</identifier>
|
||||
<class>org.apache.nifi.integration.util.NiFiTestAuthorizer</class>
|
||||
</provider>
|
||||
</authorityProviders>
|
||||
</authorizer>
|
||||
</authorizers>
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
Loading…
Reference in New Issue