From 1511887a6825c1bc96285b8b3ac999cb9d2601cb Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Mon, 1 Aug 2016 10:52:11 -0400 Subject: [PATCH] NIFI-2301: - Ensure all component specific policies are removed when the component is removed. - Allowing snippets to be created if the user has read or write access as we don't know what the intended snippet usage. When used the snippet is still authorized accordingly. - Ensuring actions involving Process Groups correctly authorize encapsulated components. - Not requiring read permissions when showing the delete button for Controller Services and Reporting Tasks. This closes #757 Signed-off-by: jpercivall --- .../org/apache/nifi/groups/ProcessGroup.java | 7 + .../nifi/groups/StandardProcessGroup.java | 13 ++ .../service/mock/MockProcessGroup.java | 5 + .../authorization/AuthorizableLookup.java | 2 +- .../ProcessGroupAuthorizable.java | 40 +++++ .../StandardAuthorizableLookup.java | 36 +++- .../nifi/web/StandardNiFiServiceFacade.java | 167 +++++++++++++----- .../nifi/web/api/ApplicationResource.java | 22 ++- .../web/api/ControllerServiceResource.java | 4 +- .../nifi/web/api/ProcessGroupResource.java | 97 +++++----- .../nifi/web/api/ReportingTaskResource.java | 4 +- .../apache/nifi/web/api/SnippetResource.java | 28 ++- .../js/nf/canvas/nf-controller-service.js | 4 +- .../js/nf/canvas/nf-controller-services.js | 6 +- .../main/webapp/js/nf/canvas/nf-settings.js | 6 +- 15 files changed, 328 insertions(+), 113 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java index d06d49cbb2..fd4673d28c 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/groups/ProcessGroup.java @@ -671,6 +671,13 @@ public interface ProcessGroup extends Authorizable, Positionable { */ void removeFunnel(Funnel funnel); + /** + * @return a List of all Funnel that are children or descendants of this + * ProcessGroup. This performs a recursive search of all descendant + * ProcessGroups + */ + List findAllFunnels(); + /** * Adds the given Controller Service to this group * diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java index 927bf89dc8..f71378df13 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/groups/StandardProcessGroup.java @@ -1578,6 +1578,19 @@ public final class StandardProcessGroup implements ProcessGroup { return allOutputPorts; } + @Override + public List findAllFunnels() { + return findAllFunnels(this); + } + + private List findAllFunnels(final ProcessGroup start) { + final List allFunnels = new ArrayList<>(start.getFunnels()); + for (final ProcessGroup group : start.getProcessGroups()) { + allFunnels.addAll(findAllFunnels(group)); + } + return allFunnels; + } + @Override public Port getInputPortByName(final String name) { return getPortByName(name, this, new InputPortRetriever()); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java index fddba053a1..27c629cefd 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/MockProcessGroup.java @@ -461,6 +461,11 @@ public class MockProcessGroup implements ProcessGroup { return null; } + @Override + public List findAllFunnels() { + return null; + } + @Override public void removeFunnel(final Funnel funnel) { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java index bc958c356f..69104db590 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizableLookup.java @@ -80,7 +80,7 @@ public interface AuthorizableLookup { * @param id process group id * @return authorizable */ - Authorizable getProcessGroup(String id); + ProcessGroupAuthorizable getProcessGroup(String id); /** * Get the authorizable RemoteProcessGroup. diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java new file mode 100644 index 0000000000..ce7c10b7da --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/ProcessGroupAuthorizable.java @@ -0,0 +1,40 @@ +/* + * 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; + +import org.apache.nifi.authorization.resource.Authorizable; + +import java.util.Set; + +/** + * Authorizable for a ProcessGroup and its encapsulated components. + */ +public interface ProcessGroupAuthorizable { + /** + * Returns the authorizable for this ProcessGroup. Non null + * + * @return authorizable + */ + Authorizable getAuthorizable(); + + /** + * The authorizables for all encapsulated components. Non null + * + * @return all encapsulated authorizables + */ + Set getEncapsulatedAuthorizables(); +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java index 1e3c03ac0f..32862b204e 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java @@ -19,8 +19,8 @@ package org.apache.nifi.authorization; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.resource.AccessPolicyAuthorizable; import org.apache.nifi.authorization.resource.Authorizable; -import org.apache.nifi.authorization.resource.DataTransferAuthorizable; import org.apache.nifi.authorization.resource.DataAuthorizable; +import org.apache.nifi.authorization.resource.DataTransferAuthorizable; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.resource.TenantAuthorizable; @@ -47,6 +47,10 @@ import org.apache.nifi.web.dao.ReportingTaskDAO; import org.apache.nifi.web.dao.SnippetDAO; import org.apache.nifi.web.dao.TemplateDAO; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + class StandardAuthorizableLookup implements AuthorizableLookup { @@ -152,8 +156,32 @@ class StandardAuthorizableLookup implements AuthorizableLookup { } @Override - public Authorizable getProcessGroup(final String id) { - return processGroupDAO.getProcessGroup(id); + public ProcessGroupAuthorizable getProcessGroup(final String id) { + final ProcessGroup processGroup = processGroupDAO.getProcessGroup(id); + + final Set encapsulatedAuthorizables = new HashSet<>(); + processGroup.findAllProcessors().forEach(processor -> encapsulatedAuthorizables.add(processor)); + processGroup.findAllConnections().forEach(connection -> encapsulatedAuthorizables.add(connection)); + processGroup.findAllInputPorts().forEach(inputPort -> encapsulatedAuthorizables.add(inputPort)); + processGroup.findAllOutputPorts().forEach(outputPort -> encapsulatedAuthorizables.add(outputPort)); + processGroup.findAllFunnels().forEach(funnel -> encapsulatedAuthorizables.add(funnel)); + processGroup.findAllLabels().forEach(label -> encapsulatedAuthorizables.add(label)); + processGroup.findAllProcessGroups().forEach(childGroup -> encapsulatedAuthorizables.add(childGroup)); + processGroup.findAllRemoteProcessGroups().forEach(remoteProcessGroup -> encapsulatedAuthorizables.add(remoteProcessGroup)); + processGroup.findAllTemplates().forEach(template -> encapsulatedAuthorizables.add(template)); + processGroup.findAllControllerServices().forEach(controllerService -> encapsulatedAuthorizables.add(controllerService)); + + return new ProcessGroupAuthorizable() { + @Override + public Authorizable getAuthorizable() { + return processGroup; + } + + @Override + public Set getEncapsulatedAuthorizables() { + return Collections.unmodifiableSet(encapsulatedAuthorizables); + } + }; } @Override @@ -334,7 +362,7 @@ class StandardAuthorizableLookup implements AuthorizableLookup { authorizable = getProcessor(componentId); break; case ProcessGroup: - authorizable = getProcessGroup(componentId); + authorizable = getProcessGroup(componentId).getAuthorizable(); break; case RemoteProcessGroup: authorizable = getRemoteProcessGroup(componentId); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 3b010d529b..8ce1f3e4ad 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -850,9 +850,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Connection connection = connectionDAO.getConnection(connectionId); final ConnectionDTO snapshot = deleteComponent( revision, - connection.getResource().getIdentifier(), + connection.getResource(), () -> connectionDAO.deleteConnection(connectionId), - false, + false, // no policies to remove dtoFactory.createConnectionDto(connection)); return entityFactory.createConnectionEntity(snapshot, null, null, null); @@ -884,7 +884,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ProcessorNode processor = processorDAO.getProcessor(processorId); final ProcessorDTO snapshot = deleteComponent( revision, - processor.getResource().getIdentifier(), + processor.getResource(), () -> processorDAO.deleteProcessor(processorId), true, dtoFactory.createProcessorDto(processor)); @@ -897,7 +897,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Label label = labelDAO.getLabel(labelId); final LabelDTO snapshot = deleteComponent( revision, - label.getResource().getIdentifier(), + label.getResource(), () -> labelDAO.deleteLabel(labelId), true, dtoFactory.createLabelDto(label)); @@ -916,7 +916,17 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userId; final UserDTO snapshot = deleteComponent( revision, - resourceIdentifier, + new Resource() { + @Override + public String getIdentifier() { + return resourceIdentifier; + } + + @Override + public String getName() { + return resourceIdentifier; + } + }, () -> userDAO.deleteUser(userId), false, // no user specific policies to remove dtoFactory.createUserDto(user, userGroups, policyEntities)); @@ -934,7 +944,17 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final String resourceIdentifier = ResourceFactory.getTenantResource().getIdentifier() + "/" + userGroupId; final UserGroupDTO snapshot = deleteComponent( revision, - resourceIdentifier, + new Resource() { + @Override + public String getIdentifier() { + return resourceIdentifier; + } + + @Override + public String getName() { + return resourceIdentifier; + } + }, () -> userGroupDAO.deleteUserGroup(userGroupId), false, // no user group specific policies to remove dtoFactory.createUserGroupDto(userGroup, users)); @@ -949,7 +969,17 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Set users = accessPolicy != null ? accessPolicy.getUsers().stream().map(mapUserIdToTenantEntity()).collect(Collectors.toSet()) : null; final AccessPolicyDTO snapshot = deleteComponent( revision, - accessPolicy.getResource(), + new Resource() { + @Override + public String getIdentifier() { + return accessPolicy.getResource(); + } + + @Override + public String getName() { + return accessPolicy.getResource(); + } + }, () -> accessPolicyDAO.deleteAccessPolicy(accessPolicyId), false, // no need to clean up any policies as it's already been removed above dtoFactory.createAccessPolicyDto(accessPolicy, userGroups, @@ -963,7 +993,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Funnel funnel = funnelDAO.getFunnel(funnelId); final FunnelDTO snapshot = deleteComponent( revision, - funnel.getResource().getIdentifier(), + funnel.getResource(), () -> funnelDAO.deleteFunnel(funnelId), true, dtoFactory.createFunnelDto(funnel)); @@ -975,50 +1005,30 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { * Deletes a component using the Optimistic Locking Manager * * @param revision the current revision - * @param resourceIdentifier the identifier of the resource being removed + * @param resource the resource being removed * @param deleteAction the action that deletes the component via the appropriate DAO object * @param cleanUpPolicies whether or not the policies for this resource should be removed as well - not necessary when there are * no component specific policies or if the policies of the component are inherited * @return a dto that represents the new configuration */ - private D deleteComponent(final Revision revision, final String resourceIdentifier, final Runnable deleteAction, final boolean cleanUpPolicies, final D dto) { + private D deleteComponent(final Revision revision, final Resource resource, final Runnable deleteAction, final boolean cleanUpPolicies, final D dto) { final RevisionClaim claim = new StandardRevisionClaim(revision); final NiFiUser user = NiFiUserUtils.getNiFiUser(); return revisionManager.deleteRevision(claim, user, new DeleteRevisionTask() { @Override public D performTask() { - logger.debug("Attempting to delete component {} with claim {}", resourceIdentifier, claim); + logger.debug("Attempting to delete component {} with claim {}", resource.getIdentifier(), claim); // run the delete action deleteAction.run(); // save the flow controllerFacade.save(); - logger.debug("Deletion of component {} was successful", resourceIdentifier); + logger.debug("Deletion of component {} was successful", resource.getIdentifier()); - // clean up the policy if necessary and configured with a policy based authorizer - if (cleanUpPolicies && accessPolicyDAO.supportsConfigurableAuthorizer()) { - try { - // since the component is being deleted, also delete any relevant read access policies - final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(RequestAction.READ, resourceIdentifier); - if (readPolicy != null) { - accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier()); - } - } catch (final Exception e) { - logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.READ, resourceIdentifier), e); - } - try { - // since the component is being deleted, also delete any relevant write access policies - final AccessPolicy writePolicy = accessPolicyDAO.getAccessPolicy(RequestAction.WRITE, resourceIdentifier); - if (writePolicy != null) { - accessPolicyDAO.deleteAccessPolicy(writePolicy.getIdentifier()); - } - } catch (final ResourceNotFoundException e) { - // no policy exists for this component... no worries - } catch (final Exception e) { - logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", RequestAction.WRITE, resourceIdentifier), e); - } + if (cleanUpPolicies) { + cleanUpPolicies(resource); } return dto; @@ -1026,6 +1036,36 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { }); } + /** + * Clean up the policies for the specified component resource. + * + * @param componentResource the resource for the component + */ + private void cleanUpPolicies(final Resource componentResource) { + // ensure the authorizer supports configuration + if (accessPolicyDAO.supportsConfigurableAuthorizer()) { + final List resources = new ArrayList<>(); + resources.add(componentResource); + resources.add(ResourceFactory.getDataResource(componentResource)); + resources.add(ResourceFactory.getDataTransferResource(componentResource)); + resources.add(ResourceFactory.getPolicyResource(componentResource)); + + for (final Resource resource : resources) { + for (final RequestAction action : RequestAction.values()) { + try { + // since the component is being deleted, also delete any relevant access policies + final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(action, resource.getIdentifier()); + if (readPolicy != null) { + accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier()); + } + } catch (final Exception e) { + logger.warn(String.format("Unable to remove access policy for %s %s after component removal.", action, resource.getIdentifier()), e); + } + } + } + } + } + @Override public void verifyDeleteSnippet(final String snippetId, final Set affectedComponentIds) { snippetDAO.verifyDeleteSnippetComponents(snippetId); @@ -1035,6 +1075,32 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { public SnippetEntity deleteSnippet(final Set revisions, final String snippetId) { final Snippet snippet = snippetDAO.getSnippet(snippetId); + // grab the resources in the snippet so we can delete the policies afterwards + final Set snippetResources = new HashSet<>(); + snippet.getProcessors().keySet().forEach(id -> snippetResources.add(processorDAO.getProcessor(id).getResource())); + snippet.getInputPorts().keySet().forEach(id -> snippetResources.add(inputPortDAO.getPort(id).getResource())); + snippet.getOutputPorts().keySet().forEach(id -> snippetResources.add(outputPortDAO.getPort(id).getResource())); + snippet.getFunnels().keySet().forEach(id -> snippetResources.add(funnelDAO.getFunnel(id).getResource())); + snippet.getLabels().keySet().forEach(id -> snippetResources.add(labelDAO.getLabel(id).getResource())); + snippet.getRemoteProcessGroups().keySet().forEach(id -> snippetResources.add(remoteProcessGroupDAO.getRemoteProcessGroup(id).getResource())); + snippet.getProcessGroups().keySet().forEach(id -> { + final ProcessGroup processGroup = processGroupDAO.getProcessGroup(id); + + // add the process group + snippetResources.add(processGroup.getResource()); + + // add each encapsulated component + processGroup.findAllProcessors().forEach(processor -> snippetResources.add(processor.getResource())); + processGroup.findAllInputPorts().forEach(inputPort -> snippetResources.add(inputPort.getResource())); + processGroup.findAllOutputPorts().forEach(outputPort -> snippetResources.add(outputPort.getResource())); + processGroup.findAllFunnels().forEach(funnel -> snippetResources.add(funnel.getResource())); + processGroup.findAllLabels().forEach(label -> snippetResources.add(label.getResource())); + processGroup.findAllProcessGroups().forEach(childGroup -> snippetResources.add(childGroup.getResource())); + processGroup.findAllRemoteProcessGroups().forEach(remoteProcessGroup -> snippetResources.add(remoteProcessGroup.getResource())); + processGroup.findAllTemplates().forEach(template -> snippetResources.add(template.getResource())); + processGroup.findAllControllerServices().forEach(controllerService -> snippetResources.add(controllerService.getResource())); + }); + final NiFiUser user = NiFiUserUtils.getNiFiUser(); final RevisionClaim claim = new StandardRevisionClaim(revisions); final SnippetDTO dto = revisionManager.deleteRevision(claim, user, new DeleteRevisionTask() { @@ -1054,6 +1120,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { } }); + // clean up component policies + snippetResources.forEach(resource -> cleanUpPolicies(resource)); + return entityFactory.createSnippetEntity(dto); } @@ -1062,7 +1131,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Port port = inputPortDAO.getPort(inputPortId); final PortDTO snapshot = deleteComponent( revision, - port.getResource().getIdentifier(), + port.getResource(), () -> inputPortDAO.deletePort(inputPortId), true, dtoFactory.createPortDto(port)); @@ -1075,7 +1144,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final Port port = outputPortDAO.getPort(outputPortId); final PortDTO snapshot = deleteComponent( revision, - port.getResource().getIdentifier(), + port.getResource(), () -> outputPortDAO.deletePort(outputPortId), true, dtoFactory.createPortDto(port)); @@ -1086,13 +1155,29 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { @Override public ProcessGroupEntity deleteProcessGroup(final Revision revision, final String groupId) { final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId); + + // grab the resources in the snippet so we can delete the policies afterwards + final Set groupResources = new HashSet<>(); + processGroup.findAllProcessors().forEach(processor -> groupResources.add(processor.getResource())); + processGroup.findAllInputPorts().forEach(inputPort -> groupResources.add(inputPort.getResource())); + processGroup.findAllOutputPorts().forEach(outputPort -> groupResources.add(outputPort.getResource())); + processGroup.findAllFunnels().forEach(funnel -> groupResources.add(funnel.getResource())); + processGroup.findAllLabels().forEach(label -> groupResources.add(label.getResource())); + processGroup.findAllProcessGroups().forEach(childGroup -> groupResources.add(childGroup.getResource())); + processGroup.findAllRemoteProcessGroups().forEach(remoteProcessGroup -> groupResources.add(remoteProcessGroup.getResource())); + processGroup.findAllTemplates().forEach(template -> groupResources.add(template.getResource())); + processGroup.findAllControllerServices().forEach(controllerService -> groupResources.add(controllerService.getResource())); + final ProcessGroupDTO snapshot = deleteComponent( revision, - processGroup.getResource().getIdentifier(), + processGroup.getResource(), () -> processGroupDAO.deleteProcessGroup(groupId), true, dtoFactory.createProcessGroupDto(processGroup)); + // delete all applicable component policies + groupResources.forEach(groupResource -> cleanUpPolicies(groupResource)); + return entityFactory.createProcessGroupEntity(snapshot, null, null, null, null); } @@ -1101,7 +1186,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId); final RemoteProcessGroupDTO snapshot = deleteComponent( revision, - remoteProcessGroup.getResource().getIdentifier(), + remoteProcessGroup.getResource(), () -> remoteProcessGroupDAO.deleteRemoteProcessGroup(remoteProcessGroupId), true, dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup)); @@ -1740,7 +1825,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId); final ControllerServiceDTO snapshot = deleteComponent( revision, - controllerService.getResource().getIdentifier(), + controllerService.getResource(), () -> controllerServiceDAO.deleteControllerService(controllerServiceId), true, dtoFactory.createControllerServiceDto(controllerService)); @@ -1794,7 +1879,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId); final ReportingTaskDTO snapshot = deleteComponent( revision, - reportingTask.getResource().getIdentifier(), + reportingTask.getResource(), () -> reportingTaskDAO.deleteReportingTask(reportingTaskId), true, dtoFactory.createReportingTaskDto(reportingTask)); @@ -2693,7 +2778,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { authorizable = authorizableLookup.getOutputPort(sourceId); break; case ProcessGroup: - authorizable = authorizableLookup.getProcessGroup(sourceId); + authorizable = authorizableLookup.getProcessGroup(sourceId).getAuthorizable(); break; case RemoteProcessGroup: authorizable = authorizableLookup.getRemoteProcessGroup(sourceId); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java index 17017643e0..aa057fefff 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java @@ -21,6 +21,8 @@ import com.sun.jersey.api.representation.Form; import com.sun.jersey.core.util.MultivaluedMapImpl; import com.sun.jersey.server.impl.model.method.dispatch.FormDispatchProvider; import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.authorization.AuthorizableLookup; +import org.apache.nifi.authorization.AuthorizeAccess; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.resource.Authorizable; @@ -40,10 +42,8 @@ import org.apache.nifi.remote.exception.HandshakeException; import org.apache.nifi.remote.exception.NotAuthorizedException; import org.apache.nifi.remote.protocol.ResponseCode; import org.apache.nifi.remote.protocol.http.HttpHeaders; -import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.ComponentIdGenerator; -import org.apache.nifi.authorization.AuthorizableLookup; -import org.apache.nifi.authorization.AuthorizeAccess; +import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.web.NiFiServiceFacade; import org.apache.nifi.web.Revision; import org.apache.nifi.web.api.dto.RevisionDTO; @@ -418,7 +418,13 @@ public abstract class ApplicationResource { protected void authorizeSnippet(final Snippet snippet, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action) { final Consumer authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser()); - snippet.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(authorize); + snippet.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(processGroupAuthorizable -> { + // authorize the process group + authorize.accept(processGroupAuthorizable.getAuthorizable()); + + // authorize the contents of the group + processGroupAuthorizable.getEncapsulatedAuthorizables().forEach(authorize); + }); snippet.getRemoteProcessGroups().keySet().stream().map(id -> lookup.getRemoteProcessGroup(id)).forEach(authorize); snippet.getProcessors().keySet().stream().map(id -> lookup.getProcessor(id)).forEach(authorize); snippet.getInputPorts().keySet().stream().map(id -> lookup.getInputPort(id)).forEach(authorize); @@ -437,7 +443,13 @@ public abstract class ApplicationResource { protected void authorizeSnippet(final SnippetDTO snippet, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action) { final Consumer authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser()); - snippet.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(authorize); + snippet.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(processGroupAuthorizable -> { + // authorize the process group + authorize.accept(processGroupAuthorizable.getAuthorizable()); + + // authorize the contents of the group + processGroupAuthorizable.getEncapsulatedAuthorizables().forEach(authorize); + }); snippet.getRemoteProcessGroups().keySet().stream().map(id -> lookup.getRemoteProcessGroup(id)).forEach(authorize); snippet.getProcessors().keySet().stream().map(id -> lookup.getProcessor(id)).forEach(authorize); snippet.getInputPorts().keySet().stream().map(id -> lookup.getInputPort(id)).forEach(authorize); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java index 51d732c1f6..89b441f089 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java @@ -96,9 +96,7 @@ public class ControllerServiceResource extends ApplicationResource { */ public Set populateRemainingControllerServiceEntitiesContent(final Set controllerServiceEntities) { for (ControllerServiceEntity controllerServiceEntity : controllerServiceEntities) { - if (controllerServiceEntity.getComponent() != null) { - populateRemainingControllerServiceEntityContent(controllerServiceEntity); - } + populateRemainingControllerServiceEntityContent(controllerServiceEntity); } return controllerServiceEntities; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java index 56bd398e05..93416a20ba 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java @@ -25,13 +25,15 @@ 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.AuthorizableLookup; import org.apache.nifi.authorization.Authorizer; +import org.apache.nifi.authorization.ProcessGroupAuthorizable; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.resource.Authorizable; +import org.apache.nifi.authorization.user.NiFiUser; import org.apache.nifi.authorization.user.NiFiUserUtils; import org.apache.nifi.cluster.protocol.NodeIdentifier; import org.apache.nifi.controller.Snippet; -import org.apache.nifi.authorization.AuthorizableLookup; import org.apache.nifi.web.NiFiServiceFacade; import org.apache.nifi.web.Revision; import org.apache.nifi.web.api.dto.ConnectionDTO; @@ -208,7 +210,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -285,17 +287,17 @@ public class ProcessGroupResource extends ApplicationResource { // handle expects request (usually from the cluster manager) final Revision revision = getRevision(processGroupEntity, id); return withWriteLock( - serviceFacade, - revision, - lookup -> { - Authorizable authorizable = lookup.getProcessGroup(id); - authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - }, - null, - () -> { - // update the process group - final ProcessGroupEntity entity = serviceFacade.updateProcessGroup(revision, requestProcessGroupDTO); - populateRemainingProcessGroupEntityContent(entity); + serviceFacade, + revision, + lookup -> { + Authorizable authorizable = lookup.getProcessGroup(id).getAuthorizable(); + authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + }, + null, + () -> { + // update the process group + final ProcessGroupEntity entity = serviceFacade.updateProcessGroup(revision, requestProcessGroupDTO); + populateRemainingProcessGroupEntityContent(entity); return clusterContext(generateOkResponse(entity)).build(); } @@ -357,16 +359,25 @@ public class ProcessGroupResource extends ApplicationResource { // handle expects request (usually from the cluster manager) final Revision revision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id); return withWriteLock( - serviceFacade, - revision, - lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(id); - processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - }, - () -> serviceFacade.verifyDeleteProcessGroup(id), - () -> { - // delete the process group - final ProcessGroupEntity entity = serviceFacade.deleteProcessGroup(revision, id); + serviceFacade, + revision, + lookup -> { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final ProcessGroupAuthorizable processGroupAuthorizable = lookup.getProcessGroup(id); + + // ensure write to the process group + final Authorizable processGroup = processGroupAuthorizable.getAuthorizable(); + processGroup.authorize(authorizer, RequestAction.WRITE, user); + + // ensure write to all encapsulated components + processGroupAuthorizable.getEncapsulatedAuthorizables().forEach(encaupsulatedAuthorizable -> { + encaupsulatedAuthorizable.authorize(authorizer, RequestAction.WRITE, user); + }); + }, + () -> serviceFacade.verifyDeleteProcessGroup(id), + () -> { + // delete the process group + final ProcessGroupEntity entity = serviceFacade.deleteProcessGroup(revision, id); // create the response return clusterContext(generateOkResponse(entity)).build(); @@ -441,7 +452,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -500,7 +511,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -597,7 +608,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -657,7 +668,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -743,7 +754,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -801,7 +812,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -886,7 +897,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -944,7 +955,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -1030,7 +1041,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -1088,7 +1099,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -1174,7 +1185,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -1232,7 +1243,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -1324,7 +1335,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -1407,7 +1418,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -1514,7 +1525,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { // ensure write access to the group - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); // ensure write access to the source @@ -1582,7 +1593,7 @@ public class ProcessGroupResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser()); }); @@ -1751,7 +1762,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); final Authorizable template = lookup.getTemplate(instantiateTemplateRequestEntity.getTemplateId()); @@ -1786,7 +1797,7 @@ public class ProcessGroupResource extends ApplicationResource { private void authorizeSnippetUsage(final AuthorizableLookup lookup, final String groupId, final String snippetId) { // ensure write access to the target process group - lookup.getProcessGroup(groupId).authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + lookup.getProcessGroup(groupId).getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); // ensure read permission to every component in the snippet final Snippet snippet = lookup.getSnippet(snippetId); @@ -2005,7 +2016,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } @@ -2110,7 +2121,7 @@ public class ProcessGroupResource extends ApplicationResource { if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) { // authorize access serviceFacade.authorizeAccess(lookup -> { - final Authorizable processGroup = lookup.getProcessGroup(groupId); + final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable(); processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); }); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java index 67ba737ee3..0920220a38 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java @@ -84,9 +84,7 @@ public class ReportingTaskResource extends ApplicationResource { */ public Set populateRemainingReportingTaskEntitiesContent(final Set reportingTaskEntities) { for (ReportingTaskEntity reportingTaskEntity : reportingTaskEntities) { - if (reportingTaskEntity.getComponent() != null) { - populateRemainingReportingTaskEntityContent(reportingTaskEntity); - } + populateRemainingReportingTaskEntityContent(reportingTaskEntity); } return reportingTaskEntities; } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java index e30de40d5d..eab59dba90 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/SnippetResource.java @@ -22,6 +22,7 @@ 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.nifi.authorization.AccessDeniedException; import org.apache.nifi.authorization.Authorizer; import org.apache.nifi.authorization.RequestAction; import org.apache.nifi.authorization.user.NiFiUserUtils; @@ -141,7 +142,18 @@ public class SnippetResource extends ApplicationResource { // authorize access serviceFacade.authorizeAccess(lookup -> { final SnippetDTO snippet = snippetEntity.getSnippet(); - authorizeSnippet(snippet, authorizer, lookup, RequestAction.READ); + + // the snippet being created may be used later for batch component modifications, + // copy/paste, or template creation. during those subsequent actions, the snippet + // will again be authorized accordingly (read or write). at this point we do not + // know what the snippet will be used for so we need to attempt to authorize as + // read OR write + + try { + authorizeSnippet(snippet, authorizer, lookup, RequestAction.READ); + } catch (final AccessDeniedException e) { + authorizeSnippet(snippet, authorizer, lookup, RequestAction.WRITE); + } }); } if (validationPhase) { @@ -218,13 +230,13 @@ public class SnippetResource extends ApplicationResource { // get the revision from this snippet final Set revisions = serviceFacade.getRevisionsFromSnippet(snippetId); return withWriteLock( - serviceFacade, - revisions, - lookup -> { - // ensure write access to the target process group - if (requestSnippetDTO.getParentGroupId() != null) { - lookup.getProcessGroup(requestSnippetDTO.getParentGroupId()).authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); - } + serviceFacade, + revisions, + lookup -> { + // ensure write access to the target process group + if (requestSnippetDTO.getParentGroupId() != null) { + lookup.getProcessGroup(requestSnippetDTO.getParentGroupId()).getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + } // ensure write permission to every component in the snippet final Snippet snippet = lookup.getSnippet(snippetId); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js index 303eb47798..1a32c8efa1 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js @@ -2019,7 +2019,9 @@ nf.ControllerService = (function () { controllerServiceData.deleteItem(controllerServiceEntity.id); // reload the as necessary - reloadControllerServiceReferences(serviceTable, controllerServiceEntity.component); + if (controllerServiceEntity.permissions.canRead) { + reloadControllerServiceReferences(serviceTable, controllerServiceEntity.component); + } }).fail(nf.Common.handleAjaxError); } }; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js index fc0051743f..22becbc3c4 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js @@ -596,8 +596,6 @@ nf.ControllerServices = (function () { if (nf.Common.isEmpty(dataContext.component.validationErrors)) { markup += '
'; } - - markup += '
'; } if (dataContext.component.persistsState === true) { @@ -605,6 +603,10 @@ nf.ControllerServices = (function () { } } + if (dataContext.permissions.canWrite) { + markup += '
'; + } + // allow policy configuration conditionally if (nf.Canvas.isConfigurableAuthorizer() && nf.Common.canAccessTenants()) { markup += '
'; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js index b18339e23b..f94368d1e5 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js @@ -673,8 +673,6 @@ nf.Settings = (function () { if (dataContext.component.state === 'STOPPED' && nf.Common.isEmpty(dataContext.component.validationErrors)) { markup += '
'; } - - markup += '
'; } if (dataContext.component.persistsState === true) { @@ -682,6 +680,10 @@ nf.Settings = (function () { } } + if (dataContext.permissions.canWrite) { + markup += '
'; + } + // allow policy configuration conditionally if (nf.Canvas.isConfigurableAuthorizer() && nf.Common.canAccessTenants()) { markup += '
';