mirror of
https://github.com/apache/nifi.git
synced 2025-02-17 15:36:36 +00:00
NIFI-3135: - Authorizing restricted components on snippet usage.
- Updating REST API docs accordingly. - Adding some integration tests to exercise the additional restricted component checks. This closes #1287. Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
parent
69b23adf1b
commit
d8d29811f5
@ -17,7 +17,6 @@
|
|||||||
package org.apache.nifi.authorization;
|
package org.apache.nifi.authorization;
|
||||||
|
|
||||||
import org.apache.nifi.authorization.resource.Authorizable;
|
import org.apache.nifi.authorization.resource.Authorizable;
|
||||||
import org.apache.nifi.controller.Snippet;
|
|
||||||
|
|
||||||
public interface AuthorizableLookup {
|
public interface AuthorizableLookup {
|
||||||
|
|
||||||
@ -201,7 +200,7 @@ public interface AuthorizableLookup {
|
|||||||
* @param id template id
|
* @param id template id
|
||||||
* @return authorizable
|
* @return authorizable
|
||||||
*/
|
*/
|
||||||
Authorizable getTemplate(String id);
|
TemplateAuthorizable getTemplate(String id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the authorizable connectable.
|
* Get the authorizable connectable.
|
||||||
@ -217,7 +216,7 @@ public interface AuthorizableLookup {
|
|||||||
* @param id snippet id
|
* @param id snippet id
|
||||||
* @return snippet of authorizable's
|
* @return snippet of authorizable's
|
||||||
*/
|
*/
|
||||||
Snippet getSnippet(String id);
|
SnippetAuthorizable getSnippet(String id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the {@link Authorizable} that represents the resource of users and user groups.
|
* Get the {@link Authorizable} that represents the resource of users and user groups.
|
||||||
|
@ -43,7 +43,7 @@ public interface ProcessGroupAuthorizable {
|
|||||||
*
|
*
|
||||||
* @return all encapsulated connections
|
* @return all encapsulated connections
|
||||||
*/
|
*/
|
||||||
Set<Authorizable> getEncapsulatedConnections();
|
Set<ConnectionAuthorizable> getEncapsulatedConnections();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The authorizables for all encapsulated input ports. Non null
|
* The authorizables for all encapsulated input ports. Non null
|
||||||
@ -78,7 +78,7 @@ public interface ProcessGroupAuthorizable {
|
|||||||
*
|
*
|
||||||
* @return all encapsulated process groups
|
* @return all encapsulated process groups
|
||||||
*/
|
*/
|
||||||
Set<Authorizable> getEncapsulatedProcessGroups();
|
Set<ProcessGroupAuthorizable> getEncapsulatedProcessGroups();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The authorizables for all encapsulated remote process groups. Non null
|
* The authorizables for all encapsulated remote process groups. Non null
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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 Snippet.
|
||||||
|
*/
|
||||||
|
public interface SnippetAuthorizable {
|
||||||
|
/**
|
||||||
|
* The authorizables for selected processors. Non null
|
||||||
|
*
|
||||||
|
* @return processors
|
||||||
|
*/
|
||||||
|
Set<ConfigurableComponentAuthorizable> getSelectedProcessors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected connections. Non null
|
||||||
|
*
|
||||||
|
* @return connections
|
||||||
|
*/
|
||||||
|
Set<ConnectionAuthorizable> getSelectedConnections();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected input ports. Non null
|
||||||
|
*
|
||||||
|
* @return input ports
|
||||||
|
*/
|
||||||
|
Set<Authorizable> getSelectedInputPorts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected output ports. Non null
|
||||||
|
*
|
||||||
|
* @return output ports
|
||||||
|
*/
|
||||||
|
Set<Authorizable> getSelectedOutputPorts();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected funnels. Non null
|
||||||
|
*
|
||||||
|
* @return funnels
|
||||||
|
*/
|
||||||
|
Set<Authorizable> getSelectedFunnels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected labels. Non null
|
||||||
|
*
|
||||||
|
* @return labels
|
||||||
|
*/
|
||||||
|
Set<Authorizable> getSelectedLabels();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected process groups. Non null
|
||||||
|
*
|
||||||
|
* @return process groups
|
||||||
|
*/
|
||||||
|
Set<ProcessGroupAuthorizable> getSelectedProcessGroups();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The authorizables for selected remote process groups. Non null
|
||||||
|
*
|
||||||
|
* @return remote process groups
|
||||||
|
*/
|
||||||
|
Set<Authorizable> getSelectedRemoteProcessGroups();
|
||||||
|
}
|
@ -34,6 +34,7 @@ import org.apache.nifi.controller.ConfiguredComponent;
|
|||||||
import org.apache.nifi.controller.ProcessorNode;
|
import org.apache.nifi.controller.ProcessorNode;
|
||||||
import org.apache.nifi.controller.ReportingTaskNode;
|
import org.apache.nifi.controller.ReportingTaskNode;
|
||||||
import org.apache.nifi.controller.Snippet;
|
import org.apache.nifi.controller.Snippet;
|
||||||
|
import org.apache.nifi.controller.Template;
|
||||||
import org.apache.nifi.controller.service.ControllerServiceNode;
|
import org.apache.nifi.controller.service.ControllerServiceNode;
|
||||||
import org.apache.nifi.controller.service.ControllerServiceReference;
|
import org.apache.nifi.controller.service.ControllerServiceReference;
|
||||||
import org.apache.nifi.groups.ProcessGroup;
|
import org.apache.nifi.groups.ProcessGroup;
|
||||||
@ -41,6 +42,8 @@ import org.apache.nifi.groups.RemoteProcessGroup;
|
|||||||
import org.apache.nifi.remote.PortAuthorizationResult;
|
import org.apache.nifi.remote.PortAuthorizationResult;
|
||||||
import org.apache.nifi.remote.RootGroupPort;
|
import org.apache.nifi.remote.RootGroupPort;
|
||||||
import org.apache.nifi.web.ResourceNotFoundException;
|
import org.apache.nifi.web.ResourceNotFoundException;
|
||||||
|
import org.apache.nifi.web.api.dto.FlowSnippetDTO;
|
||||||
|
import org.apache.nifi.web.api.dto.TemplateDTO;
|
||||||
import org.apache.nifi.web.controller.ControllerFacade;
|
import org.apache.nifi.web.controller.ControllerFacade;
|
||||||
import org.apache.nifi.web.dao.AccessPolicyDAO;
|
import org.apache.nifi.web.dao.AccessPolicyDAO;
|
||||||
import org.apache.nifi.web.dao.ConnectionDAO;
|
import org.apache.nifi.web.dao.ConnectionDAO;
|
||||||
@ -55,6 +58,7 @@ import org.apache.nifi.web.dao.ReportingTaskDAO;
|
|||||||
import org.apache.nifi.web.dao.SnippetDAO;
|
import org.apache.nifi.web.dao.SnippetDAO;
|
||||||
import org.apache.nifi.web.dao.TemplateDAO;
|
import org.apache.nifi.web.dao.TemplateDAO;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@ -221,91 +225,13 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
@Override
|
@Override
|
||||||
public ConnectionAuthorizable getConnection(final String id) {
|
public ConnectionAuthorizable getConnection(final String id) {
|
||||||
final Connection connection = connectionDAO.getConnection(id);
|
final Connection connection = connectionDAO.getConnection(id);
|
||||||
return new ConnectionAuthorizable() {
|
return new StandardConnectionAuthorizable(connection);
|
||||||
@Override
|
|
||||||
public Authorizable getAuthorizable() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Connectable getSource() {
|
|
||||||
return connection.getSource();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Connectable getDestination() {
|
|
||||||
return connection.getDestination();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ProcessGroup getParentGroup() {
|
|
||||||
return connection.getProcessGroup();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ProcessGroupAuthorizable getProcessGroup(final String id) {
|
public ProcessGroupAuthorizable getProcessGroup(final String id) {
|
||||||
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(id);
|
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(id);
|
||||||
|
return new StandardProcessGroupAuthorizable(processGroup);
|
||||||
return new ProcessGroupAuthorizable() {
|
|
||||||
@Override
|
|
||||||
public Authorizable getAuthorizable() {
|
|
||||||
return processGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<ConfigurableComponentAuthorizable> getEncapsulatedProcessors() {
|
|
||||||
return processGroup.findAllProcessors().stream().map(
|
|
||||||
processorNode -> new ProcessorConfigurableComponentAuthorizable(processorNode)).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedConnections() {
|
|
||||||
return processGroup.findAllConnections().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedInputPorts() {
|
|
||||||
return processGroup.findAllInputPorts().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedOutputPorts() {
|
|
||||||
return processGroup.findAllOutputPorts().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedFunnels() {
|
|
||||||
return processGroup.findAllFunnels().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedLabels() {
|
|
||||||
return processGroup.findAllLabels().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedProcessGroups() {
|
|
||||||
return processGroup.findAllProcessGroups().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedRemoteProcessGroups() {
|
|
||||||
return processGroup.findAllRemoteProcessGroups().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<Authorizable> getEncapsulatedTemplates() {
|
|
||||||
return processGroup.findAllTemplates().stream().collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<ConfigurableComponentAuthorizable> getEncapsulatedControllerServices() {
|
|
||||||
return processGroup.findAllControllerServices().stream().map(
|
|
||||||
controllerServiceNode -> new ControllerServiceConfigurableComponentAuthorizable(controllerServiceNode)).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -411,8 +337,75 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Snippet getSnippet(final String id) {
|
public SnippetAuthorizable getSnippet(final String id) {
|
||||||
return snippetDAO.getSnippet(id);
|
final Snippet snippet = snippetDAO.getSnippet(id);
|
||||||
|
final ProcessGroup processGroup = processGroupDAO.getProcessGroup(snippet.getParentGroupId());
|
||||||
|
|
||||||
|
return new SnippetAuthorizable() {
|
||||||
|
@Override
|
||||||
|
public Set<ConfigurableComponentAuthorizable> getSelectedProcessors() {
|
||||||
|
return processGroup.getProcessors().stream()
|
||||||
|
.filter(processor -> snippet.getProcessors().containsKey(processor.getIdentifier()))
|
||||||
|
.map(processor -> getProcessor(processor.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConnectionAuthorizable> getSelectedConnections() {
|
||||||
|
return processGroup.getConnections().stream()
|
||||||
|
.filter(connection -> snippet.getConnections().containsKey(connection.getIdentifier()))
|
||||||
|
.map(connection -> getConnection(connection.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getSelectedInputPorts() {
|
||||||
|
return processGroup.getInputPorts().stream()
|
||||||
|
.filter(inputPort -> snippet.getInputPorts().containsKey(inputPort.getIdentifier()))
|
||||||
|
.map(inputPort -> getInputPort(inputPort.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getSelectedOutputPorts() {
|
||||||
|
return processGroup.getOutputPorts().stream()
|
||||||
|
.filter(outputPort -> snippet.getOutputPorts().containsKey(outputPort.getIdentifier()))
|
||||||
|
.map(outputPort -> getOutputPort(outputPort.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getSelectedFunnels() {
|
||||||
|
return processGroup.getFunnels().stream()
|
||||||
|
.filter(funnel -> snippet.getFunnels().containsKey(funnel.getIdentifier()))
|
||||||
|
.map(funnel -> getFunnel(funnel.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getSelectedLabels() {
|
||||||
|
return processGroup.getLabels().stream()
|
||||||
|
.filter(label -> snippet.getLabels().containsKey(label.getIdentifier()))
|
||||||
|
.map(label -> getLabel(label.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ProcessGroupAuthorizable> getSelectedProcessGroups() {
|
||||||
|
return processGroup.getProcessGroups().stream()
|
||||||
|
.filter(processGroup -> snippet.getProcessGroups().containsKey(processGroup.getIdentifier()))
|
||||||
|
.map(processGroup -> getProcessGroup(processGroup.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getSelectedRemoteProcessGroups() {
|
||||||
|
return processGroup.getRemoteProcessGroups().stream()
|
||||||
|
.filter(remoteProcessGroup -> snippet.getRemoteProcessGroups().containsKey(remoteProcessGroup.getIdentifier()))
|
||||||
|
.map(remoteProcessGroup -> getRemoteProcessGroup(remoteProcessGroup.getIdentifier()))
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -530,7 +523,7 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
authorizable = getReportingTask(componentId).getAuthorizable();
|
authorizable = getReportingTask(componentId).getAuthorizable();
|
||||||
break;
|
break;
|
||||||
case Template:
|
case Template:
|
||||||
authorizable = getTemplate(componentId);
|
authorizable = getTemplate(componentId).getAuthorizable();
|
||||||
break;
|
break;
|
||||||
case Data:
|
case Data:
|
||||||
authorizable = controllerFacade.getDataAuthorizable(componentId);
|
authorizable = controllerFacade.getDataAuthorizable(componentId);
|
||||||
@ -629,9 +622,62 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
return authorizable;
|
return authorizable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates temporary instances of all processors and controller services found in the specified snippet.
|
||||||
|
*
|
||||||
|
* @param snippet snippet
|
||||||
|
* @param processors processors
|
||||||
|
* @param controllerServices controller services
|
||||||
|
*/
|
||||||
|
private void createTemporaryProcessorsAndControllerServices(final FlowSnippetDTO snippet,
|
||||||
|
final Set<ConfigurableComponentAuthorizable> processors,
|
||||||
|
final Set<ConfigurableComponentAuthorizable> controllerServices) {
|
||||||
|
|
||||||
|
if (snippet == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snippet.getProcessors() != null) {
|
||||||
|
processors.addAll(snippet.getProcessors().stream().map(processor -> getProcessorByType(processor.getType())).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snippet.getControllerServices() != null) {
|
||||||
|
controllerServices.addAll(snippet.getControllerServices().stream().map(controllerService -> getControllerServiceByType(controllerService.getType())).collect(Collectors.toSet()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snippet.getProcessGroups() != null) {
|
||||||
|
snippet.getProcessGroups().stream().forEach(group -> createTemporaryProcessorsAndControllerServices(group.getContents(), processors, controllerServices));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authorizable getTemplate(final String id) {
|
public TemplateAuthorizable getTemplate(final String id) {
|
||||||
return templateDAO.getTemplate(id);
|
final Template template = templateDAO.getTemplate(id);
|
||||||
|
final TemplateDTO contents = template.getDetails();
|
||||||
|
|
||||||
|
// templates are immutable so we can pre-compute all encapsulated processors and controller services
|
||||||
|
final Set<ConfigurableComponentAuthorizable> processors = new HashSet<>();
|
||||||
|
final Set<ConfigurableComponentAuthorizable> controllerServices = new HashSet<>();
|
||||||
|
|
||||||
|
// find all processors and controller services
|
||||||
|
createTemporaryProcessorsAndControllerServices(contents.getSnippet(), processors, controllerServices);
|
||||||
|
|
||||||
|
return new TemplateAuthorizable() {
|
||||||
|
@Override
|
||||||
|
public Authorizable getAuthorizable() {
|
||||||
|
return template;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConfigurableComponentAuthorizable> getEncapsulatedProcessors() {
|
||||||
|
return processors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConfigurableComponentAuthorizable> getEncapsulatedControllerServices() {
|
||||||
|
return controllerServices;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -645,6 +691,11 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
return RESTRICTED_COMPONENTS_AUTHORIZABLE;
|
return RESTRICTED_COMPONENTS_AUTHORIZABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authorizable getSystem() {
|
||||||
|
return SYSTEM_AUTHORIZABLE;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigurableComponentAuthorizable for a ProcessorNode.
|
* ConfigurableComponentAuthorizable for a ProcessorNode.
|
||||||
*/
|
*/
|
||||||
@ -753,9 +804,99 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private static class StandardProcessGroupAuthorizable implements ProcessGroupAuthorizable {
|
||||||
public Authorizable getSystem() {
|
private final ProcessGroup processGroup;
|
||||||
return SYSTEM_AUTHORIZABLE;
|
|
||||||
|
public StandardProcessGroupAuthorizable(ProcessGroup processGroup) {
|
||||||
|
this.processGroup = processGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authorizable getAuthorizable() {
|
||||||
|
return processGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConfigurableComponentAuthorizable> getEncapsulatedProcessors() {
|
||||||
|
return processGroup.findAllProcessors().stream().map(
|
||||||
|
processorNode -> new ProcessorConfigurableComponentAuthorizable(processorNode)).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConnectionAuthorizable> getEncapsulatedConnections() {
|
||||||
|
return processGroup.findAllConnections().stream().map(
|
||||||
|
connection -> new StandardConnectionAuthorizable(connection)).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedInputPorts() {
|
||||||
|
return processGroup.findAllInputPorts().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedOutputPorts() {
|
||||||
|
return processGroup.findAllOutputPorts().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedFunnels() {
|
||||||
|
return processGroup.findAllFunnels().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedLabels() {
|
||||||
|
return processGroup.findAllLabels().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ProcessGroupAuthorizable> getEncapsulatedProcessGroups() {
|
||||||
|
return processGroup.findAllProcessGroups().stream().map(
|
||||||
|
group -> new StandardProcessGroupAuthorizable(group)).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedRemoteProcessGroups() {
|
||||||
|
return processGroup.findAllRemoteProcessGroups().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Authorizable> getEncapsulatedTemplates() {
|
||||||
|
return processGroup.findAllTemplates().stream().collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ConfigurableComponentAuthorizable> getEncapsulatedControllerServices() {
|
||||||
|
return processGroup.findAllControllerServices().stream().map(
|
||||||
|
controllerServiceNode -> new ControllerServiceConfigurableComponentAuthorizable(controllerServiceNode)).collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StandardConnectionAuthorizable implements ConnectionAuthorizable {
|
||||||
|
private final Connection connection;
|
||||||
|
|
||||||
|
public StandardConnectionAuthorizable(Connection connection) {
|
||||||
|
this.connection = connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Authorizable getAuthorizable() {
|
||||||
|
return connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connectable getSource() {
|
||||||
|
return connection.getSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Connectable getDestination() {
|
||||||
|
return connection.getDestination();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ProcessGroup getParentGroup() {
|
||||||
|
return connection.getProcessGroup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setProcessorDAO(ProcessorDAO processorDAO) {
|
public void setProcessorDAO(ProcessorDAO processorDAO) {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
import org.apache.nifi.authorization.resource.Authorizable;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorizable for a Template.
|
||||||
|
*/
|
||||||
|
public interface TemplateAuthorizable {
|
||||||
|
/**
|
||||||
|
* Returns the authorizable for this template. Non null
|
||||||
|
*
|
||||||
|
* @return authorizable
|
||||||
|
*/
|
||||||
|
Authorizable getAuthorizable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns temporary instances of all encapsulated processors. Non null
|
||||||
|
*
|
||||||
|
* @return temporary instances of all encapsulated processors
|
||||||
|
*/
|
||||||
|
Set<ConfigurableComponentAuthorizable> getEncapsulatedProcessors();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns temporary instances of all encapsulated controller services. Non null
|
||||||
|
*
|
||||||
|
* @return temporary instances of all encapsulated controller services
|
||||||
|
*/
|
||||||
|
Set<ConfigurableComponentAuthorizable> getEncapsulatedControllerServices();
|
||||||
|
|
||||||
|
}
|
@ -29,6 +29,7 @@ import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
|
|||||||
import org.apache.nifi.authorization.Authorizer;
|
import org.apache.nifi.authorization.Authorizer;
|
||||||
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
|
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
|
||||||
import org.apache.nifi.authorization.RequestAction;
|
import org.apache.nifi.authorization.RequestAction;
|
||||||
|
import org.apache.nifi.authorization.SnippetAuthorizable;
|
||||||
import org.apache.nifi.authorization.resource.Authorizable;
|
import org.apache.nifi.authorization.resource.Authorizable;
|
||||||
import org.apache.nifi.authorization.user.NiFiUser;
|
import org.apache.nifi.authorization.user.NiFiUser;
|
||||||
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
||||||
@ -40,7 +41,6 @@ import org.apache.nifi.cluster.manager.exception.IllegalClusterStateException;
|
|||||||
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
|
import org.apache.nifi.cluster.manager.exception.UnknownNodeException;
|
||||||
import org.apache.nifi.cluster.protocol.NodeIdentifier;
|
import org.apache.nifi.cluster.protocol.NodeIdentifier;
|
||||||
import org.apache.nifi.controller.FlowController;
|
import org.apache.nifi.controller.FlowController;
|
||||||
import org.apache.nifi.controller.Snippet;
|
|
||||||
import org.apache.nifi.remote.HttpRemoteSiteListener;
|
import org.apache.nifi.remote.HttpRemoteSiteListener;
|
||||||
import org.apache.nifi.remote.VersionNegotiator;
|
import org.apache.nifi.remote.VersionNegotiator;
|
||||||
import org.apache.nifi.remote.exception.BadRequestException;
|
import org.apache.nifi.remote.exception.BadRequestException;
|
||||||
@ -462,12 +462,12 @@ public abstract class ApplicationResource {
|
|||||||
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable, authorizer, lookup, authorizeTransitiveServices);
|
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable, authorizer, lookup, authorizeTransitiveServices);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
processGroupAuthorizable.getEncapsulatedConnections().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedConnections().stream().map(connection -> connection.getAuthorizable()).forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedInputPorts().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedInputPorts().forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedOutputPorts().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedOutputPorts().forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedFunnels().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedFunnels().forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedLabels().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedLabels().forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedProcessGroups().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedProcessGroups().stream().map(group -> group.getAuthorizable()).forEach(authorize);
|
||||||
processGroupAuthorizable.getEncapsulatedRemoteProcessGroups().forEach(authorize);
|
processGroupAuthorizable.getEncapsulatedRemoteProcessGroups().forEach(authorize);
|
||||||
|
|
||||||
// authorize templates if necessary
|
// authorize templates if necessary
|
||||||
@ -496,18 +496,19 @@ public abstract class ApplicationResource {
|
|||||||
* @param lookup lookup
|
* @param lookup lookup
|
||||||
* @param action action
|
* @param action action
|
||||||
*/
|
*/
|
||||||
protected void authorizeSnippet(final Snippet snippet, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action,
|
protected void authorizeSnippet(final SnippetAuthorizable snippet, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action,
|
||||||
final boolean authorizeReferencedServices, final boolean authorizeTransitiveServices) {
|
final boolean authorizeReferencedServices, final boolean authorizeTransitiveServices) {
|
||||||
|
|
||||||
final Consumer<Authorizable> authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser());
|
final Consumer<Authorizable> authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser());
|
||||||
|
|
||||||
// authorize each component in the specified snippet
|
// authorize each component in the specified snippet
|
||||||
snippet.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(processGroupAuthorizable -> {
|
snippet.getSelectedProcessGroups().stream().forEach(processGroupAuthorizable -> {
|
||||||
// note - we are not authorizing templates or controller services as they are not considered when using this snippet. however,
|
// note - we are not authorizing templates or controller services as they are not considered when using this snippet. however,
|
||||||
// referenced services are considered so those are explicitly authorized when authorizing a processor
|
// referenced services are considered so those are explicitly authorized when authorizing a processor
|
||||||
authorizeProcessGroup(processGroupAuthorizable, authorizer, lookup, action, authorizeReferencedServices, false, false, authorizeTransitiveServices);
|
authorizeProcessGroup(processGroupAuthorizable, authorizer, lookup, action, authorizeReferencedServices, false, false, authorizeTransitiveServices);
|
||||||
});
|
});
|
||||||
snippet.getRemoteProcessGroups().keySet().stream().map(id -> lookup.getRemoteProcessGroup(id)).forEach(authorize);
|
snippet.getSelectedRemoteProcessGroups().stream().forEach(authorize);
|
||||||
snippet.getProcessors().keySet().stream().map(id -> lookup.getProcessor(id)).forEach(processorAuthorizable -> {
|
snippet.getSelectedProcessors().stream().forEach(processorAuthorizable -> {
|
||||||
// authorize the processor
|
// authorize the processor
|
||||||
authorize.accept(processorAuthorizable.getAuthorizable());
|
authorize.accept(processorAuthorizable.getAuthorizable());
|
||||||
|
|
||||||
@ -516,10 +517,11 @@ public abstract class ApplicationResource {
|
|||||||
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable, authorizer, lookup, authorizeTransitiveServices);
|
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable, authorizer, lookup, authorizeTransitiveServices);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
snippet.getInputPorts().keySet().stream().map(id -> lookup.getInputPort(id)).forEach(authorize);
|
snippet.getSelectedInputPorts().stream().forEach(authorize);
|
||||||
snippet.getOutputPorts().keySet().stream().map(id -> lookup.getOutputPort(id)).forEach(authorize);
|
snippet.getSelectedOutputPorts().stream().forEach(authorize);
|
||||||
snippet.getConnections().keySet().stream().map(id -> lookup.getConnection(id)).forEach(connAuth -> authorize.accept(connAuth.getAuthorizable()));
|
snippet.getSelectedConnections().stream().forEach(connAuth -> authorize.accept(connAuth.getAuthorizable()));
|
||||||
snippet.getFunnels().keySet().stream().map(id -> lookup.getFunnel(id)).forEach(authorize);
|
snippet.getSelectedFunnels().stream().forEach(authorize);
|
||||||
|
snippet.getSelectedLabels().stream().forEach(authorize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,7 +238,8 @@ public class ControllerResource extends ApplicationResource {
|
|||||||
response = ReportingTaskEntity.class,
|
response = ReportingTaskEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /controller", type = ""),
|
@Authorization(value = "Write - /controller", type = ""),
|
||||||
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = "")
|
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
|
||||||
|
@Authorization(value = "Write - if the Reporting Task is restricted - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
@ -330,7 +331,8 @@ public class ControllerResource extends ApplicationResource {
|
|||||||
response = ControllerServiceEntity.class,
|
response = ControllerServiceEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /controller", type = ""),
|
@Authorization(value = "Write - /controller", type = ""),
|
||||||
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = "")
|
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
|
||||||
|
@Authorization(value = "Write - if the Controller Service is restricted - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
|
@ -31,10 +31,11 @@ import org.apache.nifi.authorization.Authorizer;
|
|||||||
import org.apache.nifi.authorization.ConfigurableComponentAuthorizable;
|
import org.apache.nifi.authorization.ConfigurableComponentAuthorizable;
|
||||||
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
|
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
|
||||||
import org.apache.nifi.authorization.RequestAction;
|
import org.apache.nifi.authorization.RequestAction;
|
||||||
|
import org.apache.nifi.authorization.SnippetAuthorizable;
|
||||||
|
import org.apache.nifi.authorization.TemplateAuthorizable;
|
||||||
import org.apache.nifi.authorization.resource.Authorizable;
|
import org.apache.nifi.authorization.resource.Authorizable;
|
||||||
import org.apache.nifi.authorization.user.NiFiUser;
|
import org.apache.nifi.authorization.user.NiFiUser;
|
||||||
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
||||||
import org.apache.nifi.controller.Snippet;
|
|
||||||
import org.apache.nifi.web.NiFiServiceFacade;
|
import org.apache.nifi.web.NiFiServiceFacade;
|
||||||
import org.apache.nifi.web.ResourceNotFoundException;
|
import org.apache.nifi.web.ResourceNotFoundException;
|
||||||
import org.apache.nifi.web.Revision;
|
import org.apache.nifi.web.Revision;
|
||||||
@ -100,6 +101,8 @@ import java.net.URISyntaxException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RESTful endpoint for managing a Group.
|
* RESTful endpoint for managing a Group.
|
||||||
@ -555,7 +558,8 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
response = ProcessorEntity.class,
|
response = ProcessorEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
||||||
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = "")
|
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
|
||||||
|
@Authorization(value = "Write - if the Processor is restricted - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
@ -1646,7 +1650,8 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
response = FlowSnippetEntity.class,
|
response = FlowSnippetEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
||||||
@Authorization(value = "Read - /{component-type}/{uuid} - For each component in the snippet and their descendant components", type = "")
|
@Authorization(value = "Read - /{component-type}/{uuid} - For each component in the snippet and their descendant components", type = ""),
|
||||||
|
@Authorization(value = "Write - if the snippet contains any restricted Processors - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
@ -1687,7 +1692,24 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
serviceFacade,
|
serviceFacade,
|
||||||
requestCopySnippetEntity,
|
requestCopySnippetEntity,
|
||||||
lookup -> {
|
lookup -> {
|
||||||
authorizeSnippetUsage(lookup, groupId, requestCopySnippetEntity.getSnippetId(), false);
|
final NiFiUser user = NiFiUserUtils.getNiFiUser();
|
||||||
|
final SnippetAuthorizable snippet = authorizeSnippetUsage(lookup, groupId, requestCopySnippetEntity.getSnippetId(), false);
|
||||||
|
|
||||||
|
// flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda
|
||||||
|
final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false);
|
||||||
|
final Consumer<ConfigurableComponentAuthorizable> authorizeRestricted = authorizable -> {
|
||||||
|
if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) {
|
||||||
|
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// consider each processor. note - this request will not create new controller services so we do not need to check
|
||||||
|
// for if there are not restricted controller services. it will however, need to authorize the user has access
|
||||||
|
// to any referenced services and this is done within authorizeSnippetUsage above.
|
||||||
|
snippet.getSelectedProcessors().stream().forEach(authorizeRestricted);
|
||||||
|
snippet.getSelectedProcessGroups().stream().forEach(processGroup -> {
|
||||||
|
processGroup.getEncapsulatedProcessors().forEach(authorizeRestricted);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
copySnippetRequestEntity -> {
|
copySnippetRequestEntity -> {
|
||||||
@ -1736,7 +1758,8 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
response = FlowEntity.class,
|
response = FlowEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
||||||
@Authorization(value = "Read - /templates/{uuid}", type = "")
|
@Authorization(value = "Read - /templates/{uuid}", type = ""),
|
||||||
|
@Authorization(value = "Write - if the template contains any restricted components - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
@ -1773,11 +1796,27 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
serviceFacade,
|
serviceFacade,
|
||||||
requestInstantiateTemplateRequestEntity,
|
requestInstantiateTemplateRequestEntity,
|
||||||
lookup -> {
|
lookup -> {
|
||||||
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
|
final NiFiUser user = NiFiUserUtils.getNiFiUser();
|
||||||
processGroup.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
|
|
||||||
|
|
||||||
final Authorizable template = lookup.getTemplate(requestInstantiateTemplateRequestEntity.getTemplateId());
|
// ensure write on the group
|
||||||
template.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
|
final Authorizable processGroup = lookup.getProcessGroup(groupId).getAuthorizable();
|
||||||
|
processGroup.authorize(authorizer, RequestAction.WRITE, user);
|
||||||
|
|
||||||
|
// ensure read on the template
|
||||||
|
final TemplateAuthorizable template = lookup.getTemplate(requestInstantiateTemplateRequestEntity.getTemplateId());
|
||||||
|
template.getAuthorizable().authorize(authorizer, RequestAction.READ, user);
|
||||||
|
|
||||||
|
// flag to only perform the restricted check once, atomic reference so we can mark final and use in lambda
|
||||||
|
final AtomicBoolean restrictedCheckPerformed = new AtomicBoolean(false);
|
||||||
|
final Consumer<ConfigurableComponentAuthorizable> authorizeRestricted = authorizable -> {
|
||||||
|
if (authorizable.isRestricted() && restrictedCheckPerformed.compareAndSet(false, true)) {
|
||||||
|
lookup.getRestrictedComponents().authorize(authorizer, RequestAction.WRITE, user);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ensure restricted access if necessary
|
||||||
|
template.getEncapsulatedProcessors().forEach(authorizeRestricted);
|
||||||
|
template.getEncapsulatedControllerServices().forEach(authorizeRestricted);
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
instantiateTemplateRequestEntity -> {
|
instantiateTemplateRequestEntity -> {
|
||||||
@ -1805,13 +1844,16 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
// templates
|
// templates
|
||||||
// ---------
|
// ---------
|
||||||
|
|
||||||
private void authorizeSnippetUsage(final AuthorizableLookup lookup, final String groupId, final String snippetId, final boolean authorizeTransitiveServices) {
|
private SnippetAuthorizable authorizeSnippetUsage(final AuthorizableLookup lookup, final String groupId, final String snippetId, final boolean authorizeTransitiveServices) {
|
||||||
|
final NiFiUser user = NiFiUserUtils.getNiFiUser();
|
||||||
|
|
||||||
// ensure write access to the target process group
|
// ensure write access to the target process group
|
||||||
lookup.getProcessGroup(groupId).getAuthorizable().authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
|
lookup.getProcessGroup(groupId).getAuthorizable().authorize(authorizer, RequestAction.WRITE, user);
|
||||||
|
|
||||||
// ensure read permission to every component in the snippet including referenced services
|
// ensure read permission to every component in the snippet including referenced services
|
||||||
final Snippet snippet = lookup.getSnippet(snippetId);
|
final SnippetAuthorizable snippet = lookup.getSnippet(snippetId);
|
||||||
authorizeSnippet(snippet, authorizer, lookup, RequestAction.READ, true, authorizeTransitiveServices);
|
authorizeSnippet(snippet, authorizer, lookup, RequestAction.READ, true, authorizeTransitiveServices);
|
||||||
|
return snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2075,7 +2117,8 @@ public class ProcessGroupResource extends ApplicationResource {
|
|||||||
response = ControllerServiceEntity.class,
|
response = ControllerServiceEntity.class,
|
||||||
authorizations = {
|
authorizations = {
|
||||||
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
@Authorization(value = "Write - /process-groups/{uuid}", type = ""),
|
||||||
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = "")
|
@Authorization(value = "Read - any referenced Controller Services - /controller-services/{uuid}", type = ""),
|
||||||
|
@Authorization(value = "Write - if the Controller Service is restricted - /restricted-components", type = "")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ApiResponses(
|
@ApiResponses(
|
||||||
|
@ -26,9 +26,9 @@ import org.apache.nifi.authorization.AccessDeniedException;
|
|||||||
import org.apache.nifi.authorization.AuthorizableLookup;
|
import org.apache.nifi.authorization.AuthorizableLookup;
|
||||||
import org.apache.nifi.authorization.Authorizer;
|
import org.apache.nifi.authorization.Authorizer;
|
||||||
import org.apache.nifi.authorization.RequestAction;
|
import org.apache.nifi.authorization.RequestAction;
|
||||||
|
import org.apache.nifi.authorization.SnippetAuthorizable;
|
||||||
import org.apache.nifi.authorization.resource.Authorizable;
|
import org.apache.nifi.authorization.resource.Authorizable;
|
||||||
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
import org.apache.nifi.authorization.user.NiFiUserUtils;
|
||||||
import org.apache.nifi.controller.Snippet;
|
|
||||||
import org.apache.nifi.web.NiFiServiceFacade;
|
import org.apache.nifi.web.NiFiServiceFacade;
|
||||||
import org.apache.nifi.web.Revision;
|
import org.apache.nifi.web.Revision;
|
||||||
import org.apache.nifi.web.api.dto.SnippetDTO;
|
import org.apache.nifi.web.api.dto.SnippetDTO;
|
||||||
@ -106,8 +106,8 @@ public class SnippetResource extends ApplicationResource {
|
|||||||
private void authorizeSnippetRequest(final SnippetDTO snippetRequest, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action) {
|
private void authorizeSnippetRequest(final SnippetDTO snippetRequest, final Authorizer authorizer, final AuthorizableLookup lookup, final RequestAction action) {
|
||||||
final Consumer<Authorizable> authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser());
|
final Consumer<Authorizable> authorize = authorizable -> authorizable.authorize(authorizer, action, NiFiUserUtils.getNiFiUser());
|
||||||
|
|
||||||
|
// note - we are not authorizing templates or controller services as they are not considered when using this snippet
|
||||||
snippetRequest.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(processGroupAuthorizable -> {
|
snippetRequest.getProcessGroups().keySet().stream().map(id -> lookup.getProcessGroup(id)).forEach(processGroupAuthorizable -> {
|
||||||
// note - we are not authorizing templates or controller services as they are not considered when using this snippet. additionally,
|
|
||||||
// we are not checking referenced services since we do not know how this snippet will be used. these checks should be performed
|
// we are not checking referenced services since we do not know how this snippet will be used. these checks should be performed
|
||||||
// in a subsequent action with this snippet
|
// in a subsequent action with this snippet
|
||||||
authorizeProcessGroup(processGroupAuthorizable, authorizer, lookup, action, false, false, false, false);
|
authorizeProcessGroup(processGroupAuthorizable, authorizer, lookup, action, false, false, false, false);
|
||||||
@ -116,8 +116,9 @@ public class SnippetResource extends ApplicationResource {
|
|||||||
snippetRequest.getProcessors().keySet().stream().map(id -> lookup.getProcessor(id).getAuthorizable()).forEach(authorize);
|
snippetRequest.getProcessors().keySet().stream().map(id -> lookup.getProcessor(id).getAuthorizable()).forEach(authorize);
|
||||||
snippetRequest.getInputPorts().keySet().stream().map(id -> lookup.getInputPort(id)).forEach(authorize);
|
snippetRequest.getInputPorts().keySet().stream().map(id -> lookup.getInputPort(id)).forEach(authorize);
|
||||||
snippetRequest.getOutputPorts().keySet().stream().map(id -> lookup.getOutputPort(id)).forEach(authorize);
|
snippetRequest.getOutputPorts().keySet().stream().map(id -> lookup.getOutputPort(id)).forEach(authorize);
|
||||||
snippetRequest.getConnections().keySet().stream().map(id -> lookup.getConnection(id)).forEach(connAuth -> authorize.accept(connAuth.getAuthorizable()));
|
snippetRequest.getConnections().keySet().stream().map(id -> lookup.getConnection(id).getAuthorizable()).forEach(authorize);
|
||||||
snippetRequest.getFunnels().keySet().stream().map(id -> lookup.getFunnel(id)).forEach(authorize);
|
snippetRequest.getFunnels().keySet().stream().map(id -> lookup.getFunnel(id)).forEach(authorize);
|
||||||
|
snippetRequest.getLabels().keySet().stream().map(id -> lookup.getLabel(id)).forEach(authorize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -162,6 +163,10 @@ public class SnippetResource extends ApplicationResource {
|
|||||||
throw new IllegalArgumentException("Snippet ID cannot be specified.");
|
throw new IllegalArgumentException("Snippet ID cannot be specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (requestSnippetEntity.getSnippet().getParentGroupId() == null) {
|
||||||
|
throw new IllegalArgumentException("The parent Process Group of the snippet must be specified.");
|
||||||
|
}
|
||||||
|
|
||||||
if (isReplicateRequest()) {
|
if (isReplicateRequest()) {
|
||||||
return replicate(HttpMethod.POST, requestSnippetEntity);
|
return replicate(HttpMethod.POST, requestSnippetEntity);
|
||||||
}
|
}
|
||||||
@ -268,7 +273,7 @@ public class SnippetResource extends ApplicationResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ensure write permission to every component in the snippet excluding referenced services
|
// ensure write permission to every component in the snippet excluding referenced services
|
||||||
final Snippet snippet = lookup.getSnippet(snippetId);
|
final SnippetAuthorizable snippet = lookup.getSnippet(snippetId);
|
||||||
authorizeSnippet(snippet, authorizer, lookup, RequestAction.WRITE, false, false);
|
authorizeSnippet(snippet, authorizer, lookup, RequestAction.WRITE, false, false);
|
||||||
},
|
},
|
||||||
() -> serviceFacade.verifyUpdateSnippet(requestSnippetDTO, requestRevisions.stream().map(rev -> rev.getComponentId()).collect(Collectors.toSet())),
|
() -> serviceFacade.verifyUpdateSnippet(requestSnippetDTO, requestRevisions.stream().map(rev -> rev.getComponentId()).collect(Collectors.toSet())),
|
||||||
@ -331,7 +336,7 @@ public class SnippetResource extends ApplicationResource {
|
|||||||
requestRevisions,
|
requestRevisions,
|
||||||
lookup -> {
|
lookup -> {
|
||||||
// ensure write permission to every component in the snippet excluding referenced services
|
// ensure write permission to every component in the snippet excluding referenced services
|
||||||
final Snippet snippet = lookup.getSnippet(snippetId);
|
final SnippetAuthorizable snippet = lookup.getSnippet(snippetId);
|
||||||
authorizeSnippet(snippet, authorizer, lookup, RequestAction.WRITE, true, false);
|
authorizeSnippet(snippet, authorizer, lookup, RequestAction.WRITE, true, false);
|
||||||
},
|
},
|
||||||
() -> serviceFacade.verifyDeleteSnippet(snippetId, requestRevisions.stream().map(rev -> rev.getComponentId()).collect(Collectors.toSet())),
|
() -> serviceFacade.verifyDeleteSnippet(snippetId, requestRevisions.stream().map(rev -> rev.getComponentId()).collect(Collectors.toSet())),
|
||||||
|
@ -122,7 +122,7 @@ public class TemplateResource extends ApplicationResource {
|
|||||||
|
|
||||||
// authorize access
|
// authorize access
|
||||||
serviceFacade.authorizeAccess(lookup -> {
|
serviceFacade.authorizeAccess(lookup -> {
|
||||||
final Authorizable template = lookup.getTemplate(id);
|
final Authorizable template = lookup.getTemplate(id).getAuthorizable();
|
||||||
template.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
|
template.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -196,7 +196,7 @@ public class TemplateResource extends ApplicationResource {
|
|||||||
serviceFacade,
|
serviceFacade,
|
||||||
requestTemplateEntity,
|
requestTemplateEntity,
|
||||||
lookup -> {
|
lookup -> {
|
||||||
final Authorizable template = lookup.getTemplate(id);
|
final Authorizable template = lookup.getTemplate(id).getAuthorizable();
|
||||||
template.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
|
template.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
@ -21,12 +21,21 @@ import org.apache.nifi.integration.util.NiFiTestAuthorizer;
|
|||||||
import org.apache.nifi.integration.util.NiFiTestUser;
|
import org.apache.nifi.integration.util.NiFiTestUser;
|
||||||
import org.apache.nifi.integration.util.RestrictedProcessor;
|
import org.apache.nifi.integration.util.RestrictedProcessor;
|
||||||
import org.apache.nifi.integration.util.SourceTestProcessor;
|
import org.apache.nifi.integration.util.SourceTestProcessor;
|
||||||
|
import org.apache.nifi.util.Tuple;
|
||||||
import org.apache.nifi.web.api.dto.ProcessorDTO;
|
import org.apache.nifi.web.api.dto.ProcessorDTO;
|
||||||
import org.apache.nifi.web.api.dto.RevisionDTO;
|
import org.apache.nifi.web.api.dto.RevisionDTO;
|
||||||
|
import org.apache.nifi.web.api.dto.SnippetDTO;
|
||||||
import org.apache.nifi.web.api.dto.flow.FlowDTO;
|
import org.apache.nifi.web.api.dto.flow.FlowDTO;
|
||||||
|
import org.apache.nifi.web.api.entity.CopySnippetRequestEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.CreateTemplateRequestEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.FlowEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.InstantiateTemplateRequestEntity;
|
||||||
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
|
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
|
||||||
import org.apache.nifi.web.api.entity.ProcessorEntity;
|
import org.apache.nifi.web.api.entity.ProcessorEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.SnippetEntity;
|
||||||
|
import org.apache.nifi.web.api.entity.TemplateEntity;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
@ -435,6 +444,187 @@ public class ITProcessorAccessControl {
|
|||||||
|
|
||||||
// ensure the request is successful
|
// ensure the request is successful
|
||||||
assertEquals(201, response.getStatus());
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
final ProcessorEntity responseEntity = response.getEntity(ProcessorEntity.class);
|
||||||
|
|
||||||
|
// remove the restricted component
|
||||||
|
deleteRestrictedComponent(responseEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests attempting to copy/paste a restricted processor.
|
||||||
|
*
|
||||||
|
* @throws Exception ex
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCopyPasteRestrictedProcessor() throws Exception {
|
||||||
|
final String copyUrl = helper.getBaseUrl() + "/process-groups/root/snippet-instance";
|
||||||
|
final Tuple<ProcessorEntity, SnippetEntity> tuple = createSnippetWithRestrictedComponent();
|
||||||
|
final SnippetEntity snippetEntity = tuple.getValue();
|
||||||
|
|
||||||
|
// build the copy/paste request
|
||||||
|
final CopySnippetRequestEntity copyRequest = new CopySnippetRequestEntity();
|
||||||
|
copyRequest.setSnippetId(snippetEntity.getSnippet().getId());
|
||||||
|
copyRequest.setOriginX(0.0);
|
||||||
|
copyRequest.setOriginY(0.0);
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
ClientResponse response = helper.getReadWriteUser().testPost(copyUrl, copyRequest);
|
||||||
|
|
||||||
|
// ensure the request failed... need privileged users since snippet comprised of the restricted components
|
||||||
|
assertEquals(403, response.getStatus());
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
response = helper.getPrivilegedUser().testPost(copyUrl, copyRequest);
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
final FlowEntity flowEntity = response.getEntity(FlowEntity.class);
|
||||||
|
|
||||||
|
// remove the restricted processors
|
||||||
|
deleteRestrictedComponent(tuple.getKey());
|
||||||
|
deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests attempting to use a template with a restricted processor.
|
||||||
|
*
|
||||||
|
* @throws Exception ex
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTemplateWithRestrictedProcessor() throws Exception {
|
||||||
|
final String createTemplateUrl = helper.getBaseUrl() + "/process-groups/root/templates";
|
||||||
|
final String instantiateTemplateUrl = helper.getBaseUrl() + "/process-groups/root/template-instance";
|
||||||
|
final Tuple<ProcessorEntity, SnippetEntity> tuple = createSnippetWithRestrictedComponent();
|
||||||
|
final SnippetEntity snippetEntity = tuple.getValue();
|
||||||
|
|
||||||
|
// create the template
|
||||||
|
final CreateTemplateRequestEntity createTemplateRequest = new CreateTemplateRequestEntity();
|
||||||
|
createTemplateRequest.setSnippetId(snippetEntity.getSnippet().getId());
|
||||||
|
createTemplateRequest.setName("test");
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
ClientResponse response = helper.getWriteUser().testPost(createTemplateUrl, createTemplateRequest);
|
||||||
|
|
||||||
|
// ensure the request failed... need read perms to the components in the snippet
|
||||||
|
assertEquals(403, response.getStatus());
|
||||||
|
|
||||||
|
response = helper.getReadWriteUser().testPost(createTemplateUrl, createTemplateRequest);
|
||||||
|
|
||||||
|
// ensure the request is successfull
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
final TemplateEntity templateEntity = response.getEntity(TemplateEntity.class);
|
||||||
|
|
||||||
|
// build the template request
|
||||||
|
final InstantiateTemplateRequestEntity instantiateTemplateRequest = new InstantiateTemplateRequestEntity();
|
||||||
|
instantiateTemplateRequest.setTemplateId(templateEntity.getTemplate().getId());
|
||||||
|
instantiateTemplateRequest.setOriginX(0.0);
|
||||||
|
instantiateTemplateRequest.setOriginY(0.0);
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
response = helper.getReadWriteUser().testPost(instantiateTemplateUrl, instantiateTemplateRequest);
|
||||||
|
|
||||||
|
// ensure the request failed... need privileged user since the template is comprised of restricted components
|
||||||
|
assertEquals(403, response.getStatus());
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
response = helper.getPrivilegedUser().testPost(instantiateTemplateUrl, instantiateTemplateRequest);
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
final FlowEntity flowEntity = response.getEntity(FlowEntity.class);
|
||||||
|
|
||||||
|
// clean up the resources created during this test
|
||||||
|
deleteTemplate(templateEntity);
|
||||||
|
deleteRestrictedComponent(tuple.getKey());
|
||||||
|
deleteRestrictedComponent(flowEntity.getFlow().getProcessors().stream().findFirst().orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Tuple<ProcessorEntity, SnippetEntity> createSnippetWithRestrictedComponent() throws Exception {
|
||||||
|
final String processorUrl = helper.getBaseUrl() + "/process-groups/root/processors";
|
||||||
|
final String snippetUrl = helper.getBaseUrl() + "/snippets";
|
||||||
|
|
||||||
|
// create the processor
|
||||||
|
ProcessorDTO processor = new ProcessorDTO();
|
||||||
|
processor.setName("restricted");
|
||||||
|
processor.setType(RestrictedProcessor.class.getName());
|
||||||
|
|
||||||
|
// create the revision
|
||||||
|
final RevisionDTO revision = new RevisionDTO();
|
||||||
|
revision.setClientId(READ_WRITE_CLIENT_ID);
|
||||||
|
revision.setVersion(0L);
|
||||||
|
|
||||||
|
// create the entity body
|
||||||
|
ProcessorEntity entity = new ProcessorEntity();
|
||||||
|
entity.setRevision(revision);
|
||||||
|
entity.setComponent(processor);
|
||||||
|
|
||||||
|
// perform the request as a user with read/write and restricted access
|
||||||
|
ClientResponse response = helper.getPrivilegedUser().testPost(processorUrl, entity);
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
// get the response
|
||||||
|
final ProcessorEntity responseProcessorEntity = response.getEntity(ProcessorEntity.class);
|
||||||
|
|
||||||
|
// build the snippet for the copy/paste
|
||||||
|
final SnippetDTO snippet = new SnippetDTO();
|
||||||
|
snippet.setParentGroupId(responseProcessorEntity.getComponent().getParentGroupId());
|
||||||
|
snippet.getProcessors().put(responseProcessorEntity.getId(), responseProcessorEntity.getRevision());
|
||||||
|
|
||||||
|
// create the entity body
|
||||||
|
final SnippetEntity snippetEntity = new SnippetEntity();
|
||||||
|
snippetEntity.setSnippet(snippet);
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
response = helper.getNoneUser().testPost(snippetUrl, snippetEntity);
|
||||||
|
|
||||||
|
// ensure the request failed... need either read or write to create snippet (not sure what snippet will be used for)
|
||||||
|
assertEquals(403, response.getStatus());
|
||||||
|
|
||||||
|
// create the snippet
|
||||||
|
response = helper.getReadWriteUser().testPost(snippetUrl, snippetEntity);
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
|
||||||
|
// get the response
|
||||||
|
return new Tuple<>(responseProcessorEntity, response.getEntity(SnippetEntity.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteTemplate(final TemplateEntity entity) throws Exception {
|
||||||
|
// perform the request
|
||||||
|
ClientResponse response = helper.getReadWriteUser().testDelete(entity.getTemplate().getUri());
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteRestrictedComponent(final ProcessorEntity entity) throws Exception {
|
||||||
|
if (entity == null) {
|
||||||
|
Assert.fail("Failed to get Processor from template or snippet request.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the entity body
|
||||||
|
final Map<String, String> queryParams = new HashMap<>();
|
||||||
|
queryParams.put("version", String.valueOf(entity.getRevision().getVersion()));
|
||||||
|
queryParams.put("clientId", READ_WRITE_CLIENT_ID);
|
||||||
|
|
||||||
|
// perform the request
|
||||||
|
ClientResponse response = helper.getReadWriteUser().testDelete(entity.getUri(), queryParams);
|
||||||
|
|
||||||
|
// ensure the request fails... needs access to restricted components
|
||||||
|
assertEquals(403, response.getStatus());
|
||||||
|
|
||||||
|
response = helper.getPrivilegedUser().testDelete(entity.getUri(), queryParams);
|
||||||
|
|
||||||
|
// ensure the request is successful
|
||||||
|
assertEquals(200, response.getStatus());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProcessorEntity getRandomProcessor(final NiFiTestUser user) throws Exception {
|
private ProcessorEntity getRandomProcessor(final NiFiTestUser user) throws Exception {
|
||||||
|
@ -14,3 +14,4 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
org.apache.nifi.integration.util.SourceTestProcessor
|
org.apache.nifi.integration.util.SourceTestProcessor
|
||||||
org.apache.nifi.integration.util.TerminationTestProcessor
|
org.apache.nifi.integration.util.TerminationTestProcessor
|
||||||
|
org.apache.nifi.integration.util.RestrictedProcessor
|
Loading…
x
Reference in New Issue
Block a user