NIFI-2347: - Ensuring component specific policies are retained when using copy/paste. - This includes the policies for the component, data of the component, data transfers of the component, and policies of the component.

This closes #730

Signed-off-by: jpercivall <joepercivall@yahoo.com>
This commit is contained in:
Matt Gilman 2016-07-27 21:24:46 -04:00 committed by jpercivall
parent 25cadf5db1
commit 09b124714e
3 changed files with 421 additions and 213 deletions

View File

@ -92,10 +92,19 @@ public class StandardSnippetDAO implements SnippetDAO {
org.apache.nifi.util.SnippetUtils.moveSnippet(snippetContents, originX, originY);
}
// instantiate the snippet
try {
// instantiate the snippet and return the contents
flowController.instantiateSnippet(processGroup, snippetContents);
return snippetContents;
} catch (IllegalStateException ise) {
// illegal state will be thrown from instantiateSnippet when there is an issue with the snippet _before_ any of the
// components are actually created. if we've received this exception we want to attempt to roll back any of the
// policies that we've already cloned for this request
snippetUtils.rollbackClonedPolicies(snippetContents);
// rethrow the same exception
throw ise;
}
} catch (ProcessorInstantiationException pie) {
throw new NiFiCoreException(String.format("Unable to copy snippet because processor type '%s' is unknown to this NiFi.",
StringUtils.substringAfterLast(pie.getMessage(), ".")));

View File

@ -16,19 +16,12 @@
*/
package org.apache.nifi.web.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessPolicy;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.connectable.Connection;
@ -44,6 +37,7 @@ import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.groups.RemoteProcessGroup;
import org.apache.nifi.util.TypeOneUUIDGenerator;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.dto.ConnectableDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
@ -59,15 +53,34 @@ import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupContentsDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.dao.AccessPolicyDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Template utilities.
*/
public final class SnippetUtils {
private static final Logger logger = LoggerFactory.getLogger(SnippetUtils.class);
private FlowController flowController;
private DtoFactory dtoFactory;
private AccessPolicyDAO accessPolicyDAO;
/**
@ -262,8 +275,7 @@ public final class SnippetUtils {
}
public FlowSnippetDTO copy(final FlowSnippetDTO snippetContents, final ProcessGroup group,
final String idGenerationSeed, boolean isCopy) {
public FlowSnippetDTO copy(final FlowSnippetDTO snippetContents, final ProcessGroup group, final String idGenerationSeed, boolean isCopy) {
final FlowSnippetDTO snippetCopy = copyContentsForGroup(snippetContents, group.getIdentifier(), null, null, idGenerationSeed, isCopy);
resolveNameConflicts(snippetCopy, group);
return snippetCopy;
@ -319,10 +331,11 @@ public final class SnippetUtils {
}
}
private FlowSnippetDTO copyContentsForGroup(final FlowSnippetDTO snippetContents, final String groupId, final Map<String, ConnectableDTO> parentConnectableMap, Map<String, String> serviceIdMap,
final String idGenerationSeed, boolean isCopy) {
final FlowSnippetDTO snippetContentsCopy = new FlowSnippetDTO();
private FlowSnippetDTO copyContentsForGroup(final FlowSnippetDTO snippetContents, final String groupId, final Map<String, ConnectableDTO> parentConnectableMap,
Map<String, String> serviceIdMap, final String idGenerationSeed, boolean isCopy) {
final FlowSnippetDTO snippetContentsCopy = new FlowSnippetDTO();
try {
//
// Copy the Controller Services
//
@ -340,6 +353,13 @@ public final class SnippetUtils {
// Map old service ID to new service ID so that we can make sure that we reference the new ones.
serviceIdMap.put(serviceDTO.getId(), service.getId());
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.ControllerService, serviceDTO.getId(), serviceDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.ControllerService, service.getId(), service.getName()), idGenerationSeed);
}
}
}
@ -373,6 +393,13 @@ public final class SnippetUtils {
label.setId(generateId(labelDTO.getId(), idGenerationSeed, isCopy));
label.setParentGroupId(groupId);
labels.add(label);
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.Label, labelDTO.getId(), labelDTO.getLabel()),
ResourceFactory.getComponentResource(ResourceType.Label, label.getId(), label.getLabel()), idGenerationSeed);
}
}
}
snippetContentsCopy.setLabels(labels);
@ -395,6 +422,13 @@ public final class SnippetUtils {
funnels.add(cp);
connectableMap.put(funnelDTO.getParentGroupId() + "-" + funnelDTO.getId(), dtoFactory.createConnectableDto(cp));
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.Funnel, funnelDTO.getId(), funnelDTO.getId()),
ResourceFactory.getComponentResource(ResourceType.Funnel, cp.getId(), cp.getId()), idGenerationSeed);
}
}
}
snippetContentsCopy.setFunnels(funnels);
@ -413,6 +447,13 @@ public final class SnippetUtils {
if (parentConnectableMap != null) {
parentConnectableMap.put(portDTO.getParentGroupId() + "-" + portDTO.getId(), portConnectable);
}
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.InputPort, cp.getId(), cp.getName()), idGenerationSeed);
}
}
}
snippetContentsCopy.setInputPorts(inputPorts);
@ -431,6 +472,13 @@ public final class SnippetUtils {
if (parentConnectableMap != null) {
parentConnectableMap.put(portDTO.getParentGroupId() + "-" + portDTO.getId(), portConnectable);
}
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.OutputPort, cp.getId(), cp.getName()), idGenerationSeed);
}
}
}
snippetContentsCopy.setOutputPorts(outputPorts);
@ -448,6 +496,13 @@ public final class SnippetUtils {
processors.add(cp);
connectableMap.put(processorDTO.getParentGroupId() + "-" + processorDTO.getId(), dtoFactory.createConnectableDto(cp));
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.Processor, processorDTO.getId(), processorDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.Processor, cp.getId(), cp.getName()), idGenerationSeed);
}
}
}
snippetContentsCopy.setProcessors(processors);
@ -470,6 +525,13 @@ public final class SnippetUtils {
final FlowSnippetDTO contentsCopy = copyContentsForGroup(groupDTO.getContents(), cp.getId(), connectableMap, serviceIdMap, idGenerationSeed, isCopy);
cp.setContents(contentsCopy);
groups.add(cp);
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.ProcessGroup, groupDTO.getId(), groupDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.ProcessGroup, cp.getId(), cp.getName()), idGenerationSeed);
}
}
}
snippetContentsCopy.setProcessGroups(groups);
@ -496,6 +558,13 @@ public final class SnippetUtils {
}
remoteGroups.add(cp);
// clone policies as appropriate
if (isCopy) {
cloneComponentSpecificPolicies(
ResourceFactory.getComponentResource(ResourceType.RemoteProcessGroup, remoteGroupDTO.getId(), remoteGroupDTO.getName()),
ResourceFactory.getComponentResource(ResourceType.RemoteProcessGroup, cp.getId(), cp.getName()), idGenerationSeed);
}
}
}
snippetContentsCopy.setRemoteProcessGroups(remoteGroups);
@ -518,11 +587,136 @@ public final class SnippetUtils {
cp.setDestination(destination);
cp.setParentGroupId(groupId);
connections.add(cp);
// note - no need to copy policies of a connection as their permissions are inferred through the source and destination
}
}
snippetContentsCopy.setConnections(connections);
return snippetContentsCopy;
} catch (Exception e) {
// attempt to role back any policies of the copies that were created in preparation for the clone
rollbackClonedPolicies(snippetContentsCopy);
// rethrow the original exception
throw e;
}
}
/**
* 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.
*
* @param originalComponentResource original component resource
* @param clonedComponentResource cloned component resource
* @param idGenerationSeed id generation seed
*/
private void cloneComponentSpecificPolicies(final Resource originalComponentResource, final Resource clonedComponentResource, final String idGenerationSeed) {
final Map<Resource, Resource> resources = new HashMap<>();
resources.put(originalComponentResource, clonedComponentResource);
resources.put(ResourceFactory.getDataResource(originalComponentResource), ResourceFactory.getDataResource(clonedComponentResource));
resources.put(ResourceFactory.getDataTransferResource(originalComponentResource), ResourceFactory.getDataTransferResource(clonedComponentResource));
resources.put(ResourceFactory.getPolicyResource(originalComponentResource), ResourceFactory.getPolicyResource(clonedComponentResource));
for (final Entry<Resource, Resource> entry : resources.entrySet()) {
final Resource originalResource = entry.getKey();
final Resource cloneResource = entry.getValue();
for (final RequestAction action : RequestAction.values()) {
final AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(action, originalResource.getIdentifier());
// if there is a component specific policy we want to clone it for the new component
if (accessPolicy != null) {
final AccessPolicyDTO cloneAccessPolicy = new AccessPolicyDTO();
cloneAccessPolicy.setId(generateId(accessPolicy.getIdentifier(), idGenerationSeed, true));
cloneAccessPolicy.setAction(accessPolicy.getAction().toString());
cloneAccessPolicy.setResource(cloneResource.getIdentifier());
final Set<TenantEntity> users = new HashSet<>();
accessPolicy.getUsers().forEach(userId -> {
final TenantEntity entity = new TenantEntity();
entity.setId(userId);
users.add(entity);
});
cloneAccessPolicy.setUsers(users);
final Set<TenantEntity> groups = new HashSet<>();
accessPolicy.getGroups().forEach(groupId -> {
final TenantEntity entity = new TenantEntity();
entity.setId(groupId);
groups.add(entity);
});
cloneAccessPolicy.setUserGroups(groups);
// create the access policy for the cloned policy
accessPolicyDAO.createAccessPolicy(cloneAccessPolicy);
}
}
}
}
/**
* Attempts to roll back and in the specified snippet.
*
* @param snippet snippet
*/
public void rollbackClonedPolicies(final FlowSnippetDTO snippet) {
snippet.getControllerServices().forEach(controllerServiceDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.ControllerService, controllerServiceDTO.getId(), controllerServiceDTO.getName()));
});
snippet.getFunnels().forEach(funnelDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.Funnel, funnelDTO.getId(), funnelDTO.getId()));
});
snippet.getInputPorts().forEach(inputPortDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.InputPort, inputPortDTO.getId(), inputPortDTO.getName()));
});
snippet.getLabels().forEach(labelDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.Label, labelDTO.getId(), labelDTO.getLabel()));
});
snippet.getOutputPorts().forEach(outputPortDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.OutputPort, outputPortDTO.getId(), outputPortDTO.getName()));
});
snippet.getProcessors().forEach(processorDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.Processor, processorDTO.getId(), processorDTO.getName()));
});
snippet.getRemoteProcessGroups().forEach(remoteProcessGroupDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.RemoteProcessGroup, remoteProcessGroupDTO.getId(), remoteProcessGroupDTO.getName()));
});
snippet.getProcessGroups().forEach(processGroupDTO -> {
rollbackClonedPolicy(ResourceFactory.getComponentResource(ResourceType.ProcessGroup, processGroupDTO.getId(), processGroupDTO.getName()));
// consider all descendant components
if (processGroupDTO.getContents() != null) {
rollbackClonedPolicies(processGroupDTO.getContents());
}
});
}
/**
* 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.
*
* @param componentResource component resource
*/
private void rollbackClonedPolicy(final Resource componentResource) {
final List<Resource> 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()) {
final AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(action, resource.getIdentifier());
if (accessPolicy != null) {
try {
accessPolicyDAO.deleteAccessPolicy(accessPolicy.getIdentifier());
} catch (final Exception e) {
logger.warn(String.format("Unable to clean up cloned access policy for %s %s after failed copy/paste action.", action, componentResource.getIdentifier()), e);
}
}
}
}
}
private void updateControllerServiceIdentifiers(final FlowSnippetDTO snippet, final Map<String, String> serviceIdMap) {
@ -585,6 +779,10 @@ public final class SnippetUtils {
this.flowController = flowController;
}
public void setAccessPolicyDAO(AccessPolicyDAO accessPolicyDAO) {
this.accessPolicyDAO = accessPolicyDAO;
}
/**
* Will normalize the coordinates of the processors to ensure their
* consistency across exports. It will do so by fist calculating the

View File

@ -58,6 +58,7 @@
<bean id="snippetUtils" class="org.apache.nifi.web.util.SnippetUtils">
<property name="dtoFactory" ref="dtoFactory"/>
<property name="flowController" ref="flowController"/>
<property name="accessPolicyDAO" ref="policyBasedAuthorizerDAO"/>
</bean>
<!-- nifi component dao initialization -->