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 38041ab7d19 NIFI-16419 Standardized Authorization for Parameter
Context Creation (#11775)
38041ab7d19 is described below
commit 38041ab7d1973eccdc702ed0f9cf8201660aa842
Author: David Handermann <[email protected]>
AuthorDate: Wed Oct 7 05:24:13 2026 -0500
NIFI-16419 Standardized Authorization for Parameter Context Creation
(#11775)
- Added AuthorizeParameterProviderApply for handling conditional Parameter
Context authorization based on required operations
---
.../AuthorizeParameterProviderApply.java | 69 ++++++++++++
.../nifi/web/api/ParameterProviderResource.java | 108 +++++++++++--------
.../AuthorizeParameterProviderApplyTest.java | 118 +++++++++++++++++++++
3 files changed, 252 insertions(+), 43 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeParameterProviderApply.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeParameterProviderApply.java
new file mode 100644
index 00000000000..62198fac596
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/AuthorizeParameterProviderApply.java
@@ -0,0 +1,69 @@
+/*
+ * 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.web.api.entity.ParameterContextEntity;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Authorization helpers for applying Parameter Provider fetched parameters to
Parameter Contexts.
+ */
+public final class AuthorizeParameterProviderApply {
+
+ private AuthorizeParameterProviderApply() {
+ }
+
+ /**
+ * Authorizes a Parameter Provider apply-parameters request. Requires READ
on the Parameter Provider. When the request
+ * will create one or more Parameter Contexts, requires WRITE on the
Parameter Contexts resource. Requires READ and
+ * WRITE on each existing Parameter Context that will be updated.
+ *
+ * @param parameterProviderId the Parameter Provider identifier
+ * @param requiresParameterContextCreation whether the request will create
one or more Parameter Contexts
+ * @param parameterContextUpdates existing Parameter Contexts that will be
updated
+ * @param authorizer the Authorizer
+ * @param lookup the AuthorizableLookup
+ * @param user the current user
+ */
+ public static void authorizeApplyParameters(
+ final String parameterProviderId,
+ final boolean requiresParameterContextCreation,
+ final Collection<ParameterContextEntity> parameterContextUpdates,
+ final Authorizer authorizer,
+ final AuthorizableLookup lookup,
+ final NiFiUser user
+ ) {
+ final ComponentAuthorizable parameterProvider =
lookup.getParameterProvider(parameterProviderId);
+ parameterProvider.getAuthorizable().authorize(authorizer,
RequestAction.READ, user);
+
+ if (requiresParameterContextCreation) {
+ // Creating a Parameter Context requires the same global WRITE
permission as POST /parameter-contexts.
+ lookup.getParameterContexts().authorize(authorizer,
RequestAction.WRITE, user);
+ }
+
+ final Collection<ParameterContextEntity> updates =
parameterContextUpdates == null ? List.of() : parameterContextUpdates;
+ for (final ParameterContextEntity context : updates) {
+ final Authorizable parameterContext =
lookup.getParameterContext(context.getComponent().getId());
+ parameterContext.authorize(authorizer, RequestAction.READ, user);
+ parameterContext.authorize(authorizer, RequestAction.WRITE, user);
+ }
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterProviderResource.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterProviderResource.java
index 1f3a5a4ff04..588826ae289 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterProviderResource.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterProviderResource.java
@@ -46,6 +46,7 @@ import
org.apache.nifi.authorization.AuthorizeComponentAnalysis;
import org.apache.nifi.authorization.AuthorizeComponentReference;
import org.apache.nifi.authorization.AuthorizeConfigVerification;
import org.apache.nifi.authorization.AuthorizeControllerServiceReference;
+import org.apache.nifi.authorization.AuthorizeParameterProviderApply;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ComponentAuthorizable;
import org.apache.nifi.authorization.RequestAction;
@@ -915,6 +916,9 @@ public class ParameterProviderResource extends
AbstractParameterResource {
security = {
@SecurityRequirement(name = "Read -
/parameter-providers/{parameterProviderId}"),
@SecurityRequirement(name = "Write -
/parameter-providers/{parameterProviderId}"),
+ @SecurityRequirement(name = "Write - /parameter-contexts -
when applying creates a new Parameter Context"),
+ @SecurityRequirement(name = "Read - for every Parameter
Context that is affected by the update"),
+ @SecurityRequirement(name = "Write - for every Parameter
Context that is affected by the update"),
@SecurityRequirement(name = "Read - for every component
that is affected by the update"),
@SecurityRequirement(name = "Write - for every component
that is affected by the update")
}
@@ -952,35 +956,30 @@ public class ParameterProviderResource extends
AbstractParameterResource {
// 1. Determine which components will be affected and are
enabled/running
// 2. Verify READ and WRITE permissions for user, for every component
that is affected
// 3. Verify READ and WRITE permissions for user, for each referenced
Parameter Context
- // 4. Stop all Processors that are affected.
- // 5. Wait for all of the Processors to finish stopping.
- // 6. Disable all Controller Services that are affected.
- // 7. Wait for all Controller Services to finish disabling.
- // 8. Update Parameter Contexts with fetched parameters from the
Parameter Provider
- // 9. Re-Enable all affected Controller Services
- // 10. Re-Start all Processors
+ // 4. Verify WRITE permission for Parameter Contexts when a new
Parameter Context must be created
+ // 5. Create any required Parameter Contexts
+ // 6. Stop all Processors that are affected.
+ // 7. Wait for all of the Processors to finish stopping.
+ // 8. Disable all Controller Services that are affected.
+ // 9. Wait for all Controller Services to finish disabling.
+ // 10. Update Parameter Contexts with fetched parameters from the
Parameter Provider
+ // 11. Re-Enable all affected Controller Services
+ // 12. Re-Start all Processors
final Collection<ParameterGroupConfiguration>
parameterGroupConfigurations =
getParameterGroupConfigurations(requestEntity.getParameterGroupConfigurations());
validateParameterGroupConfigurations(parameterProviderId,
parameterGroupConfigurations, user);
- parameterGroupConfigurations.stream()
- .filter(parameterGroupConfiguration ->
requiresNewParameterContext(parameterGroupConfiguration, user))
- .forEach(parameterGroupConfiguration -> {
- final ParameterContextEntity newParameterContext =
getNewParameterContextEntity(parameterProviderId, parameterGroupConfiguration);
- try {
- performParameterContextCreate(user, getAbsolutePath(),
replicateRequest, newParameterContext);
- } catch (final LifecycleManagementException e) {
- throw new RuntimeException("Failed to create Parameter
Context " + parameterGroupConfiguration.getGroupName(), e);
- }
- });
- // Get a list of parameter context entities representing changes
needed in order to apply the fetched parameters
+ final boolean requiresParameterContextCreation =
parameterGroupConfigurations.stream()
+ .anyMatch(parameterGroupConfiguration ->
requiresNewParameterContext(parameterGroupConfiguration, user));
+
+ // Preliminary updates for Parameter Contexts that already exist. New
Parameter Contexts are created only after authorization.
final List<ParameterContextEntity> parameterContextUpdates =
serviceFacade.getParameterContextUpdatesForAppliedParameters(parameterProviderId,
parameterGroupConfigurations);
final Set<AffectedComponentEntity> affectedComponents =
getAffectedComponentEntities(parameterContextUpdates);
logger.debug("Received Apply Request for Parameter Provider: {}; the
following {} components will be affected: {}", requestEntity,
affectedComponents.size(), affectedComponents);
final InitiateParameterProviderApplyParametersRequestWrapper
requestWrapper = new
InitiateParameterProviderApplyParametersRequestWrapper(parameterProviderId,
- parameterContextUpdates, componentLifecycle,
getAbsolutePath(), affectedComponents, replicateRequest, user);
+ parameterGroupConfigurations, componentLifecycle,
getAbsolutePath(), replicateRequest, user);
final Revision requestRevision =
getRevision(requestEntity.getRevision(), parameterProviderId);
return withWriteLock(
@@ -988,15 +987,13 @@ public class ParameterProviderResource extends
AbstractParameterResource {
requestWrapper,
requestRevision,
lookup -> {
- // Verify READ permissions for user, for the Parameter
Provider itself
- final ComponentAuthorizable parameterProvider =
lookup.getParameterProvider(parameterProviderId);
- parameterProvider.getAuthorizable().authorize(authorizer,
RequestAction.READ, user);
-
- parameterContextUpdates.forEach(context -> {
- final Authorizable parameterContext =
lookup.getParameterContext(context.getComponent().getId());
- parameterContext.authorize(authorizer,
RequestAction.READ, NiFiUserUtils.getNiFiUser());
- parameterContext.authorize(authorizer,
RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
- });
+ AuthorizeParameterProviderApply.authorizeApplyParameters(
+ parameterProviderId,
+ requiresParameterContextCreation,
+ parameterContextUpdates,
+ authorizer,
+ lookup, user
+ );
// Verify READ and WRITE permissions for user, for every
component that is affected
affectedComponents.forEach(component ->
parameterUpdateManager.authorizeAffectedComponent(component, lookup, user,
true, true));
@@ -1584,19 +1581,49 @@ public class ParameterProviderResource extends
AbstractParameterResource {
}
private Response submitApplyRequest(final Revision requestRevision, final
InitiateParameterProviderApplyParametersRequestWrapper requestWrapper) {
+ final String parameterProviderId =
requestWrapper.getParameterProviderId();
+ final NiFiUser user = requestWrapper.getUser();
+ final Collection<ParameterGroupConfiguration>
parameterGroupConfigurations = requestWrapper.getParameterGroupConfigurations();
+
+ final List<ParameterGroupConfiguration>
parameterGroupConfigurationsToCreate = parameterGroupConfigurations.stream()
+ .filter(parameterGroupConfiguration ->
requiresNewParameterContext(parameterGroupConfiguration, user))
+ .toList();
+
+ // Authorize Parameter Context creation against the current flow state
before creating, then create.
+ if (!parameterGroupConfigurationsToCreate.isEmpty()) {
+ serviceFacade.authorizeAccess(lookup ->
lookup.getParameterContexts().authorize(authorizer, RequestAction.WRITE, user));
+
+ for (final ParameterGroupConfiguration parameterGroupConfiguration
: parameterGroupConfigurationsToCreate) {
+ final ParameterContextEntity newParameterContext =
getNewParameterContextEntity(parameterProviderId, parameterGroupConfiguration);
+ try {
+ performParameterContextCreate(user,
requestWrapper.getExampleUri(), requestWrapper.isReplicateRequest(),
newParameterContext);
+ } catch (final LifecycleManagementException e) {
+ throw new RuntimeException("Failed to create Parameter
Context " + parameterGroupConfiguration.getGroupName(), e);
+ }
+ }
+ }
+
+ final List<ParameterContextEntity> parameterContextUpdates =
serviceFacade.getParameterContextUpdatesForAppliedParameters(parameterProviderId,
parameterGroupConfigurations);
+ final Set<AffectedComponentEntity> affectedComponents =
getAffectedComponentEntities(parameterContextUpdates);
+
+ // Authorize the Parameter Contexts and affected components that will
actually be updated after any creates.
+ serviceFacade.authorizeAccess(lookup -> {
+
AuthorizeParameterProviderApply.authorizeApplyParameters(parameterProviderId,
false, parameterContextUpdates, authorizer, lookup, user);
+ affectedComponents.forEach(component ->
parameterUpdateManager.authorizeAffectedComponent(component, lookup, user,
true, true));
+ });
+
// Create an asynchronous request that will occur in the background,
because this request may
// result in stopping components, which can take an indeterminate
amount of time.
final String requestId = UUID.randomUUID().toString();
- final String parameterProviderId =
requestWrapper.getParameterProviderId();
final AsynchronousWebRequest<List<ParameterContextEntity>,
List<ParameterContextEntity>> request = new StandardAsynchronousWebRequest<>(
- requestId, requestWrapper.getParameterContextEntities(),
parameterProviderId, requestWrapper.getUser(), getUpdateSteps());
+ requestId, parameterContextUpdates, parameterProviderId, user,
getUpdateSteps());
// Submit the request to be performed in the background
final Consumer<AsynchronousWebRequest<List<ParameterContextEntity>,
List<ParameterContextEntity>>> updateTask = asyncRequest -> {
try {
final List<ParameterContextEntity>
updatedParameterContextEntities =
parameterUpdateManager.updateParameterContexts(asyncRequest,
requestWrapper.getComponentLifecycle(),
- requestWrapper.getExampleUri(),
requestWrapper.getReferencingComponents(), requestWrapper.isReplicateRequest(),
requestRevision, requestWrapper.getParameterContextEntities());
+ requestWrapper.getExampleUri(), affectedComponents,
requestWrapper.isReplicateRequest(), requestRevision, parameterContextUpdates);
asyncRequest.markStepComplete(updatedParameterContextEntities);
} catch (final ResumeFlowException rfe) {
@@ -1697,22 +1724,21 @@ public class ParameterProviderResource extends
AbstractParameterResource {
private static class
InitiateParameterProviderApplyParametersRequestWrapper extends Entity {
private final String parameterProviderId;
- private final List<ParameterContextEntity> parameterContextEntities;
+ private final Collection<ParameterGroupConfiguration>
parameterGroupConfigurations;
private final ComponentLifecycle componentLifecycle;
private final URI exampleUri;
- private final Set<AffectedComponentEntity> affectedComponents;
private final boolean replicateRequest;
private final NiFiUser nifiUser;
- public InitiateParameterProviderApplyParametersRequestWrapper(final
String parameterProviderId, final List<ParameterContextEntity>
parameterContextEntities,
+ public InitiateParameterProviderApplyParametersRequestWrapper(final
String parameterProviderId,
+ final
Collection<ParameterGroupConfiguration> parameterGroupConfigurations,
final
ComponentLifecycle componentLifecycle,
- final
URI exampleUri, final Set<AffectedComponentEntity> affectedComponents, final
boolean replicateRequest,
+ final
URI exampleUri, final boolean replicateRequest,
final
NiFiUser nifiUser) {
this.parameterProviderId = parameterProviderId;
- this.parameterContextEntities = parameterContextEntities;
+ this.parameterGroupConfigurations = parameterGroupConfigurations;
this.componentLifecycle = componentLifecycle;
this.exampleUri = exampleUri;
- this.affectedComponents = affectedComponents;
this.replicateRequest = replicateRequest;
this.nifiUser = nifiUser;
}
@@ -1721,8 +1747,8 @@ public class ParameterProviderResource extends
AbstractParameterResource {
return parameterProviderId;
}
- public List<ParameterContextEntity> getParameterContextEntities() {
- return parameterContextEntities;
+ public Collection<ParameterGroupConfiguration>
getParameterGroupConfigurations() {
+ return parameterGroupConfigurations;
}
public ComponentLifecycle getComponentLifecycle() {
@@ -1733,10 +1759,6 @@ public class ParameterProviderResource extends
AbstractParameterResource {
return exampleUri;
}
- public Set<AffectedComponentEntity> getReferencingComponents() {
- return affectedComponents;
- }
-
public boolean isReplicateRequest() {
return replicateRequest;
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeParameterProviderApplyTest.java
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeParameterProviderApplyTest.java
new file mode 100644
index 00000000000..8ff769535db
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/AuthorizeParameterProviderApplyTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.parameter.ParameterContext;
+import org.apache.nifi.web.api.dto.ParameterContextDTO;
+import org.apache.nifi.web.api.entity.ParameterContextEntity;
+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.List;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class AuthorizeParameterProviderApplyTest {
+
+ private static final String PARAMETER_PROVIDER_ID =
"parameter-provider-id";
+
+ private static final String PARAMETER_CONTEXT_ID = "parameter-context-id";
+
+ @Mock
+ private Authorizer authorizer;
+
+ @Mock
+ private AuthorizableLookup lookup;
+
+ @Mock
+ private NiFiUser user;
+
+ @Mock
+ private ComponentAuthorizable parameterProviderAuthorizable;
+
+ @Mock
+ private Authorizable parameterProviderResource;
+
+ @Mock
+ private Authorizable parameterContexts;
+
+ @Mock
+ private ParameterContext parameterContext;
+
+ @Test
+ void testAuthorizeApplyParametersRequiresWriteToCreateParameterContext() {
+
when(lookup.getParameterProvider(PARAMETER_PROVIDER_ID)).thenReturn(parameterProviderAuthorizable);
+
when(parameterProviderAuthorizable.getAuthorizable()).thenReturn(parameterProviderResource);
+ when(lookup.getParameterContexts()).thenReturn(parameterContexts);
+ doThrow(new AccessDeniedException("Unable to modify parameter
contexts")).when(parameterContexts)
+ .authorize(eq(authorizer), eq(RequestAction.WRITE), eq(user));
+
+ assertThrows(AccessDeniedException.class, () ->
AuthorizeParameterProviderApply.authorizeApplyParameters(
+ PARAMETER_PROVIDER_ID, true, List.of(), authorizer, lookup,
user));
+
+ verify(parameterProviderResource).authorize(eq(authorizer),
eq(RequestAction.READ), eq(user));
+ verify(parameterContexts).authorize(eq(authorizer),
eq(RequestAction.WRITE), eq(user));
+ }
+
+ @Test
+ void
testAuthorizeApplyParametersDoesNotRequireWriteToCreateWhenNoNewParameterContext()
{
+
when(lookup.getParameterProvider(PARAMETER_PROVIDER_ID)).thenReturn(parameterProviderAuthorizable);
+
when(parameterProviderAuthorizable.getAuthorizable()).thenReturn(parameterProviderResource);
+
+
AuthorizeParameterProviderApply.authorizeApplyParameters(PARAMETER_PROVIDER_ID,
false, List.of(), authorizer, lookup, user);
+
+ verify(parameterProviderResource).authorize(eq(authorizer),
eq(RequestAction.READ), eq(user));
+ verify(lookup, never()).getParameterContexts();
+ }
+
+ @Test
+ void
testAuthorizeApplyParametersRequiresReadAndWriteOnExistingParameterContext() {
+
when(lookup.getParameterProvider(PARAMETER_PROVIDER_ID)).thenReturn(parameterProviderAuthorizable);
+
when(parameterProviderAuthorizable.getAuthorizable()).thenReturn(parameterProviderResource);
+
when(lookup.getParameterContext(PARAMETER_CONTEXT_ID)).thenReturn(parameterContext);
+ doNothing().when(parameterContext).authorize(eq(authorizer),
eq(RequestAction.READ), eq(user));
+ doThrow(new AccessDeniedException("Unable to write Parameter
Context")).when(parameterContext)
+ .authorize(eq(authorizer), eq(RequestAction.WRITE), eq(user));
+
+ assertThrows(AccessDeniedException.class, () ->
AuthorizeParameterProviderApply.authorizeApplyParameters(
+ PARAMETER_PROVIDER_ID, false,
List.of(parameterContextEntity()), authorizer, lookup, user));
+
+ verify(parameterContext).authorize(eq(authorizer),
eq(RequestAction.READ), eq(user));
+ verify(parameterContext).authorize(eq(authorizer),
eq(RequestAction.WRITE), eq(user));
+ verify(lookup, never()).getParameterContexts();
+ }
+
+ private static ParameterContextEntity parameterContextEntity() {
+ final ParameterContextDTO parameterContextDto = new
ParameterContextDTO();
+ parameterContextDto.setId(PARAMETER_CONTEXT_ID);
+
+ final ParameterContextEntity parameterContextEntity = new
ParameterContextEntity();
+ parameterContextEntity.setComponent(parameterContextDto);
+ return parameterContextEntity;
+ }
+}