This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 9cdecd3b7d4 NIFI-16263 Streamlined Process Group Update Authorization
(#11601)
9cdecd3b7d4 is described below
commit 9cdecd3b7d48a805df4d22d725f9ac5a4c17e15d
Author: David Handermann <[email protected]>
AuthorDate: Mon Aug 31 04:35:06 2026 -0500
NIFI-16263 Streamlined Process Group Update Authorization (#11601)
---
.../nifi/authorization/AuthorizeFlowUpdate.java | 130 +++++++++++++++
.../nifi/authorization/AuthorizeProcessGroup.java | 101 +++++++++++
.../apache/nifi/web/api/ApplicationResource.java | 86 +++-------
.../apache/nifi/web/api/FlowUpdateResource.java | 61 +------
.../apache/nifi/web/api/ProcessGroupResource.java | 13 +-
.../org/apache/nifi/web/api/VersionsResource.java | 40 ++---
.../authorization/AuthorizeFlowUpdateTest.java | 184 +++++++++++++++++++++
7 files changed, 463 insertions(+), 152 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeFlowUpdate.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeFlowUpdate.java
new file mode 100644
index 00000000000..9200798633e
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeFlowUpdate.java
@@ -0,0 +1,130 @@
+/*
+ * 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.user.NiFiUser;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.registry.flow.FlowSnapshotContainer;
+import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
+import org.apache.nifi.web.NiFiServiceFacade;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Authorizes replacing the contents of a Process Group with a proposed flow
snapshot along with resolved references
+ */
+public final class AuthorizeFlowUpdate {
+ /**
+ * Identifiers of inherited Controller Services and Parameter Providers
from a proposed snapshot that could not be
+ * resolved against the local flow.
+ *
+ * @param controllerServices unresolved inherited Controller Service
identifiers
+ * @param parameterProviders unresolved Parameter Provider identifiers
+ */
+ public record UnresolvedReferences(Set<String> controllerServices,
Set<String> parameterProviders) {
+ }
+
+ /**
+ * Resolves inherited references on the proposed snapshot and then
authorizes the current user requesting flow updates
+ *
+ * @param groupId the id of the process group being updated
+ * @param flowSnapshot the proposed flow contents
+ * @param serviceFacade the service facade
+ * @param authorizer the authorizer
+ * @param lookup the authorizable lookup
+ * @param user the user to authorize
+ */
+ public static void resolveAndAuthorizeFlowUpdate(
+ final String groupId,
+ final RegisteredFlowSnapshot flowSnapshot,
+ final NiFiServiceFacade serviceFacade,
+ final Authorizer authorizer,
+ final AuthorizableLookup lookup,
+ final NiFiUser user
+ ) {
+ final FlowSnapshotContainer flowSnapshotContainer = new
FlowSnapshotContainer(flowSnapshot);
+ final UnresolvedReferences unresolvedReferences =
resolveReferences(groupId, flowSnapshotContainer, serviceFacade, user);
+ authorizeFlowUpdate(groupId, flowSnapshot, unresolvedReferences,
serviceFacade, authorizer, lookup, user);
+ }
+
+ /**
+ * Discovers compatible bundles for the proposed snapshot and resolves the
inherited Controller Services and the
+ * Parameter Providers that it references to components in the local flow
+ *
+ * @param groupId the id of the process group being updated
+ * @param flowSnapshotContainer the proposed flow snapshot along with the
snapshots of any version controlled child groups
+ * @param serviceFacade the service facade
+ * @param user the user performing the update
+ * @return the references that could not be resolved against the local flow
+ */
+ public static UnresolvedReferences resolveReferences(
+ final String groupId,
+ final FlowSnapshotContainer flowSnapshotContainer,
+ final NiFiServiceFacade serviceFacade,
+ final NiFiUser user
+ ) {
+ final RegisteredFlowSnapshot flowSnapshot =
flowSnapshotContainer.getFlowSnapshot();
+
+ // Discover compatible bundles since the flow snapshot can contain
different versions
+
serviceFacade.discoverCompatibleBundles(flowSnapshot.getFlowContents());
+
serviceFacade.discoverCompatibleBundles(flowSnapshot.getParameterProviders());
+
+ // If there are any Controller Services referenced that are inherited
from the parent group, resolve those to point to the appropriate Controller
Service
+ final Set<String> unresolvedControllerServices =
serviceFacade.resolveInheritedControllerServices(flowSnapshotContainer,
groupId, user);
+
+ // If there are any Parameter Providers referenced by Parameter
Contexts, resolve these to point to the appropriate Parameter Provider
+ final Set<String> unresolvedParameterProviders =
serviceFacade.resolveParameterProviders(flowSnapshot, user);
+
+ return new UnresolvedReferences(unresolvedControllerServices,
unresolvedParameterProviders);
+ }
+
+ /**
+ * Authorizes READ and WRITE permissions for the given user on the Process
Group being updated
+ *
+ * @param groupId the id of the process group being updated
+ * @param flowSnapshot the proposed flow contents
+ * @param unresolvedReferences the references from the proposed snapshot
that could not be resolved against the local flow
+ * @param serviceFacade the service facade
+ * @param authorizer the authorizer
+ * @param lookup the authorizable lookup
+ * @param user the user to authorize
+ */
+ public static void authorizeFlowUpdate(
+ final String groupId,
+ final RegisteredFlowSnapshot flowSnapshot,
+ final UnresolvedReferences unresolvedReferences,
+ final NiFiServiceFacade serviceFacade,
+ final Authorizer authorizer,
+ final AuthorizableLookup lookup,
+ final NiFiUser user
+ ) {
+ final ProcessGroupAuthorizable groupAuthorizable =
lookup.getProcessGroup(groupId);
+ AuthorizeProcessGroup.authorizeProcessGroup(groupAuthorizable,
authorizer, lookup, RequestAction.READ, true, false, true, false, true);
+ AuthorizeProcessGroup.authorizeProcessGroup(groupAuthorizable,
authorizer, lookup, RequestAction.WRITE, true, false, true, false, false);
+
+ final Map<String, VersionedParameterContext> parameterContexts =
flowSnapshot.getParameterContexts();
+ if (parameterContexts != null) {
+ for (final VersionedParameterContext parameterContext :
parameterContexts.values()) {
+
AuthorizeParameterReference.authorizeParameterContextAddition(parameterContext,
serviceFacade, authorizer, lookup, user);
+ }
+ }
+
+
AuthorizeParameterProviders.authorizeUnresolvedParameterProviders(unresolvedReferences.parameterProviders(),
authorizer, lookup, user);
+
AuthorizeControllerServiceReference.authorizeUnresolvedControllerServiceReferences(groupId,
unresolvedReferences.controllerServices(), authorizer, lookup, user);
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeProcessGroup.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeProcessGroup.java
new file mode 100644
index 00000000000..d121b4ea51e
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeProcessGroup.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+
+import java.util.function.Consumer;
+
+/**
+ * Authorizes a Process Group along with encapsulated and referenced Components
+ */
+public final class AuthorizeProcessGroup {
+ /**
+ * Authorizes the specified Process Group and referenced Components
+ *
+ * @param processGroupAuthorizable process group
+ * @param authorizer authorizer
+ * @param lookup lookup
+ * @param action action
+ * @param authorizeReferencedServices whether to authorize referenced
services
+ * @param authorizeControllerServices whether to authorize controller
services
+ * @param authorizeTransitiveServices whether to authorize transitive
services
+ * @param authorizeParameterReferences whether to authorize parameter
context that contained referenced parameter if applicable
+ * @param authorizeParameterContext whether to authorize the bound
parameter context if applicable
+ */
+ public static void authorizeProcessGroup(
+ final ProcessGroupAuthorizable processGroupAuthorizable,
+ final Authorizer authorizer,
+ final AuthorizableLookup lookup,
+ final RequestAction action,
+ final boolean authorizeReferencedServices,
+ final boolean authorizeControllerServices,
+ final boolean authorizeTransitiveServices,
+ final boolean authorizeParameterReferences,
+ final boolean authorizeParameterContext
+ ) {
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final Consumer<Authorizable> authorize = authorizable ->
authorizable.authorize(authorizer, action, user);
+
+
+ authorize.accept(processGroupAuthorizable.getAuthorizable());
+
+
+ if (authorizeParameterContext) {
+
processGroupAuthorizable.getParameterContextAuthorizable().ifPresent(authorize);
+ }
+
+
processGroupAuthorizable.getEncapsulatedProcessors().forEach(processorAuthorizable
-> {
+ authorize.accept(processorAuthorizable.getAuthorizable());
+ if (authorizeReferencedServices) {
+
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable,
authorizer, lookup, authorizeTransitiveServices);
+ }
+ if (authorizeParameterReferences) {
+
AuthorizeParameterReference.authorizeParameterReferences(processorAuthorizable,
authorizer, processorAuthorizable.getParameterContext(), user);
+ }
+ });
+
processGroupAuthorizable.getEncapsulatedConnections().stream().map(AuthorizableHolder::getAuthorizable).forEach(authorize);
+
processGroupAuthorizable.getEncapsulatedInputPorts().forEach(authorize);
+
processGroupAuthorizable.getEncapsulatedOutputPorts().forEach(authorize);
+ processGroupAuthorizable.getEncapsulatedFunnels().forEach(authorize);
+ processGroupAuthorizable.getEncapsulatedLabels().forEach(authorize);
+ processGroupAuthorizable.getEncapsulatedProcessGroups().forEach(pga ->
{
+ final Authorizable authorizable = pga.getAuthorizable();
+
+ authorize.accept(authorizable);
+
+ if (authorizeParameterContext) {
+ pga.getParameterContextAuthorizable().ifPresent(authorize);
+ }
+ });
+
processGroupAuthorizable.getEncapsulatedRemoteProcessGroups().forEach(authorize);
+
+ if (authorizeControllerServices) {
+
processGroupAuthorizable.getEncapsulatedControllerServices().forEach(controllerServiceAuthorizable
-> {
+
authorize.accept(controllerServiceAuthorizable.getAuthorizable());
+ if (authorizeReferencedServices) {
+
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(controllerServiceAuthorizable,
authorizer, lookup, authorizeTransitiveServices);
+ }
+ if (authorizeParameterReferences) {
+
AuthorizeParameterReference.authorizeParameterReferences(controllerServiceAuthorizable,
authorizer, controllerServiceAuthorizable.getParameterContext(), user);
+ }
+ });
+ }
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
index 81f16ad7057..b43c9614ef6 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ApplicationResource.java
@@ -34,6 +34,7 @@ import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizeAccess;
import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
import org.apache.nifi.authorization.AuthorizeParameterReference;
+import org.apache.nifi.authorization.AuthorizeProcessGroup;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
import org.apache.nifi.authorization.RequestAction;
@@ -430,69 +431,28 @@ public abstract class ApplicationResource {
* @param authorizeParameterReferences whether to authorize parameter
context that contained referenced parameter if applicable
* @param authorizeParameterContext whether to authorize the bound
parameter context if applicable
*/
- protected void authorizeProcessGroup(final ProcessGroupAuthorizable
processGroupAuthorizable, final Authorizer authorizer, final AuthorizableLookup
lookup, final RequestAction action,
- final boolean
authorizeReferencedServices,
- final boolean
authorizeControllerServices, final boolean authorizeTransitiveServices,
- final boolean
authorizeParameterReferences, final boolean authorizeParameterContext) {
-
- final NiFiUser user = NiFiUserUtils.getNiFiUser();
- final Consumer<Authorizable> authorize = authorizable ->
authorizable.authorize(authorizer, action, user);
-
- // authorize the process group
- authorize.accept(processGroupAuthorizable.getAuthorizable());
-
- // authorize the parameter context for the specified process group
- if (authorizeParameterContext) {
-
processGroupAuthorizable.getParameterContextAuthorizable().ifPresent(authorize);
- }
-
- // authorize the contents of the group - these methods return all
encapsulated components (recursive)
-
processGroupAuthorizable.getEncapsulatedProcessors().forEach(processorAuthorizable
-> {
- // authorize the processor
- authorize.accept(processorAuthorizable.getAuthorizable());
-
- // authorize any referenced services if necessary
- if (authorizeReferencedServices) {
-
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(processorAuthorizable,
authorizer, lookup, authorizeTransitiveServices);
- }
-
- // authorize any referenced parameters if necessary
- if (authorizeParameterReferences) {
-
AuthorizeParameterReference.authorizeParameterReferences(processorAuthorizable,
authorizer, processorAuthorizable.getParameterContext(), user);
- }
- });
-
processGroupAuthorizable.getEncapsulatedConnections().stream().map(connection
-> connection.getAuthorizable()).forEach(authorize);
-
processGroupAuthorizable.getEncapsulatedInputPorts().forEach(authorize);
-
processGroupAuthorizable.getEncapsulatedOutputPorts().forEach(authorize);
- processGroupAuthorizable.getEncapsulatedFunnels().forEach(authorize);
- processGroupAuthorizable.getEncapsulatedLabels().forEach(authorize);
- processGroupAuthorizable.getEncapsulatedProcessGroups().forEach(pga ->
{
- final Authorizable authorizable = pga.getAuthorizable();
-
- authorize.accept(authorizable);
-
- if (authorizeParameterContext) {
- pga.getParameterContextAuthorizable().ifPresent(authorize);
- }
- });
-
processGroupAuthorizable.getEncapsulatedRemoteProcessGroups().forEach(authorize);
-
- // authorize controller services if necessary
- if (authorizeControllerServices) {
-
processGroupAuthorizable.getEncapsulatedControllerServices().forEach(controllerServiceAuthorizable
-> {
- // authorize the controller service
-
authorize.accept(controllerServiceAuthorizable.getAuthorizable());
-
- // authorize any referenced services if necessary
- if (authorizeReferencedServices) {
-
AuthorizeControllerServiceReference.authorizeControllerServiceReferences(controllerServiceAuthorizable,
authorizer, lookup, authorizeTransitiveServices);
- }
-
- if (authorizeParameterReferences) {
-
AuthorizeParameterReference.authorizeParameterReferences(controllerServiceAuthorizable,
authorizer, controllerServiceAuthorizable.getParameterContext(), user);
- }
- });
- }
+ protected void authorizeProcessGroup(
+ final ProcessGroupAuthorizable processGroupAuthorizable,
+ final Authorizer authorizer,
+ final AuthorizableLookup lookup,
+ final RequestAction action,
+ final boolean authorizeReferencedServices,
+ final boolean authorizeControllerServices,
+ final boolean authorizeTransitiveServices,
+ final boolean authorizeParameterReferences,
+ final boolean authorizeParameterContext
+ ) {
+ AuthorizeProcessGroup.authorizeProcessGroup(
+ processGroupAuthorizable,
+ authorizer,
+ lookup,
+ action,
+ authorizeReferencedServices,
+ authorizeControllerServices,
+ authorizeTransitiveServices,
+ authorizeParameterReferences,
+ authorizeParameterContext
+ );
}
/**
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowUpdateResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowUpdateResource.java
index 143b784605d..b999b1f60ba 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowUpdateResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowUpdateResource.java
@@ -20,19 +20,14 @@ import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
-import org.apache.nifi.authorization.AuthorizableLookup;
-import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
-import org.apache.nifi.authorization.AuthorizeParameterProviders;
-import org.apache.nifi.authorization.AuthorizeParameterReference;
+import org.apache.nifi.authorization.AuthorizeFlowUpdate;
+import org.apache.nifi.authorization.AuthorizeFlowUpdate.UnresolvedReferences;
import org.apache.nifi.authorization.Authorizer;
-import org.apache.nifi.authorization.ProcessGroupAuthorizable;
-import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceState;
-import org.apache.nifi.flow.VersionedParameterContext;
import org.apache.nifi.registry.flow.FlowSnapshotContainer;
import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
import org.apache.nifi.web.NiFiServiceFacade;
@@ -195,17 +190,7 @@ public abstract class FlowUpdateResource<T extends
ProcessGroupDescriptorEntity,
// Step 0: Obtain the versioned flow snapshot to use for the update
final FlowSnapshotContainer flowSnapshotContainer =
flowSnapshotContainerSupplier.get();
final RegisteredFlowSnapshot flowSnapshot =
flowSnapshotContainer.getFlowSnapshot();
-
- // The new flow may not contain the same versions of components in
existing flow. As a result, we need to update
- // the flow snapshot to contain compatible bundles.
-
serviceFacade.discoverCompatibleBundles(flowSnapshot.getFlowContents());
-
serviceFacade.discoverCompatibleBundles(flowSnapshot.getParameterProviders());
-
- // If there are any Controller Services referenced that are inherited
from the parent group, resolve those to point to the appropriate Controller
Service, if we are able to.
- final Set<String> unresolvedControllerServices =
serviceFacade.resolveInheritedControllerServices(flowSnapshotContainer,
groupId, user);
-
- // If there are any Parameter Providers referenced by Parameter
Contexts, resolve these to point to the appropriate Parameter Provider, if we
are able to.
- final Set<String> unresolvedParameterProviders =
serviceFacade.resolveParameterProviders(flowSnapshot, user);
+ final UnresolvedReferences unresolvedReferences =
AuthorizeFlowUpdate.resolveReferences(groupId, flowSnapshotContainer,
serviceFacade, user);
// Step 1: Determine which components will be affected by updating the
flow
final Set<AffectedComponentEntity> affectedComponents =
serviceFacade.getComponentsAffectedByFlowUpdate(groupId, flowSnapshot);
@@ -220,7 +205,7 @@ public abstract class FlowUpdateResource<T extends
ProcessGroupDescriptorEntity,
serviceFacade,
requestWrapper,
requestRevision,
- lookup -> authorizeFlowUpdate(lookup, user, groupId,
flowSnapshot, unresolvedControllerServices, unresolvedParameterProviders),
+ lookup -> AuthorizeFlowUpdate.authorizeFlowUpdate(groupId,
flowSnapshot, unresolvedReferences, serviceFacade, authorizer, lookup, user),
() -> {
// Step 3: Verify that all components in the snapshot
exist on all nodes
// Step 4: Verify that Process Group can be updated. Only
versioned flows care about the verifyNotDirty flag
@@ -230,38 +215,6 @@ public abstract class FlowUpdateResource<T extends
ProcessGroupDescriptorEntity,
);
}
- /**
- * Authorize read/write permissions for the given user on every component
of the given flow in support of flow update.
- *
- * @param lookup A lookup instance to use for retrieving components for
authorization purposes
- * @param user the user to authorize
- * @param groupId the id of the process group being evaluated
- * @param flowSnapshot the new flow contents to authorize
- */
- protected void authorizeFlowUpdate(final AuthorizableLookup lookup, final
NiFiUser user, final String groupId,
- final RegisteredFlowSnapshot
flowSnapshot, final Set<String> unresolvedControllerServices,
- final Set<String>
unresolvedParameterProviders) {
- // Step 2: Verify READ and WRITE permissions for user, for every
component.
- final ProcessGroupAuthorizable groupAuthorizable =
lookup.getProcessGroup(groupId);
- authorizeProcessGroup(groupAuthorizable, authorizer, lookup,
RequestAction.READ, true,
- false, true, false, true);
- authorizeProcessGroup(groupAuthorizable, authorizer, lookup,
RequestAction.WRITE, true,
- false, true, false, false);
-
- final Map<String, VersionedParameterContext> parameterContexts =
flowSnapshot.getParameterContexts();
- if (parameterContexts != null) {
- parameterContexts.values().forEach(
- context ->
AuthorizeParameterReference.authorizeParameterContextAddition(context,
serviceFacade, authorizer, lookup, user)
- );
- }
-
- // authorize parameter providers
-
AuthorizeParameterProviders.authorizeUnresolvedParameterProviders(unresolvedParameterProviders,
authorizer, lookup, user);
-
- // authorizer controller services
-
AuthorizeControllerServiceReference.authorizeUnresolvedControllerServiceReferences(groupId,
unresolvedControllerServices, authorizer, lookup, user);
- }
-
/**
* Create and submit the flow update request. Return response containing
an entity reflecting the status of the async request.
* <p>
@@ -401,11 +354,7 @@ public abstract class FlowUpdateResource<T extends
ProcessGroupDescriptorEntity,
// Resolve compatible bundles, inherited controller services, and
parameter providers for the rollback snapshot before any
// replication occurs, ensuring that all nodes in the cluster
receive the same resolved references.
-
serviceFacade.discoverCompatibleBundles(originalFlowSnapshot.getFlowContents());
-
serviceFacade.discoverCompatibleBundles(originalFlowSnapshot.getParameterProviders());
- final NiFiUser user = NiFiUserUtils.getNiFiUser();
-
serviceFacade.resolveInheritedControllerServices(originalFlowSnapshotContainer,
groupId, user);
- serviceFacade.resolveParameterProviders(originalFlowSnapshot,
user);
+ AuthorizeFlowUpdate.resolveReferences(groupId,
originalFlowSnapshotContainer, serviceFacade, NiFiUserUtils.getNiFiUser());
}
try {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
index 77c43f836d3..6074142e03a 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessGroupResource.java
@@ -48,6 +48,7 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizeComponentReference;
import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
+import org.apache.nifi.authorization.AuthorizeFlowUpdate;
import org.apache.nifi.authorization.AuthorizeParameterProviders;
import org.apache.nifi.authorization.AuthorizeParameterReference;
import org.apache.nifi.authorization.ConnectionAuthorizable;
@@ -3275,7 +3276,10 @@ public class ProcessGroupResource extends
FlowUpdateResource<ProcessGroupImportE
+ NON_GUARANTEED_ENDPOINT,
security = {
@SecurityRequirement(name = "Read -
/process-groups/{uuid}"),
- @SecurityRequirement(name = "Write -
/process-groups/{uuid}")
+ @SecurityRequirement(name = "Write -
/process-groups/{uuid}"),
+ @SecurityRequirement(name = "Read -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Write -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Read -
/parameter-contexts/{uuid} - For any Parameter Context that is referenced by a
Property that is changed, added, or removed")
}
)
public Response replaceProcessGroup(
@@ -3316,12 +3320,7 @@ public class ProcessGroupResource extends
FlowUpdateResource<ProcessGroupImportE
serviceFacade,
importEntity,
requestRevision,
- lookup -> {
- final ProcessGroupAuthorizable groupAuthorizable =
lookup.getProcessGroup(groupId);
- final Authorizable processGroup =
groupAuthorizable.getAuthorizable();
- processGroup.authorize(authorizer, RequestAction.READ,
NiFiUserUtils.getNiFiUser());
- processGroup.authorize(authorizer, RequestAction.WRITE,
NiFiUserUtils.getNiFiUser());
- },
+ lookup ->
AuthorizeFlowUpdate.resolveAndAuthorizeFlowUpdate(groupId, requestFlowSnapshot,
serviceFacade, authorizer, lookup, NiFiUserUtils.getNiFiUser()),
() -> {
// We do not enforce that the Process Group is 'not dirty'
because at this point,
// the client has explicitly indicated the dataflow that
the Process Group should
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
index 6bbaf98b676..00a1133109b 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/VersionsResource.java
@@ -42,6 +42,8 @@ import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AuthorizeFlowUpdate;
+import org.apache.nifi.authorization.AuthorizeFlowUpdate.UnresolvedReferences;
import org.apache.nifi.authorization.ProcessGroupAuthorizable;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
@@ -877,7 +879,10 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
+ NON_GUARANTEED_ENDPOINT,
security = {
@SecurityRequirement(name = "Read -
/process-groups/{uuid}"),
- @SecurityRequirement(name = "Write -
/process-groups/{uuid}")
+ @SecurityRequirement(name = "Write -
/process-groups/{uuid}"),
+ @SecurityRequirement(name = "Read -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Write -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Read -
/parameter-contexts/{uuid} - For any Parameter Context that is referenced by a
Property that is changed, added, or removed")
}
)
public Response updateFlowVersion(
@@ -922,12 +927,7 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
serviceFacade,
requestEntity,
requestRevision,
- lookup -> {
- final ProcessGroupAuthorizable groupAuthorizable =
lookup.getProcessGroup(groupId);
- final Authorizable processGroup =
groupAuthorizable.getAuthorizable();
- processGroup.authorize(authorizer, RequestAction.READ,
NiFiUserUtils.getNiFiUser());
- processGroup.authorize(authorizer, RequestAction.WRITE,
NiFiUserUtils.getNiFiUser());
- },
+ lookup ->
AuthorizeFlowUpdate.resolveAndAuthorizeFlowUpdate(groupId, requestFlowSnapshot,
serviceFacade, authorizer, lookup, NiFiUserUtils.getNiFiUser()),
() -> {
// We do not enforce that the Process Group is 'not dirty'
because at this point,
// the client has explicitly indicated the dataflow that
the Process Group should
@@ -978,7 +978,10 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
+ NON_GUARANTEED_ENDPOINT,
security = {
@SecurityRequirement(name = "Read -
/process-groups/{uuid}"),
- @SecurityRequirement(name = "Write -
/process-groups/{uuid}")
+ @SecurityRequirement(name = "Write -
/process-groups/{uuid}"),
+ @SecurityRequirement(name = "Read -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Write -
/{component-type}/{uuid} - For all encapsulated components"),
+ @SecurityRequirement(name = "Read -
/parameter-contexts/{uuid} - For any Parameter Context that is referenced by a
Property that is changed, added, or removed")
}
)
public Response applyRebasedFlowVersion(
@@ -1023,12 +1026,7 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
serviceFacade,
requestEntity,
requestRevision,
- lookup -> {
- final ProcessGroupAuthorizable groupAuthorizable =
lookup.getProcessGroup(groupId);
- final Authorizable processGroup =
groupAuthorizable.getAuthorizable();
- processGroup.authorize(authorizer, RequestAction.READ,
NiFiUserUtils.getNiFiUser());
- processGroup.authorize(authorizer, RequestAction.WRITE,
NiFiUserUtils.getNiFiUser());
- },
+ lookup ->
AuthorizeFlowUpdate.resolveAndAuthorizeFlowUpdate(groupId, requestFlowSnapshot,
serviceFacade, authorizer, lookup, NiFiUserUtils.getNiFiUser()),
() -> {
// We do not enforce that the Process Group is 'not dirty'
because a rebase intentionally applies
// over locally modified flows.
@@ -1510,17 +1508,7 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
// Step 0: Get the Versioned Flow Snapshot from the Flow Registry
final FlowSnapshotContainer flowSnapshotContainer =
serviceFacade.getVersionedFlowSnapshot(requestEntity.getVersionControlInformation(),
true);
final RegisteredFlowSnapshot flowSnapshot =
flowSnapshotContainer.getFlowSnapshot();
-
- // The flow in the registry may not contain the same versions of
components that we have in our flow. As a result, we need to update
- // the flow snapshot to contain compatible bundles.
-
serviceFacade.discoverCompatibleBundles(flowSnapshot.getFlowContents());
-
serviceFacade.discoverCompatibleBundles(flowSnapshot.getParameterProviders());
-
- // If there are any Controller Services referenced that are inherited
from the parent group, resolve those to point to the appropriate Controller
Service, if we are able to.
- final Set<String> unresolvedControllerServices =
serviceFacade.resolveInheritedControllerServices(flowSnapshotContainer,
groupId, NiFiUserUtils.getNiFiUser());
-
- // If there are any Parameter Providers referenced by Parameter
Contexts, resolve these to point to the appropriate Parameter Provider, if we
are able to.
- final Set<String> unresolvedParameterProviders =
serviceFacade.resolveParameterProviders(flowSnapshot,
NiFiUserUtils.getNiFiUser());
+ final UnresolvedReferences unresolvedReferences =
AuthorizeFlowUpdate.resolveReferences(groupId, flowSnapshotContainer,
serviceFacade, user);
// Step 1: Determine which components will be affected by updating the
version
final Set<AffectedComponentEntity> affectedComponents =
serviceFacade.getComponentsAffectedByFlowUpdate(groupId, flowSnapshot);
@@ -1535,7 +1523,7 @@ public class VersionsResource extends
FlowUpdateResource<VersionControlInformati
serviceFacade,
requestWrapper,
requestRevision,
- lookup -> authorizeFlowUpdate(lookup, user, groupId,
flowSnapshot, unresolvedControllerServices, unresolvedParameterProviders),
+ lookup -> AuthorizeFlowUpdate.authorizeFlowUpdate(groupId,
flowSnapshot, unresolvedReferences, serviceFacade, authorizer, lookup, user),
() -> {
// Step 3: Verify that all components in the snapshot
exist on all nodes
// Step 4: Verify that Process Group is already under
version control. If not, must start Version Control instead of updating flow
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeFlowUpdateTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeFlowUpdateTest.java
new file mode 100644
index 00000000000..402292085a0
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeFlowUpdateTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.AuthorizeFlowUpdate.UnresolvedReferences;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.flow.VersionedProcessGroup;
+import org.apache.nifi.registry.flow.FlowSnapshotContainer;
+import org.apache.nifi.registry.flow.RegisteredFlowSnapshot;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class AuthorizeFlowUpdateTest {
+
+ private static final String GROUP_ID = "group-id";
+
+ private static final String PARAMETER_CONTEXT_NAME = "Parameter Context";
+
+ private static final String PARAMETER_PROVIDER_ID =
"parameter-provider-id";
+
+ private static final String CONTROLLER_SERVICE_ID =
"controller-service-id";
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup lookup;
+
+ @Mock
+ private NiFiServiceFacade serviceFacade;
+
+ @Mock
+ private NiFiUser user;
+
+ @Mock
+ private ProcessGroupAuthorizable groupAuthorizable;
+
+ @Mock
+ private Authorizable groupResource;
+
+ @Mock
+ private ComponentAuthorizable encapsulatedProcessor;
+
+ @Mock
+ private Authorizable encapsulatedProcessorResource;
+
+ @Mock
+ private Authorizable parameterContexts;
+
+ @Mock
+ private Authorizable controller;
+
+ @Test
+ void
testResolveAndAuthorizeFlowUpdateResolvesReferencesBeforeAuthorizing() {
+ final RegisteredFlowSnapshot flowSnapshot = createFlowSnapshot();
+
when(lookup.getProcessGroup(eq(GROUP_ID))).thenReturn(groupAuthorizable);
+ when(groupAuthorizable.getAuthorizable()).thenReturn(groupResource);
+
+ AuthorizeFlowUpdate.resolveAndAuthorizeFlowUpdate(GROUP_ID,
flowSnapshot, serviceFacade, authorizer, lookup, user);
+
+
verify(serviceFacade).discoverCompatibleBundles(eq(flowSnapshot.getFlowContents()));
+
verify(serviceFacade).discoverCompatibleBundles(eq(flowSnapshot.getParameterProviders()));
+
verify(serviceFacade).resolveInheritedControllerServices(any(FlowSnapshotContainer.class),
eq(GROUP_ID), eq(user));
+ verify(serviceFacade).resolveParameterProviders(eq(flowSnapshot),
eq(user));
+
+ verify(groupResource).authorize(eq(authorizer),
eq(RequestAction.READ), any());
+ verify(groupResource).authorize(eq(authorizer),
eq(RequestAction.WRITE), any());
+ }
+
+ @Test
+ void testResolveAndAuthorizeFlowUpdateDeniedEncapsulatedProcessor() {
+ final RegisteredFlowSnapshot flowSnapshot = createFlowSnapshot();
+
when(lookup.getProcessGroup(eq(GROUP_ID))).thenReturn(groupAuthorizable);
+ when(groupAuthorizable.getAuthorizable()).thenReturn(groupResource);
+
when(groupAuthorizable.getEncapsulatedProcessors()).thenReturn(Set.of(encapsulatedProcessor));
+
when(encapsulatedProcessor.getAuthorizable()).thenReturn(encapsulatedProcessorResource);
+
doNothing().when(encapsulatedProcessorResource).authorize(eq(authorizer),
eq(RequestAction.READ), any());
+ doThrow(new
AccessDeniedException("denied")).when(encapsulatedProcessorResource).authorize(eq(authorizer),
eq(RequestAction.WRITE), any());
+
+ assertThrows(AccessDeniedException.class,
+ () ->
AuthorizeFlowUpdate.resolveAndAuthorizeFlowUpdate(GROUP_ID, flowSnapshot,
serviceFacade, authorizer, lookup, user));
+ }
+
+ @Test
+ void testAuthorizeFlowUpdateDeniedParameterContextCreation() {
+ final RegisteredFlowSnapshot flowSnapshot = createFlowSnapshot();
+ flowSnapshot.setParameterContexts(Map.of(PARAMETER_CONTEXT_NAME,
createParameterContext()));
+
+
when(lookup.getProcessGroup(eq(GROUP_ID))).thenReturn(groupAuthorizable);
+ when(groupAuthorizable.getAuthorizable()).thenReturn(groupResource);
+
when(serviceFacade.getParameterContextByName(eq(PARAMETER_CONTEXT_NAME),
eq(user))).thenReturn(null);
+ when(lookup.getParameterContexts()).thenReturn(parameterContexts);
+ doThrow(new
AccessDeniedException("denied")).when(parameterContexts).authorize(eq(authorizer),
eq(RequestAction.WRITE), eq(user));
+
+ final UnresolvedReferences unresolvedReferences = new
UnresolvedReferences(Set.of(), Set.of());
+
+ assertThrows(AccessDeniedException.class,
+ () -> AuthorizeFlowUpdate.authorizeFlowUpdate(GROUP_ID,
flowSnapshot, unresolvedReferences, serviceFacade, authorizer, lookup, user));
+ }
+
+ @Test
+ void testAuthorizeFlowUpdateDeniedUnresolvedParameterProvider() {
+ final RegisteredFlowSnapshot flowSnapshot = createFlowSnapshot();
+
when(lookup.getProcessGroup(eq(GROUP_ID))).thenReturn(groupAuthorizable);
+ when(groupAuthorizable.getAuthorizable()).thenReturn(groupResource);
+ when(lookup.getController()).thenReturn(controller);
+ doThrow(new
AccessDeniedException("denied")).when(controller).authorize(eq(authorizer),
eq(RequestAction.WRITE), eq(user));
+
+ final UnresolvedReferences unresolvedReferences = new
UnresolvedReferences(Set.of(), Set.of(PARAMETER_PROVIDER_ID));
+
+ assertThrows(AccessDeniedException.class,
+ () -> AuthorizeFlowUpdate.authorizeFlowUpdate(GROUP_ID,
flowSnapshot, unresolvedReferences, serviceFacade, authorizer, lookup, user));
+ }
+
+ @Test
+ void testResolveReferencesReturnsUnresolvedIdentifiers() {
+ final RegisteredFlowSnapshot flowSnapshot = createFlowSnapshot();
+ final FlowSnapshotContainer flowSnapshotContainer = new
FlowSnapshotContainer(flowSnapshot);
+
when(serviceFacade.resolveInheritedControllerServices(eq(flowSnapshotContainer),
eq(GROUP_ID), eq(user))).thenReturn(Set.of(CONTROLLER_SERVICE_ID));
+ when(serviceFacade.resolveParameterProviders(eq(flowSnapshot),
eq(user))).thenReturn(Set.of(PARAMETER_PROVIDER_ID));
+
+ final UnresolvedReferences unresolvedReferences =
AuthorizeFlowUpdate.resolveReferences(GROUP_ID, flowSnapshotContainer,
serviceFacade, user);
+
+ assertEquals(Set.of(CONTROLLER_SERVICE_ID),
unresolvedReferences.controllerServices());
+ assertEquals(Set.of(PARAMETER_PROVIDER_ID),
unresolvedReferences.parameterProviders());
+ }
+
+ private RegisteredFlowSnapshot createFlowSnapshot() {
+ final VersionedProcessGroup flowContents = new VersionedProcessGroup();
+ flowContents.setIdentifier(GROUP_ID);
+
+ final RegisteredFlowSnapshot flowSnapshot = new
RegisteredFlowSnapshot();
+ flowSnapshot.setFlowContents(flowContents);
+ flowSnapshot.setParameterContexts(Map.of());
+ flowSnapshot.setParameterProviders(Map.of());
+
+ return flowSnapshot;
+ }
+
+ private VersionedParameterContext createParameterContext() {
+ final VersionedParameter parameter = new VersionedParameter();
+ parameter.setName("parameter");
+ parameter.setValue("value");
+
+ final VersionedParameterContext parameterContext = new
VersionedParameterContext();
+ parameterContext.setName(PARAMETER_CONTEXT_NAME);
+ parameterContext.setParameters(Set.of(parameter));
+
+ return parameterContext;
+ }
+}