NIFI-4907: add 'view provenance' component policy

whitespace removed for checkstyle
This commit is contained in:
Mark Bean 2018-05-12 16:32:29 -04:00 committed by Matt Gilman
parent d98d335497
commit e27798797a
No known key found for this signature in database
GPG Key ID: DF61EC19432AEE37
21 changed files with 475 additions and 113 deletions

View File

@ -925,6 +925,7 @@ The following tables summarize the global and component policies assigned to eac
|modify the component | |* | | | |
|view the data | |* | |* | |*
|modify the data | |* | | | |*
|view provenance | | | |* | |
|==========================
@ -1102,13 +1103,17 @@ Component level access policies govern the following component level authorizati
|`resource="/<component-type>/<component-UUID>" action="W"`
|view the data
|Allows user to view metadata and content for this component through provenance data and flowfile queues in outbound connections
|Allows users to view metadata and content for this component through provenance data and flowfile queues in outbound connections
|`resource="/data/<component-type>/<component-UUID>" action="R"`
|modify the data
|Allows user to empty flowfile queues in outbound connections and submit replays
|Allows users to empty flowfile queues in outbound connections and submit replays
|`resource="/data/<component-type>/<component-UUID>" action="W"`
|view provenance
|Allows users to view provenance data generated by this component
|`resource="/provenance-data/<component-type>/<component-UUID>" action="R"`
|view the policies
|Allows users to view the list of users who can view/modify a component
|`resource="/policies/<component-type>/<component-UUID>" action="R"`

View File

@ -211,6 +211,7 @@ The available component-level access policies are:
|modify the component |Allows users to modify component configuration details
|view the data |Allows users to view metadata and content for this component through provenance data and flowfile queues in outbound connection
|modify the data |Allows users to empty flowfile queues in outbound connections and to submit replays
|view provenance |Allows users to view provenance data generated by this component
|view the policies |Allows users to view the list of users who can view and modify a component
|modify the policies |Allows users to modify the list of users who can view and modify a component
|retrieve data via site-to-site |Allows a port to receive data from NiFi instances
@ -2184,6 +2185,10 @@ search the information for specific items, and filter the search results. It is
replay data at any point within the dataflow, and see a graphical representation of the data's lineage, or path through the flow.
(These features are described in depth below.)
When authorization is enabled, accessessing Data Provenance information requires the 'query provenance' Global Policy as well as the 'view provenance'
Component Policy for the component which generated the event. In addition, access to event details which include FlowFile attributes and content require
the 'view the data' Component Policy for the component which generated the event.
image:provenance-annotated.png["Provenance Table"]
[[provenance_events]]

View File

@ -42,4 +42,14 @@ public interface ProvenanceAuthorizableFactory {
*/
Authorizable createRemoteDataAuthorizable(String remoteGroupPortId);
/**
* Generates an Authorizable object for the Provenance Data of the component with the given ID.
*
* @param componentId the ID of the component to which the Data belongs
*
* @return the Authorizable that can be use to authorize access to provenance events
* @throws ResourceNotFoundException if no component can be found with the given ID
*/
Authorizable createProvenanceDataAuthorizable(String componentId);
}

View File

@ -64,6 +64,7 @@ public final class RoleAccessPolicy {
provenancePolicies.add(new RoleAccessPolicy(ResourceType.Provenance.getValue(), READ_ACTION));
if (rootGroupId != null) {
provenancePolicies.add(new RoleAccessPolicy(ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION));
provenancePolicies.add(new RoleAccessPolicy(ResourceType.ProvenanceData.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION));
}
roleAccessPolicies.put(Role.ROLE_PROVENANCE, Collections.unmodifiableSet(provenancePolicies));

View File

@ -341,12 +341,20 @@ public class FileAccessPolicyProviderTest {
// verify user2's policies
final Map<String,Set<RequestAction>> user2Policies = getResourceActions(policies, user2);
assertEquals(2, user2Policies.size());
assertEquals(3, user2Policies.size());
assertTrue(user2Policies.containsKey(ResourceType.Provenance.getValue()));
assertEquals(1, user2Policies.get(ResourceType.Provenance.getValue()).size());
assertTrue(user2Policies.get(ResourceType.Provenance.getValue()).contains(RequestAction.READ));
assertTrue(user2Policies.containsKey(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID));
assertEquals(1, user2Policies.get(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID).size());
assertTrue(user2Policies.get(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID).contains(RequestAction.READ));
assertTrue(user2Policies.containsKey(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID));
assertEquals(1, user2Policies.get(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID).size());
assertTrue(user2Policies.get(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID).contains(RequestAction.READ));
// verify user3's policies
final Map<String,Set<RequestAction>> user3Policies = getResourceActions(policies, user3);
assertEquals(6, user3Policies.size());

View File

@ -341,12 +341,20 @@ public class FileAuthorizerTest {
// verify user2's policies
final Map<String,Set<RequestAction>> user2Policies = getResourceActions(policies, user2);
assertEquals(2, user2Policies.size());
assertEquals(3, user2Policies.size());
assertTrue(user2Policies.containsKey(ResourceType.Provenance.getValue()));
assertEquals(1, user2Policies.get(ResourceType.Provenance.getValue()).size());
assertTrue(user2Policies.get(ResourceType.Provenance.getValue()).contains(RequestAction.READ));
assertTrue(user2Policies.containsKey(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID));
assertEquals(1, user2Policies.get(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID).size());
assertTrue(user2Policies.get(ResourceType.ProvenanceData.getValue() + "/process-groups/" + ROOT_GROUP_ID).contains(RequestAction.READ));
assertTrue(user2Policies.containsKey(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID));
assertEquals(1, user2Policies.get(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID).size());
assertTrue(user2Policies.get(ResourceType.Data.getValue() + "/process-groups/" + ROOT_GROUP_ID).contains(RequestAction.READ));
// verify user3's policies
final Map<String,Set<RequestAction>> user3Policies = getResourceActions(policies, user3);
assertEquals(6, user3Policies.size());

View File

@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.authorization.resource;
import org.apache.nifi.authorization.Resource;
public class ProvenanceDataAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource {
private final Authorizable authorizable;
public ProvenanceDataAuthorizable(final Authorizable authorizable) {
this.authorizable = authorizable;
}
@Override
public Authorizable getBaseAuthorizable() {
return authorizable;
}
@Override
public Authorizable getParentAuthorizable() {
if (authorizable.getParentAuthorizable() == null) {
return null;
} else {
return new ProvenanceDataAuthorizable(authorizable.getParentAuthorizable());
}
}
@Override
public Resource getResource() {
return ResourceFactory.getProvenanceDataResource(authorizable.getResource());
}
}

View File

@ -108,6 +108,23 @@ public final class ResourceFactory {
}
};
private final static Resource PROVENANCE_DATA_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return ResourceType.ProvenanceData.getValue();
}
@Override
public String getName() {
return "Provenance data for ";
}
@Override
public String getSafeDescription() {
return "the provenance data for ";
}
};
private final static Resource DATA_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
@ -491,10 +508,10 @@ public final class ResourceFactory {
}
/**
* Gets a Resource for accessing a component's provenance events.
* Gets a Resource for accessing flowfile information
*
* @param resource The resource for the component being accessed
* @return The resource for the provenance of the component being accessed
* @return The resource for the data of the component being accessed
*/
public static Resource getDataResource(final Resource resource) {
return new Resource() {
@ -515,6 +532,33 @@ public final class ResourceFactory {
};
}
/**
* Gets a Resource for accessing provenance data.
*
* @param resource The resource for the component being accessed
* @return The resource for the provenance data being accessed
*/
public static Resource getProvenanceDataResource(final Resource resource) {
Objects.requireNonNull(resource, "The resource must be specified.");
return new Resource() {
@Override
public String getIdentifier() {
return String.format("%s%s", PROVENANCE_DATA_RESOURCE.getIdentifier(), resource.getIdentifier());
}
@Override
public String getName() {
return PROVENANCE_DATA_RESOURCE.getName() + resource.getName();
}
@Override
public String getSafeDescription() {
return PROVENANCE_DATA_RESOURCE.getSafeDescription() + resource.getSafeDescription();
}
};
}
/**
* Prevent outside instantiation.
*/

View File

@ -29,6 +29,7 @@ public enum ResourceType {
Processor("/processors"),
ProcessGroup("/process-groups"),
Provenance("/provenance"),
ProvenanceData("/provenance-data"),
Data("/data"),
Proxy("/proxy"),
RemoteProcessGroup("/remote-process-groups"),

View File

@ -0,0 +1,117 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nifi.authorization.resource;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ProvenanceDataAuthorizableTest {
private static final String IDENTITY_1 = "identity-1";
private Authorizer testAuthorizer;
private ProvenanceDataAuthorizable testProvenanceDataAuthorizable;
@Before
public void setup() {
Authorizable testProcessorAuthorizable;
testProcessorAuthorizable = mock(Authorizable.class);
when(testProcessorAuthorizable.getParentAuthorizable()).thenReturn(null);
when(testProcessorAuthorizable.getResource()).thenReturn(ResourceFactory.getComponentResource(ResourceType.Processor, "id", "name"));
testAuthorizer = mock(Authorizer.class);
when(testAuthorizer.authorize(any(AuthorizationRequest.class))).then(invocation -> {
final AuthorizationRequest request = invocation.getArgumentAt(0, AuthorizationRequest.class);
if (IDENTITY_1.equals(request.getIdentity())) {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied();
});
testProvenanceDataAuthorizable = new ProvenanceDataAuthorizable(testProcessorAuthorizable);
}
@Test(expected = AccessDeniedException.class)
public void testAuthorizeNullUser() {
testProvenanceDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, null, null);
}
@Test
public void testCheckAuthorizationNullUser() {
final AuthorizationResult result = testProvenanceDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, null, null);
assertEquals(Result.Denied, result.getResult());
}
@Test(expected = AccessDeniedException.class)
public void testAuthorizeUnauthorizedUser() {
final NiFiUser user = new Builder().identity("unknown").build();
testProvenanceDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
}
@Test
public void testCheckAuthorizationUnauthorizedUser() {
final NiFiUser user = new Builder().identity("unknown").build();
final AuthorizationResult result = testProvenanceDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
assertEquals(Result.Denied, result.getResult());
}
@Test
public void testAuthorizedUser() {
final NiFiUser user = new Builder().identity(IDENTITY_1).build();
testProvenanceDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
@Override
public boolean matches(Object o) {
return IDENTITY_1.equals(((AuthorizationRequest) o).getIdentity());
}
}));
}
@Test
public void testCheckAuthorizationUser() {
final NiFiUser user = new Builder().identity(IDENTITY_1).build();
final AuthorizationResult result = testProvenanceDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
assertEquals(Result.Approved, result.getResult());
verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
@Override
public boolean matches(Object o) {
return IDENTITY_1.equals(((AuthorizationRequest) o).getIdentity());
}
}));
}
}

View File

@ -32,6 +32,7 @@ import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.DataAuthorizable;
import org.apache.nifi.authorization.resource.ProvenanceDataAuthorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.bundle.Bundle;
@ -4966,6 +4967,38 @@ public class FlowController implements EventAccess, ControllerServiceProvider, R
return authorizable;
}
@Override
public Authorizable createProvenanceDataAuthorizable(String componentId) {
final String rootGroupId = getRootGroupId();
// Provenance Events are generated only by connectable components, with the exception of DOWNLOAD events,
// which have the root process group's identifier assigned as the component ID, and DROP events, which
// could have the connection identifier assigned as the component ID. So, we check if the component ID
// is set to the root group and otherwise assume that the ID is that of a connectable or connection.
final ProvenanceDataAuthorizable authorizable;
if (rootGroupId.equals(componentId)) {
authorizable = new ProvenanceDataAuthorizable(getRootGroup());
} else {
// check if the component is a connectable, this should be the case most often
final Connectable connectable = getRootGroup().findLocalConnectable(componentId);
if (connectable == null) {
// if the component id is not a connectable then consider a connection
final Connection connection = getRootGroup().findConnection(componentId);
if (connection == null) {
throw new ResourceNotFoundException("The component that generated this event is no longer part of the data flow.");
} else {
// authorizable for connection data is associated with the source connectable
authorizable = new ProvenanceDataAuthorizable(connection.getSource());
}
} else {
authorizable = new ProvenanceDataAuthorizable(connectable);
}
}
return authorizable;
}
@Override
public List<Action> getFlowChanges(final int firstActionId, final int maxActions) {
final History history = auditService.getActions(firstActionId, maxActions);

View File

@ -22,6 +22,7 @@ import org.apache.nifi.authorization.resource.AccessPolicyAuthorizable;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.DataAuthorizable;
import org.apache.nifi.authorization.resource.DataTransferAuthorizable;
import org.apache.nifi.authorization.resource.ProvenanceDataAuthorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizableFactory;
@ -480,8 +481,8 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
throw new ResourceNotFoundException("Unrecognized resource: " + resource);
}
// if this is a policy or a provenance event resource, there should be another resource type
if (ResourceType.Policy.equals(resourceType) || ResourceType.Data.equals(resourceType) || ResourceType.DataTransfer.equals(resourceType)) {
// if this is a policy, data or a provenance event resource, there should be another resource type
if (ResourceType.Policy.equals(resourceType) || ResourceType.Data.equals(resourceType) || ResourceType.DataTransfer.equals(resourceType) || ResourceType.ProvenanceData.equals(resourceType)) {
final ResourceType primaryResourceType = resourceType;
resourceType = null;
@ -503,6 +504,8 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
return new AccessPolicyAuthorizable(getAccessPolicy(resourceType, resource));
} else if (ResourceType.Data.equals(primaryResourceType)) {
return new DataAuthorizable(getAccessPolicy(resourceType, resource));
} else if (ResourceType.ProvenanceData.equals(primaryResourceType)) {
return new ProvenanceDataAuthorizable(getAccessPolicy(resourceType, resource));
} else {
return new DataTransferAuthorizable(getAccessPolicy(resourceType, resource));
}

View File

@ -1395,6 +1395,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final List<Resource> resources = new ArrayList<>();
resources.add(componentResource);
resources.add(ResourceFactory.getDataResource(componentResource));
resources.add(ResourceFactory.getProvenanceDataResource(componentResource));
resources.add(ResourceFactory.getDataTransferResource(componentResource));
resources.add(ResourceFactory.getPolicyResource(componentResource));

View File

@ -845,6 +845,7 @@ public class ControllerFacade implements Authorizable {
final Resource rootResource = root.getResource();
resources.add(rootResource);
resources.add(ResourceFactory.getDataResource(rootResource));
resources.add(ResourceFactory.getProvenanceDataResource(rootResource));
resources.add(ResourceFactory.getPolicyResource(rootResource));
// add each processor
@ -852,6 +853,7 @@ public class ControllerFacade implements Authorizable {
final Resource processorResource = processor.getResource();
resources.add(processorResource);
resources.add(ResourceFactory.getDataResource(processorResource));
resources.add(ResourceFactory.getProvenanceDataResource(processorResource));
resources.add(ResourceFactory.getPolicyResource(processorResource));
}
@ -867,6 +869,7 @@ public class ControllerFacade implements Authorizable {
final Resource processGroupResource = processGroup.getResource();
resources.add(processGroupResource);
resources.add(ResourceFactory.getDataResource(processGroupResource));
resources.add(ResourceFactory.getProvenanceDataResource(processGroupResource));
resources.add(ResourceFactory.getPolicyResource(processGroupResource));
}
@ -875,6 +878,7 @@ public class ControllerFacade implements Authorizable {
final Resource remoteProcessGroupResource = remoteProcessGroup.getResource();
resources.add(remoteProcessGroupResource);
resources.add(ResourceFactory.getDataResource(remoteProcessGroupResource));
resources.add(ResourceFactory.getProvenanceDataResource(remoteProcessGroupResource));
resources.add(ResourceFactory.getPolicyResource(remoteProcessGroupResource));
}
@ -1077,7 +1081,10 @@ public class ControllerFacade implements Authorizable {
if (includeResults || queryResult.isFinished()) {
final List<ProvenanceEventDTO> events = new ArrayList<>();
for (final ProvenanceEventRecord record : queryResult.getMatchingEvents()) {
events.add(createProvenanceEventDto(record, Boolean.TRUE.equals(summarize)));
final ProvenanceEventDTO dto = createProvenanceEventDto(record, Boolean.TRUE.equals(summarize));
if (dto != null) {
events.add(dto);
}
}
resultsDto.setProvenanceEvents(events);
}
@ -1270,7 +1277,7 @@ public class ControllerFacade implements Authorizable {
}
// authorize the replay
authorizeReplay(originalEvent);
authorizeData(originalEvent);
// replay the flow file
final ProvenanceEventRecord event = flowController.replayFlowFile(originalEvent, user);
@ -1303,13 +1310,7 @@ public class ControllerFacade implements Authorizable {
final Map<String, String> eventAttributes = event.getAttributes();
// ensure we can read the data
final AuthorizationResult result = dataAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user, eventAttributes);
if (!Result.Approved.equals(result.getResult())) {
return result;
}
// ensure we can write the data
// ensure we can write the data; read the data should have been checked already
return dataAuthorizable.checkAuthorization(authorizer, RequestAction.WRITE, user, eventAttributes);
}
@ -1318,7 +1319,7 @@ public class ControllerFacade implements Authorizable {
*
* @param event event
*/
private void authorizeReplay(final ProvenanceEventRecord event) {
private void authorizeData(final ProvenanceEventRecord event) {
// if the connection id isn't specified, then the replay wouldn't be available anyways and we have nothing to authorize against so deny it`
if (event.getSourceQueueIdentifier() == null) {
throw new AccessDeniedException("The connection id in the provenance event is unknown.");
@ -1338,6 +1339,67 @@ public class ControllerFacade implements Authorizable {
dataAuthorizable.authorize(authorizer, RequestAction.WRITE, user, eventAttributes);
}
private AuthorizationResult checkAuthorizationForData(ProvenanceEventRecord event) {
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final Authorizable dataAuthorizable;
if (event.isRemotePortType()) {
dataAuthorizable = flowController.createRemoteDataAuthorizable(event.getComponentId());
} else {
dataAuthorizable = flowController.createLocalDataAuthorizable(event.getComponentId());
}
final Map<String, String> eventAttributes = event.getAttributes();
// ensure we can read the data
return dataAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user, eventAttributes);
}
private AuthorizationResult checkAuthorizationForProvenanceData(final ProvenanceEventRecord event) {
final ProcessGroup rootGroup = flowController.getGroup(getRootGroupId());
final NiFiUser user = NiFiUserUtils.getNiFiUser();
final String componentId = event.getComponentId();
Connectable connectable;
String targetId = null;
// check if the component is the rootGroup
if (getRootGroupId().equals(componentId)) {
targetId = componentId;
}
if (targetId == null) {
// check if the component is a processor
connectable = rootGroup.findProcessor(componentId);
if (connectable == null) {
// if the component id is not a processor then consider a connection
connectable = rootGroup.findConnection(componentId).getSource();
if (connectable == null) {
throw new ResourceNotFoundException("The component that generated this event is no longer part of the data flow");
}
}
targetId = connectable.getIdentifier();
}
final Authorizable provenanceDataAuthorizable = flowController.createProvenanceDataAuthorizable(targetId);
return provenanceDataAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user);
}
private AuthorizationResult checkConnectableAuthorization(final String componentId) {
final ProcessGroup rootGroup = flowController.getGroup(getRootGroupId());
final NiFiUser user = NiFiUserUtils.getNiFiUser();
if (rootGroup.getIdentifier().equals(componentId)) {
return rootGroup.checkAuthorization(authorizer, RequestAction.READ, user);
}
Connectable connectable = rootGroup.findProcessor(componentId);
if (connectable == null) {
// if the component id is not a processor then consider a connection
connectable = rootGroup.findConnection(componentId).getSource();
if (connectable == null) {
throw new ResourceNotFoundException("The component that generated this event is no longer part of the data flow");
}
}
return connectable.checkAuthorization(authorizer, RequestAction.READ, user);
}
/**
* Get the provenance event with the specified event id.
*
@ -1346,21 +1408,11 @@ public class ControllerFacade implements Authorizable {
*/
public ProvenanceEventDTO getProvenanceEvent(final Long eventId) {
try {
final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId);
final ProvenanceEventRecord event = flowController.getProvenanceRepository().getEvent(eventId, NiFiUserUtils.getNiFiUser());
if (event == null) {
throw new ResourceNotFoundException("Unable to find the specified event.");
}
// get the flowfile attributes and authorize the event
final Map<String, String> attributes = event.getAttributes();
final Authorizable dataAuthorizable;
if (event.isRemotePortType()) {
dataAuthorizable = flowController.createRemoteDataAuthorizable(event.getComponentId());
} else {
dataAuthorizable = flowController.createLocalDataAuthorizable(event.getComponentId());
}
dataAuthorizable.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser(), attributes);
// convert the event
return createProvenanceEventDto(event, false);
} catch (final IOException ioe) {
@ -1375,6 +1427,11 @@ public class ControllerFacade implements Authorizable {
* @return event
*/
private ProvenanceEventDTO createProvenanceEventDto(final ProvenanceEventRecord event, final boolean summarize) {
// do not generate DTO if not authorized
final AuthorizationResult provenanceResult = checkAuthorizationForProvenanceData(event);
if (Result.Denied.equals(provenanceResult.getResult())) {
return null;
}
final ProvenanceEventDTO dto = new ProvenanceEventDTO();
dto.setId(String.valueOf(event.getEventId()));
dto.setEventId(event.getEventId());
@ -1429,7 +1486,6 @@ public class ControllerFacade implements Authorizable {
// additional event details
dto.setAlternateIdentifierUri(event.getAlternateIdentifierUri());
dto.setAttributes(attributes);
dto.setTransitUri(event.getTransitUri());
dto.setSourceSystemFlowFileId(event.getSourceSystemFlowFileIdentifier());
dto.setRelationship(event.getRelationship());
@ -1437,36 +1493,44 @@ public class ControllerFacade implements Authorizable {
final ContentAvailability contentAvailability = flowController.getContentAvailability(event);
// content
dto.setContentEqual(contentAvailability.isContentSame());
dto.setInputContentAvailable(contentAvailability.isInputAvailable());
dto.setInputContentClaimSection(event.getPreviousContentClaimSection());
dto.setInputContentClaimContainer(event.getPreviousContentClaimContainer());
dto.setInputContentClaimIdentifier(event.getPreviousContentClaimIdentifier());
dto.setInputContentClaimOffset(event.getPreviousContentClaimOffset());
dto.setInputContentClaimFileSizeBytes(event.getPreviousFileSize());
dto.setOutputContentAvailable(contentAvailability.isOutputAvailable());
dto.setOutputContentClaimSection(event.getContentClaimSection());
dto.setOutputContentClaimContainer(event.getContentClaimContainer());
dto.setOutputContentClaimIdentifier(event.getContentClaimIdentifier());
dto.setOutputContentClaimOffset(event.getContentClaimOffset());
dto.setOutputContentClaimFileSize(FormatUtils.formatDataSize(event.getFileSize()));
dto.setOutputContentClaimFileSizeBytes(event.getFileSize());
// set flowfile attributes and content only if approved for view the data
final AuthorizationResult dataResult = checkAuthorizationForData(event);
if (Result.Approved.equals(dataResult.getResult())) {
// attributes
dto.setAttributes(attributes);
// format the previous file sizes if possible
if (event.getPreviousFileSize() != null) {
dto.setInputContentClaimFileSize(FormatUtils.formatDataSize(event.getPreviousFileSize()));
// content
dto.setContentEqual(contentAvailability.isContentSame());
dto.setInputContentAvailable(contentAvailability.isInputAvailable());
dto.setInputContentClaimSection(event.getPreviousContentClaimSection());
dto.setInputContentClaimContainer(event.getPreviousContentClaimContainer());
dto.setInputContentClaimIdentifier(event.getPreviousContentClaimIdentifier());
dto.setInputContentClaimOffset(event.getPreviousContentClaimOffset());
dto.setInputContentClaimFileSizeBytes(event.getPreviousFileSize());
dto.setOutputContentAvailable(contentAvailability.isOutputAvailable());
dto.setOutputContentClaimSection(event.getContentClaimSection());
dto.setOutputContentClaimContainer(event.getContentClaimContainer());
dto.setOutputContentClaimIdentifier(event.getContentClaimIdentifier());
dto.setOutputContentClaimOffset(event.getContentClaimOffset());
dto.setOutputContentClaimFileSize(FormatUtils.formatDataSize(event.getFileSize()));
dto.setOutputContentClaimFileSizeBytes(event.getFileSize());
// format the previous file sizes if possible
if (event.getPreviousFileSize() != null) {
dto.setInputContentClaimFileSize(FormatUtils.formatDataSize(event.getPreviousFileSize()));
}
// determine if authorized for event replay
// this authorization is for data write only; ensure data read was approved
final AuthorizationResult replayAuthorized = checkAuthorizationForReplay(event);
// replay
dto.setReplayAvailable(contentAvailability.isReplayable() && Result.Approved.equals(replayAuthorized.getResult()));
dto.setReplayExplanation(contentAvailability.isReplayable()
&& !Result.Approved.equals(replayAuthorized.getResult()) ? replayAuthorized.getExplanation() : contentAvailability.getReasonNotReplayable());
dto.setSourceConnectionIdentifier(event.getSourceQueueIdentifier());
}
// determine if authorized for event replay
final AuthorizationResult replayAuthorized = checkAuthorizationForReplay(event);
// replay
dto.setReplayAvailable(contentAvailability.isReplayable() && Result.Approved.equals(replayAuthorized.getResult()));
dto.setReplayExplanation(contentAvailability.isReplayable()
&& !Result.Approved.equals(replayAuthorized.getResult()) ? replayAuthorized.getExplanation() : contentAvailability.getReasonNotReplayable());
dto.setSourceConnectionIdentifier(event.getSourceQueueIdentifier());
// event duration
if (event.getEventDuration() >= 0) {
dto.setEventDuration(event.getEventDuration());
@ -1494,18 +1558,29 @@ public class ControllerFacade implements Authorizable {
private void setComponentDetails(final ProvenanceEventDTO dto) {
final ProcessGroup root = flowController.getGroup(flowController.getRootGroupId());
final AuthorizationResult componentResult = checkConnectableAuthorization(dto.getComponentId());
final Connectable connectable = root.findLocalConnectable(dto.getComponentId());
if (connectable != null) {
dto.setGroupId(connectable.getProcessGroup().getIdentifier());
dto.setComponentName(connectable.getName());
if (Result.Denied.equals(componentResult.getResult())) {
dto.setComponentType("Processor");
dto.setComponentName(dto.getComponentId());
} else {
dto.setComponentName(connectable.getName());
}
return;
}
final RemoteGroupPort remoteGroupPort = root.findRemoteGroupPort(dto.getComponentId());
if (remoteGroupPort != null) {
dto.setGroupId(remoteGroupPort.getProcessGroupIdentifier());
dto.setComponentName(remoteGroupPort.getName());
if (Result.Denied.equals(componentResult.getResult())) {
dto.setComponentType("RemoteGroupPort");
dto.setComponentName(dto.getComponentId());
} else {
dto.setComponentName(remoteGroupPort.getName());
}
return;
}
@ -1513,14 +1588,17 @@ public class ControllerFacade implements Authorizable {
if (connection != null) {
dto.setGroupId(connection.getProcessGroup().getIdentifier());
String name = connection.getName();
final Collection<Relationship> relationships = connection.getRelationships();
if (StringUtils.isBlank(name) && CollectionUtils.isNotEmpty(relationships)) {
name = StringUtils.join(relationships.stream().map(relationship -> relationship.getName()).collect(Collectors.toSet()), ", ");
if (Result.Denied.equals(componentResult.getResult())) {
dto.setComponentType("Connection");
dto.setComponentName(dto.getComponentId());
} else {
String name = connection.getName();
final Collection<Relationship> relationships = connection.getRelationships();
if (StringUtils.isBlank(name) && CollectionUtils.isNotEmpty(relationships)) {
name = StringUtils.join(relationships.stream().map(relationship -> relationship.getName()).collect(Collectors.toSet()), ", ");
}
dto.setComponentName(name);
}
dto.setComponentName(name);
return;
}
}

View File

@ -732,7 +732,7 @@ public final class SnippetUtils {
/**
* Clones all the component specified policies for the specified original component. This will include the component resource, data resource
* for the component, data transfer resource for the component, and policy resource for the component.
* for the component, view provenance for the component, data transfer resource for the component, and policy resource for the component.
*
* @param originalComponentResource original component resource
* @param clonedComponentResource cloned component resource
@ -746,6 +746,7 @@ public final class SnippetUtils {
final Map<Resource, Resource> resources = new HashMap<>();
resources.put(originalComponentResource, clonedComponentResource);
resources.put(ResourceFactory.getDataResource(originalComponentResource), ResourceFactory.getDataResource(clonedComponentResource));
resources.put(ResourceFactory.getProvenanceDataResource(originalComponentResource), ResourceFactory.getProvenanceDataResource(clonedComponentResource));
resources.put(ResourceFactory.getDataTransferResource(originalComponentResource), ResourceFactory.getDataTransferResource(clonedComponentResource));
resources.put(ResourceFactory.getPolicyResource(originalComponentResource), ResourceFactory.getPolicyResource(clonedComponentResource));
@ -829,7 +830,7 @@ public final class SnippetUtils {
/**
* Attempts to roll back all policies for the specified component. This includes the component resource, data resource
* for the component, data transfer resource for the component, and policy resource for the component.
* for the component, view provenance resource for the component, data transfer resource for the component, and policy resource for the component.
*
* @param componentResource component resource
*/
@ -841,6 +842,7 @@ public final class SnippetUtils {
final List<Resource> resources = new ArrayList<>();
resources.add(componentResource);
resources.add(ResourceFactory.getDataResource(componentResource));
resources.add(ResourceFactory.getProvenanceDataResource(componentResource));
resources.add(ResourceFactory.getDataTransferResource(componentResource));
resources.add(ResourceFactory.getPolicyResource(componentResource));

View File

@ -532,11 +532,15 @@
}, {
text: 'view the data',
value: 'read-data',
description: 'Allows users to view metadata and content for this component through provenance data and flowfile queues in outbound connections'
description: 'Allows users to view metadata and content for this component through flowfile queues in outbound connections'
}, {
text: 'modify the data',
value: 'write-data',
description: 'Allows users to empty flowfile queues in outbound connections and submit replays'
}, {
text: 'view provenance',
value: 'read-provenance',
description: 'Allows users to view provenance data generated by this component'
}, {
text: 'receive data via site-to-site',
value: 'write-receive-data',
@ -570,6 +574,9 @@
} else if (option.value === 'write-data') {
$('#selected-policy-action').text('write');
resource = ('data/' + resource);
} else if (option.value === 'read-provenance') {
$('#selected-policy-action').text('read');
resource = ('provenance-data/' + resource);
} else if (option.value === 'read-policies') {
$('#selected-policy-action').text('read');
resource = ('policies/' + resource);
@ -1470,6 +1477,9 @@
.combo('setOptionEnabled', {
value: 'read-data'
}, false)
.combo('setOptionEnabled', {
value: 'read-provenance'
}, false)
.combo('setOptionEnabled', {
value: 'write-data'
}, false)
@ -1514,6 +1524,9 @@
.combo('setOptionEnabled', {
value: 'read-data'
}, false)
.combo('setOptionEnabled', {
value: 'read-provenance'
}, false)
.combo('setOptionEnabled', {
value: 'write-data'
}, false)
@ -1558,6 +1571,9 @@
.combo('setOptionEnabled', {
value: 'read-data'
}, false)
.combo('setOptionEnabled', {
value: 'read-provenance'
}, false)
.combo('setOptionEnabled', {
value: 'write-data'
}, false)
@ -1598,6 +1614,9 @@
.combo('setOptionEnabled', {
value: 'read-data'
}, true)
.combo('setOptionEnabled', {
value: 'read-provenance'
}, true)
.combo('setOptionEnabled', {
value: 'write-data'
}, true);

View File

@ -1301,6 +1301,17 @@
provenanceTableCtrl.getEventDetails(eventId, clusterNodeId).done(function (response) {
var event = response.provenanceEvent;
// Hide or show dialog tabs as required if base properties are defined
var tabs = $('#event-details-tabs').find("li");
$(tabs).each(function(index) {
if ((event["attributes"] === undefined && index == 1) ||
(event["inputContentAvailable"] === undefined && index ==2)) {
$(this).hide();
} else {
$(this).show();
}
});
// update the event details
$('#provenance-event-id').text(event.eventId);
$('#provenance-event-time').html(nfCommon.formatValue(event.eventTime)).ellipsis();

View File

@ -385,11 +385,7 @@ public class PersistentProvenanceRepository implements ProvenanceRepository {
final Authorizable eventAuthorizable;
try {
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
} catch (final ResourceNotFoundException rnfe) {
return false;
}
@ -403,13 +399,8 @@ public class PersistentProvenanceRepository implements ProvenanceRepository {
return;
}
final Authorizable eventAuthorizable;
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes());
final Authorizable eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
eventAuthorizable.authorize(authorizer, RequestAction.READ, user);
}
public List<ProvenanceEventRecord> filterUnauthorizedEvents(final List<ProvenanceEventRecord> events, final NiFiUser user) {

View File

@ -226,13 +226,8 @@ public class WriteAheadProvenanceRepository implements ProvenanceRepository {
return;
}
final Authorizable eventAuthorizable;
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes());
final Authorizable eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
eventAuthorizable.authorize(authorizer, RequestAction.READ, user);
}

View File

@ -46,16 +46,12 @@ public class UserEventAuthorizer implements EventAuthorizer {
final Authorizable eventAuthorizable;
try {
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
} catch (final ResourceNotFoundException rnfe) {
return false;
}
final AuthorizationResult result = eventAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user, event.getAttributes());
final AuthorizationResult result = eventAuthorizable.checkAuthorization(authorizer, RequestAction.READ, user);
return Result.Approved.equals(result.getResult());
}
@ -65,12 +61,7 @@ public class UserEventAuthorizer implements EventAuthorizer {
return;
}
final Authorizable eventAuthorizable;
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes());
final Authorizable eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
eventAuthorizable.authorize(authorizer, RequestAction.READ, user);
}
}

View File

@ -262,11 +262,7 @@ public class VolatileProvenanceRepository implements ProvenanceRepository {
final Authorizable eventAuthorizable;
try {
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
} catch (final ResourceNotFoundException rnfe) {
return false;
}
@ -280,13 +276,8 @@ public class VolatileProvenanceRepository implements ProvenanceRepository {
return;
}
final Authorizable eventAuthorizable;
if (event.isRemotePortType()) {
eventAuthorizable = resourceFactory.createRemoteDataAuthorizable(event.getComponentId());
} else {
eventAuthorizable = resourceFactory.createLocalDataAuthorizable(event.getComponentId());
}
eventAuthorizable.authorize(authorizer, RequestAction.READ, user, event.getAttributes());
final Authorizable eventAuthorizable = resourceFactory.createProvenanceDataAuthorizable(event.getComponentId());
eventAuthorizable.authorize(authorizer, RequestAction.READ, user);
}
private Filter<ProvenanceEventRecord> createFilter(final Query query, final NiFiUser user) {