From 05872f336c8da69de70adc9e9aae46e6bc70a9cf Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 13 Oct 2025 17:17:03 +0200 Subject: [PATCH 1/9] init patch --- ...ace-process-groups-root-with-root-ID.patch | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch new file mode 100644 index 000000000..31891894f --- /dev/null +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -0,0 +1,65 @@ +From 7bae76c6e7361356fdab4886bf078b0c4fbbce51 Mon Sep 17 00:00:00 2001 +From: Andrew Kenworthy +Date: Fri, 10 Oct 2025 15:28:56 +0200 +Subject: replace process groups root with root ID + +--- + .../FileAccessPolicyProvider.java | 30 +++++++++++++++++-- + 1 file changed, 28 insertions(+), 2 deletions(-) + +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +index 5363bb5619..2951e6899a 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +@@ -568,8 +568,8 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); + + // if we are starting fresh then we might need to populate an initial admin or convert legacy users ++ parseFlow(); + if (emptyAuthorizations) { +- parseFlow(); + + if (hasInitialAdminIdentity) { + logger.info("Populating authorizations for Initial Admin: {}", initialAdminIdentity); +@@ -581,7 +581,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + // save any changes that were made and repopulate the holder + saveAndRefreshHolder(authorizations); + } else { +- this.authorizationsHolder.set(authorizationsHolder); ++ if (isUpdatedRootInAccessPolicy(authorizations)) { ++ // only do this if we have actually changed something ++ saveAndRefreshHolder(authorizations); ++ } else { ++ this.authorizationsHolder.set(authorizationsHolder); ++ } + } + } + +@@ -744,6 +749,27 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + } + } + ++ /** ++ * Replaces process group root references with the process group ID. ++ * Relevant when a static authorizations file is provided, which can ++ * then use "root" as a placeholder. ++ * ++ * @param authorizations the Authorizations instance to edit the policies in ++ */ ++ private boolean isUpdatedRootInAccessPolicy(final Authorizations authorizations) { ++ boolean authorizationsChanged = false; ++ for (Policy policy: authorizations.getPolicies().getPolicy()) { ++ String resource = policy.getResource(); ++ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/root"; ++ if (resource.endsWith(processGroupRoot)) { ++ int pos = resource.indexOf(processGroupRoot); ++ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); ++ authorizationsChanged = true; ++ } ++ } ++ return authorizationsChanged; ++ } ++ + /** + * Creates and adds an access policy for the given resource, group identity, and actions to the specified authorizations. + * From 529e92613f9bba4b426d89d31a20ea7ea7216233 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 14 Oct 2025 10:33:45 +0200 Subject: [PATCH 2/9] wip: replaceWithRootGroupId --- ...ace-process-groups-root-with-root-ID.patch | 78 +++++++++---------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch index 31891894f..37da96606 100644 --- a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -1,41 +1,18 @@ -From 7bae76c6e7361356fdab4886bf078b0c4fbbce51 Mon Sep 17 00:00:00 2001 +From 7d3774380339871ab4890b898eb35e4a8d4fc995 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 10 Oct 2025 15:28:56 +0200 Subject: replace process groups root with root ID --- - .../FileAccessPolicyProvider.java | 30 +++++++++++++++++-- - 1 file changed, 28 insertions(+), 2 deletions(-) + .../FileAccessPolicyProvider.java | 24 +++++++++++++++++++ + .../nifi/controller/StandardFlowService.java | 5 ++++ + 2 files changed, 29 insertions(+) diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index 5363bb5619..2951e6899a 100644 +index 5363bb5619..ca9758f32c 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -568,8 +568,8 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide - final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); - - // if we are starting fresh then we might need to populate an initial admin or convert legacy users -+ parseFlow(); - if (emptyAuthorizations) { -- parseFlow(); - - if (hasInitialAdminIdentity) { - logger.info("Populating authorizations for Initial Admin: {}", initialAdminIdentity); -@@ -581,7 +581,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide - // save any changes that were made and repopulate the holder - saveAndRefreshHolder(authorizations); - } else { -- this.authorizationsHolder.set(authorizationsHolder); -+ if (isUpdatedRootInAccessPolicy(authorizations)) { -+ // only do this if we have actually changed something -+ saveAndRefreshHolder(authorizations); -+ } else { -+ this.authorizationsHolder.set(authorizationsHolder); -+ } - } - } - -@@ -744,6 +749,27 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -744,6 +744,30 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide } } @@ -43,23 +20,42 @@ index 5363bb5619..2951e6899a 100644 + * Replaces process group root references with the process group ID. + * Relevant when a static authorizations file is provided, which can + * then use "root" as a placeholder. -+ * -+ * @param authorizations the Authorizations instance to edit the policies in + */ -+ private boolean isUpdatedRootInAccessPolicy(final Authorizations authorizations) { -+ boolean authorizationsChanged = false; -+ for (Policy policy: authorizations.getPolicies().getPolicy()) { -+ String resource = policy.getResource(); -+ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/root"; -+ if (resource.endsWith(processGroupRoot)) { -+ int pos = resource.indexOf(processGroupRoot); -+ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); -+ authorizationsChanged = true; ++ public void replaceWithRootGroupId() throws JAXBException { ++ if (rootGroupId != null) { ++ Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); ++ boolean authorizationsChanged = false; ++ for (Policy policy: authorizations.getPolicies().getPolicy()) { ++ String resource = policy.getResource(); ++ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/root"; ++ if (resource.endsWith(processGroupRoot)) { ++ int pos = resource.indexOf(processGroupRoot); ++ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); ++ authorizationsChanged = true; ++ } ++ } ++ if (authorizationsChanged) { ++ saveAuthorizations(authorizations); + } + } -+ return authorizationsChanged; + } + /** * Creates and adds an access policy for the given resource, group identity, and actions to the specified authorizations. * +diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +index 09f4d38f77..dad44540de 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +@@ -933,6 +933,11 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + // start the processors as indicated by the dataflow + controller.onFlowInitialized(autoResumeState); + ++ // this should be done once the flow has been initialized ++ if (this.authorizer instanceof org.apache.nifi.authorization.FileAccessPolicyProvider) { ++ ((org.apache.nifi.authorization.FileAccessPolicyProvider) this.authorizer).replaceWithRootGroupId(); ++ } ++ + loadSnippets(dataFlow.getSnippets()); + + controller.startHeartbeating(); From eaa27755b9209809fd638c9e802a4567658300e1 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 15 Oct 2025 17:15:16 +0200 Subject: [PATCH 3/9] replace root ID via callback --- .../0006-update-root-ID-via-callback.patch | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch diff --git a/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch b/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch new file mode 100644 index 000000000..f60b793b7 --- /dev/null +++ b/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch @@ -0,0 +1,124 @@ +From 93055143af33db3044aeb2ca6bab78d15ddaa4b2 Mon Sep 17 00:00:00 2001 +From: Andrew Kenworthy +Date: Wed, 15 Oct 2025 17:12:12 +0200 +Subject: update root ID via callback + +--- + .../nifi/flow/FlowInitializationCallback.java | 9 +++++++ + .../FileAccessPolicyProvider.java | 4 +++ + .../FileAuthorizerInitializer.java | 25 +++++++++++++++++++ + .../nifi/controller/StandardFlowService.java | 16 ++++++++++-- + 4 files changed, 52 insertions(+), 2 deletions(-) + create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java + create mode 100644 nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java + +diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +new file mode 100644 +index 0000000000..3039c97497 +--- /dev/null ++++ b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +@@ -0,0 +1,9 @@ ++package org.apache.nifi.flow; ++ ++/** ++ * Simple callback interface invoked when the root process group has been ++ * loaded and the flow is fully initialized for the first time. ++ */ ++public interface FlowInitializationCallback { ++ void onRootGroupLoaded(); ++} +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +index ca9758f32c..cb16cd1a30 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; + import org.apache.nifi.authorization.util.IdentityMapping; + import org.apache.nifi.authorization.util.IdentityMappingUtil; + import org.apache.nifi.components.PropertyValue; ++import org.apache.nifi.controller.StandardFlowService; + import org.apache.nifi.util.FlowInfo; + import org.apache.nifi.util.FlowParser; + import org.apache.nifi.util.NiFiProperties; +@@ -133,6 +134,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); + ++ // Register flow initialization hook ++ StandardFlowService.registerInitializationCallback(new FileAuthorizerInitializer(this)); ++ + try { + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +new file mode 100644 +index 0000000000..f67328ef84 +--- /dev/null ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +@@ -0,0 +1,25 @@ ++package org.apache.nifi.authorization; ++ ++import org.apache.nifi.flow.FlowInitializationCallback; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++ ++public class FileAuthorizerInitializer implements FlowInitializationCallback { ++ private static final Logger logger = LoggerFactory.getLogger(FileAuthorizerInitializer.class); ++private FileAccessPolicyProvider fileAccessPolicyProvider; ++ ++ public FileAuthorizerInitializer(FileAccessPolicyProvider fileAccessPolicyProvider) { ++ this.fileAccessPolicyProvider = fileAccessPolicyProvider; ++ } ++ ++ @Override ++ public void onRootGroupLoaded() { ++ try { ++ logger.info("Flow initialized; ensuring root group ID is recorded in authorizations.xml"); ++ this.fileAccessPolicyProvider.replaceWithRootGroupId(); ++ } catch (Exception e) { ++ logger.warn("Unable to update authorizations.xml with root group ID", e); ++ } ++ } ++} +\ No newline at end of file +diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +index dad44540de..b0137c8302 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +@@ -55,6 +55,7 @@ import org.apache.nifi.controller.serialization.FlowSynchronizationException; + import org.apache.nifi.controller.status.ProcessGroupStatus; + import org.apache.nifi.engine.FlowEngine; + import org.apache.nifi.events.BulletinFactory; ++import org.apache.nifi.flow.FlowInitializationCallback; + import org.apache.nifi.groups.BundleUpdateStrategy; + import org.apache.nifi.groups.ProcessGroup; + import org.apache.nifi.groups.RemoteProcessGroup; +@@ -148,6 +149,13 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + private static final String CONNECTION_EXCEPTION_MSG_PREFIX = "Failed to connect node to cluster"; + private static final Logger logger = LoggerFactory.getLogger(StandardFlowService.class); + ++ // Static callback registration for post-initialization hooks ++ private static volatile FlowInitializationCallback initializationCallback; ++ ++ public static void registerInitializationCallback(FlowInitializationCallback callback) { ++ initializationCallback = callback; ++ } ++ + public static StandardFlowService createStandaloneInstance( + final FlowController controller, + final NiFiProperties nifiProperties, +@@ -934,8 +942,12 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + controller.onFlowInitialized(autoResumeState); + + // this should be done once the flow has been initialized +- if (this.authorizer instanceof org.apache.nifi.authorization.FileAccessPolicyProvider) { +- ((org.apache.nifi.authorization.FileAccessPolicyProvider) this.authorizer).replaceWithRootGroupId(); ++ if (initializationCallback != null) { ++ try { ++ initializationCallback.onRootGroupLoaded(); ++ } catch (Exception e) { ++ logger.warn("Error invoking FlowInitializationCallback", e); ++ } + } + + loadSnippets(dataFlow.getSnippets()); From 1f929314b2465ed29f8984648406aebbbc9aaf14 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 16 Oct 2025 09:25:23 +0200 Subject: [PATCH 4/9] re-parse flow --- .../patches/2.4.0/0007-re-parse-flow.patch | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch diff --git a/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch b/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch new file mode 100644 index 000000000..3e2854d61 --- /dev/null +++ b/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch @@ -0,0 +1,37 @@ +From e69a23a6b3181582d0638db00cf1caf81eff56ff Mon Sep 17 00:00:00 2001 +From: Andrew Kenworthy +Date: Thu, 16 Oct 2025 09:24:39 +0200 +Subject: re-parse flow + +--- + .../nifi/authorization/FileAccessPolicyProvider.java | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +index cb16cd1a30..17b3f3929d 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +@@ -754,7 +754,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + * then use "root" as a placeholder. + */ + public void replaceWithRootGroupId() throws JAXBException { ++ if (rootGroupId == null) { ++ logger.info("Parsing flow as rootGroupId is not yet defined"); ++ parseFlow(); ++ } + if (rootGroupId != null) { ++ logger.info("Parsing root group with {}", rootGroupId); + Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); + boolean authorizationsChanged = false; + for (Policy policy: authorizations.getPolicies().getPolicy()) { +@@ -769,6 +774,10 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + if (authorizationsChanged) { + saveAuthorizations(authorizations); + } ++ } else { ++ // this is not expected as this is called from the flow service ++ // once it has been configured ++ logger.info("rootGroupId still not established!"); + } + } + From 6e95c47eefa8caa3d48d4b6d0fca077a4476e942 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 16 Oct 2025 10:49:05 +0200 Subject: [PATCH 5/9] consolidate patches --- ...ace-process-groups-root-with-root-ID.patch | 124 ++++++++++++++++-- .../0006-update-root-ID-via-callback.patch | 124 ------------------ .../patches/2.4.0/0007-re-parse-flow.patch | 37 ------ 3 files changed, 114 insertions(+), 171 deletions(-) delete mode 100644 nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch delete mode 100644 nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch index 37da96606..905e07a3d 100644 --- a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -1,18 +1,55 @@ -From 7d3774380339871ab4890b898eb35e4a8d4fc995 Mon Sep 17 00:00:00 2001 +From 775a7fcb5c9828b5b9077c2cccf7f300de65bd45 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 10 Oct 2025 15:28:56 +0200 Subject: replace process groups root with root ID --- - .../FileAccessPolicyProvider.java | 24 +++++++++++++++++++ - .../nifi/controller/StandardFlowService.java | 5 ++++ - 2 files changed, 29 insertions(+) + .../nifi/flow/FlowInitializationCallback.java | 9 +++++ + .../FileAccessPolicyProvider.java | 37 +++++++++++++++++++ + .../FileAuthorizerInitializer.java | 25 +++++++++++++ + .../nifi/controller/StandardFlowService.java | 17 +++++++++ + 4 files changed, 88 insertions(+) + create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java + create mode 100644 nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +new file mode 100644 +index 0000000000..3039c97497 +--- /dev/null ++++ b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +@@ -0,0 +1,9 @@ ++package org.apache.nifi.flow; ++ ++/** ++ * Simple callback interface invoked when the root process group has been ++ * loaded and the flow is fully initialized for the first time. ++ */ ++public interface FlowInitializationCallback { ++ void onRootGroupLoaded(); ++} diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index 5363bb5619..ca9758f32c 100644 +index 5363bb5619..17b3f3929d 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -744,6 +744,30 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; + import org.apache.nifi.authorization.util.IdentityMapping; + import org.apache.nifi.authorization.util.IdentityMappingUtil; + import org.apache.nifi.components.PropertyValue; ++import org.apache.nifi.controller.StandardFlowService; + import org.apache.nifi.util.FlowInfo; + import org.apache.nifi.util.FlowParser; + import org.apache.nifi.util.NiFiProperties; +@@ -133,6 +134,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); + ++ // Register flow initialization hook ++ StandardFlowService.registerInitializationCallback(new FileAuthorizerInitializer(this)); ++ + try { + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); +@@ -744,6 +748,39 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide } } @@ -22,7 +59,12 @@ index 5363bb5619..ca9758f32c 100644 + * then use "root" as a placeholder. + */ + public void replaceWithRootGroupId() throws JAXBException { ++ if (rootGroupId == null) { ++ logger.info("Parsing flow as rootGroupId is not yet defined"); ++ parseFlow(); ++ } + if (rootGroupId != null) { ++ logger.info("Parsing root group with {}", rootGroupId); + Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); + boolean authorizationsChanged = false; + for (Policy policy: authorizations.getPolicies().getPolicy()) { @@ -37,23 +79,85 @@ index 5363bb5619..ca9758f32c 100644 + if (authorizationsChanged) { + saveAuthorizations(authorizations); + } ++ } else { ++ // this is not expected as this is called from the flow service ++ // once it has been configured ++ logger.info("rootGroupId still not established!"); + } + } + /** * Creates and adds an access policy for the given resource, group identity, and actions to the specified authorizations. * +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +new file mode 100644 +index 0000000000..f67328ef84 +--- /dev/null ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +@@ -0,0 +1,25 @@ ++package org.apache.nifi.authorization; ++ ++import org.apache.nifi.flow.FlowInitializationCallback; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++ ++public class FileAuthorizerInitializer implements FlowInitializationCallback { ++ private static final Logger logger = LoggerFactory.getLogger(FileAuthorizerInitializer.class); ++private FileAccessPolicyProvider fileAccessPolicyProvider; ++ ++ public FileAuthorizerInitializer(FileAccessPolicyProvider fileAccessPolicyProvider) { ++ this.fileAccessPolicyProvider = fileAccessPolicyProvider; ++ } ++ ++ @Override ++ public void onRootGroupLoaded() { ++ try { ++ logger.info("Flow initialized; ensuring root group ID is recorded in authorizations.xml"); ++ this.fileAccessPolicyProvider.replaceWithRootGroupId(); ++ } catch (Exception e) { ++ logger.warn("Unable to update authorizations.xml with root group ID", e); ++ } ++ } ++} +\ No newline at end of file diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java -index 09f4d38f77..dad44540de 100644 +index 09f4d38f77..b0137c8302 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java -@@ -933,6 +933,11 @@ public class StandardFlowService implements FlowService, ProtocolHandler { +@@ -55,6 +55,7 @@ import org.apache.nifi.controller.serialization.FlowSynchronizationException; + import org.apache.nifi.controller.status.ProcessGroupStatus; + import org.apache.nifi.engine.FlowEngine; + import org.apache.nifi.events.BulletinFactory; ++import org.apache.nifi.flow.FlowInitializationCallback; + import org.apache.nifi.groups.BundleUpdateStrategy; + import org.apache.nifi.groups.ProcessGroup; + import org.apache.nifi.groups.RemoteProcessGroup; +@@ -148,6 +149,13 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + private static final String CONNECTION_EXCEPTION_MSG_PREFIX = "Failed to connect node to cluster"; + private static final Logger logger = LoggerFactory.getLogger(StandardFlowService.class); + ++ // Static callback registration for post-initialization hooks ++ private static volatile FlowInitializationCallback initializationCallback; ++ ++ public static void registerInitializationCallback(FlowInitializationCallback callback) { ++ initializationCallback = callback; ++ } ++ + public static StandardFlowService createStandaloneInstance( + final FlowController controller, + final NiFiProperties nifiProperties, +@@ -933,6 +941,15 @@ public class StandardFlowService implements FlowService, ProtocolHandler { // start the processors as indicated by the dataflow controller.onFlowInitialized(autoResumeState); + // this should be done once the flow has been initialized -+ if (this.authorizer instanceof org.apache.nifi.authorization.FileAccessPolicyProvider) { -+ ((org.apache.nifi.authorization.FileAccessPolicyProvider) this.authorizer).replaceWithRootGroupId(); ++ if (initializationCallback != null) { ++ try { ++ initializationCallback.onRootGroupLoaded(); ++ } catch (Exception e) { ++ logger.warn("Error invoking FlowInitializationCallback", e); ++ } + } + loadSnippets(dataFlow.getSnippets()); diff --git a/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch b/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch deleted file mode 100644 index f60b793b7..000000000 --- a/nifi/stackable/patches/2.4.0/0006-update-root-ID-via-callback.patch +++ /dev/null @@ -1,124 +0,0 @@ -From 93055143af33db3044aeb2ca6bab78d15ddaa4b2 Mon Sep 17 00:00:00 2001 -From: Andrew Kenworthy -Date: Wed, 15 Oct 2025 17:12:12 +0200 -Subject: update root ID via callback - ---- - .../nifi/flow/FlowInitializationCallback.java | 9 +++++++ - .../FileAccessPolicyProvider.java | 4 +++ - .../FileAuthorizerInitializer.java | 25 +++++++++++++++++++ - .../nifi/controller/StandardFlowService.java | 16 ++++++++++-- - 4 files changed, 52 insertions(+), 2 deletions(-) - create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java - create mode 100644 nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java - -diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java -new file mode 100644 -index 0000000000..3039c97497 ---- /dev/null -+++ b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java -@@ -0,0 +1,9 @@ -+package org.apache.nifi.flow; -+ -+/** -+ * Simple callback interface invoked when the root process group has been -+ * loaded and the flow is fully initialized for the first time. -+ */ -+public interface FlowInitializationCallback { -+ void onRootGroupLoaded(); -+} -diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index ca9758f32c..cb16cd1a30 100644 ---- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -+++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; - import org.apache.nifi.authorization.util.IdentityMapping; - import org.apache.nifi.authorization.util.IdentityMappingUtil; - import org.apache.nifi.components.PropertyValue; -+import org.apache.nifi.controller.StandardFlowService; - import org.apache.nifi.util.FlowInfo; - import org.apache.nifi.util.FlowParser; - import org.apache.nifi.util.NiFiProperties; -@@ -133,6 +134,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide - public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { - userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); - -+ // Register flow initialization hook -+ StandardFlowService.registerInitializationCallback(new FileAuthorizerInitializer(this)); -+ - try { - final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); -diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java -new file mode 100644 -index 0000000000..f67328ef84 ---- /dev/null -+++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java -@@ -0,0 +1,25 @@ -+package org.apache.nifi.authorization; -+ -+import org.apache.nifi.flow.FlowInitializationCallback; -+import org.slf4j.Logger; -+import org.slf4j.LoggerFactory; -+ -+ -+public class FileAuthorizerInitializer implements FlowInitializationCallback { -+ private static final Logger logger = LoggerFactory.getLogger(FileAuthorizerInitializer.class); -+private FileAccessPolicyProvider fileAccessPolicyProvider; -+ -+ public FileAuthorizerInitializer(FileAccessPolicyProvider fileAccessPolicyProvider) { -+ this.fileAccessPolicyProvider = fileAccessPolicyProvider; -+ } -+ -+ @Override -+ public void onRootGroupLoaded() { -+ try { -+ logger.info("Flow initialized; ensuring root group ID is recorded in authorizations.xml"); -+ this.fileAccessPolicyProvider.replaceWithRootGroupId(); -+ } catch (Exception e) { -+ logger.warn("Unable to update authorizations.xml with root group ID", e); -+ } -+ } -+} -\ No newline at end of file -diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java -index dad44540de..b0137c8302 100644 ---- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java -+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java -@@ -55,6 +55,7 @@ import org.apache.nifi.controller.serialization.FlowSynchronizationException; - import org.apache.nifi.controller.status.ProcessGroupStatus; - import org.apache.nifi.engine.FlowEngine; - import org.apache.nifi.events.BulletinFactory; -+import org.apache.nifi.flow.FlowInitializationCallback; - import org.apache.nifi.groups.BundleUpdateStrategy; - import org.apache.nifi.groups.ProcessGroup; - import org.apache.nifi.groups.RemoteProcessGroup; -@@ -148,6 +149,13 @@ public class StandardFlowService implements FlowService, ProtocolHandler { - private static final String CONNECTION_EXCEPTION_MSG_PREFIX = "Failed to connect node to cluster"; - private static final Logger logger = LoggerFactory.getLogger(StandardFlowService.class); - -+ // Static callback registration for post-initialization hooks -+ private static volatile FlowInitializationCallback initializationCallback; -+ -+ public static void registerInitializationCallback(FlowInitializationCallback callback) { -+ initializationCallback = callback; -+ } -+ - public static StandardFlowService createStandaloneInstance( - final FlowController controller, - final NiFiProperties nifiProperties, -@@ -934,8 +942,12 @@ public class StandardFlowService implements FlowService, ProtocolHandler { - controller.onFlowInitialized(autoResumeState); - - // this should be done once the flow has been initialized -- if (this.authorizer instanceof org.apache.nifi.authorization.FileAccessPolicyProvider) { -- ((org.apache.nifi.authorization.FileAccessPolicyProvider) this.authorizer).replaceWithRootGroupId(); -+ if (initializationCallback != null) { -+ try { -+ initializationCallback.onRootGroupLoaded(); -+ } catch (Exception e) { -+ logger.warn("Error invoking FlowInitializationCallback", e); -+ } - } - - loadSnippets(dataFlow.getSnippets()); diff --git a/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch b/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch deleted file mode 100644 index 3e2854d61..000000000 --- a/nifi/stackable/patches/2.4.0/0007-re-parse-flow.patch +++ /dev/null @@ -1,37 +0,0 @@ -From e69a23a6b3181582d0638db00cf1caf81eff56ff Mon Sep 17 00:00:00 2001 -From: Andrew Kenworthy -Date: Thu, 16 Oct 2025 09:24:39 +0200 -Subject: re-parse flow - ---- - .../nifi/authorization/FileAccessPolicyProvider.java | 9 +++++++++ - 1 file changed, 9 insertions(+) - -diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index cb16cd1a30..17b3f3929d 100644 ---- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -+++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -754,7 +754,12 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide - * then use "root" as a placeholder. - */ - public void replaceWithRootGroupId() throws JAXBException { -+ if (rootGroupId == null) { -+ logger.info("Parsing flow as rootGroupId is not yet defined"); -+ parseFlow(); -+ } - if (rootGroupId != null) { -+ logger.info("Parsing root group with {}", rootGroupId); - Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); - boolean authorizationsChanged = false; - for (Policy policy: authorizations.getPolicies().getPolicy()) { -@@ -769,6 +774,10 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide - if (authorizationsChanged) { - saveAuthorizations(authorizations); - } -+ } else { -+ // this is not expected as this is called from the flow service -+ // once it has been configured -+ logger.info("rootGroupId still not established!"); - } - } - From 547782072f4764595e5da8d16a45991e84303dbb Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 23 Oct 2025 16:20:59 +0200 Subject: [PATCH 6/9] corrected save action --- .../0005-replace-process-groups-root-with-root-ID.patch | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch index 905e07a3d..43185ebd6 100644 --- a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -1,4 +1,4 @@ -From 775a7fcb5c9828b5b9077c2cccf7f300de65bd45 Mon Sep 17 00:00:00 2001 +From 1caea87a7a4806dd0a233cfac2ee2a4dc7994dd7 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 10 Oct 2025 15:28:56 +0200 Subject: replace process groups root with root ID @@ -28,7 +28,7 @@ index 0000000000..3039c97497 + void onRootGroupLoaded(); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index 5363bb5619..17b3f3929d 100644 +index 5363bb5619..5ed48a5af9 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java @@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; @@ -77,7 +77,7 @@ index 5363bb5619..17b3f3929d 100644 + } + } + if (authorizationsChanged) { -+ saveAuthorizations(authorizations); ++ saveAndRefreshHolder(authorizations); + } + } else { + // this is not expected as this is called from the flow service From b1726331509476b0938ddc52fdf1281d22ce6c05 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 4 Dec 2025 16:28:33 +0100 Subject: [PATCH 7/9] add property for root name and patch 2.6.0 --- ...ace-process-groups-root-with-root-ID.patch | 99 ++++++--- ...ace-process-groups-root-with-root-ID.patch | 193 ++++++++++++++++++ 2 files changed, 260 insertions(+), 32 deletions(-) create mode 100644 nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch index 43185ebd6..58555e95a 100644 --- a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -1,17 +1,32 @@ -From 1caea87a7a4806dd0a233cfac2ee2a4dc7994dd7 Mon Sep 17 00:00:00 2001 +From cd8d70c7fc112bfe1cd4478bb477b15e70744da1 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 10 Oct 2025 15:28:56 +0200 Subject: replace process groups root with root ID --- - .../nifi/flow/FlowInitializationCallback.java | 9 +++++ - .../FileAccessPolicyProvider.java | 37 +++++++++++++++++++ - .../FileAuthorizerInitializer.java | 25 +++++++++++++ - .../nifi/controller/StandardFlowService.java | 17 +++++++++ - 4 files changed, 88 insertions(+) + .../org/apache/nifi/util/NiFiProperties.java | 3 ++ + .../nifi/flow/FlowInitializationCallback.java | 9 ++++ + .../FileAccessPolicyProvider.java | 43 +++++++++++++++++++ + .../FileAuthorizerInitializer.java | 25 +++++++++++ + .../nifi/controller/StandardFlowService.java | 17 ++++++++ + 5 files changed, 97 insertions(+) create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java create mode 100644 nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +index 4bd2f4f810..24d31960b7 100644 +--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java ++++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +@@ -336,6 +336,9 @@ public class NiFiProperties extends ApplicationProperties { + // performance tracking defaults + public static final int DEFAULT_TRACK_PERFORMANCE_PERCENTAGE = 0; + ++ // root process group replacement ++ public static final String ROOT_PROCESS_GROUP_PLACEHOLDER ="nifi.process.group.root.placeholder"; ++ + // defaults + public static final Boolean DEFAULT_AUTO_RESUME_STATE = true; + public static final String DEFAULT_AUTHORIZER_CONFIGURATION_FILE = "conf/authorizers.xml"; diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java new file mode 100644 index 0000000000..3039c97497 @@ -28,10 +43,18 @@ index 0000000000..3039c97497 + void onRootGroupLoaded(); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index 5363bb5619..5ed48a5af9 100644 +index 5363bb5619..db377bb9d3 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; +@@ -16,6 +16,7 @@ + */ + package org.apache.nifi.authorization; + ++import com.google.common.base.Strings; + import org.apache.commons.lang3.StringUtils; + import org.apache.nifi.authorization.annotation.AuthorizerContext; + import org.apache.nifi.authorization.exception.AuthorizationAccessException; +@@ -29,6 +30,7 @@ import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; import org.apache.nifi.components.PropertyValue; @@ -39,7 +62,16 @@ index 5363bb5619..5ed48a5af9 100644 import org.apache.nifi.util.FlowInfo; import org.apache.nifi.util.FlowParser; import org.apache.nifi.util.NiFiProperties; -@@ -133,6 +134,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -77,6 +79,8 @@ import java.util.concurrent.atomic.AtomicReference; + import java.util.regex.Matcher; + import java.util.regex.Pattern; + ++import static org.apache.nifi.util.NiFiProperties.ROOT_PROCESS_GROUP_PLACEHOLDER; ++ + public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { + + private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); +@@ -133,6 +137,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); @@ -49,7 +81,7 @@ index 5363bb5619..5ed48a5af9 100644 try { final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); -@@ -744,6 +748,39 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -744,6 +751,42 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide } } @@ -59,30 +91,33 @@ index 5363bb5619..5ed48a5af9 100644 + * then use "root" as a placeholder. + */ + public void replaceWithRootGroupId() throws JAXBException { -+ if (rootGroupId == null) { -+ logger.info("Parsing flow as rootGroupId is not yet defined"); -+ parseFlow(); -+ } -+ if (rootGroupId != null) { -+ logger.info("Parsing root group with {}", rootGroupId); -+ Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); -+ boolean authorizationsChanged = false; -+ for (Policy policy: authorizations.getPolicies().getPolicy()) { -+ String resource = policy.getResource(); -+ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/root"; -+ if (resource.endsWith(processGroupRoot)) { -+ int pos = resource.indexOf(processGroupRoot); -+ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); -+ authorizationsChanged = true; -+ } ++ String placeholder = this.properties.getProperty(ROOT_PROCESS_GROUP_PLACEHOLDER, ""); ++ if (!Strings.isNullOrEmpty(placeholder)) { ++ if (rootGroupId == null) { ++ logger.info("Parsing flow as rootGroupId is not yet defined"); ++ parseFlow(); + } -+ if (authorizationsChanged) { -+ saveAndRefreshHolder(authorizations); ++ if (rootGroupId != null) { ++ logger.info("Parsing root group with {}", rootGroupId); ++ Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); ++ boolean authorizationsChanged = false; ++ for (Policy policy: authorizations.getPolicies().getPolicy()) { ++ String resource = policy.getResource(); ++ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/" + placeholder; ++ if (resource.endsWith(processGroupRoot)) { ++ int pos = resource.indexOf(processGroupRoot); ++ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); ++ authorizationsChanged = true; ++ } ++ } ++ if (authorizationsChanged) { ++ saveAndRefreshHolder(authorizations); ++ } ++ } else { ++ // this is not expected as this is called from the flow service ++ // once it has been configured ++ logger.info("rootGroupId still not established!"); + } -+ } else { -+ // this is not expected as this is called from the flow service -+ // once it has been configured -+ logger.info("rootGroupId still not established!"); + } + } + diff --git a/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch new file mode 100644 index 000000000..fb8859930 --- /dev/null +++ b/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch @@ -0,0 +1,193 @@ +From 5abbf351e769a64019332335da5f7d12c6c85868 Mon Sep 17 00:00:00 2001 +From: Andrew Kenworthy +Date: Thu, 4 Dec 2025 15:07:07 +0100 +Subject: replace process groups root with root ID + +--- + .../org/apache/nifi/util/NiFiProperties.java | 3 ++ + .../nifi/flow/FlowInitializationCallback.java | 9 ++++ + .../FileAccessPolicyProvider.java | 43 +++++++++++++++++++ + .../FileAuthorizerInitializer.java | 25 +++++++++++ + .../nifi/controller/StandardFlowService.java | 17 ++++++++ + 5 files changed, 97 insertions(+) + create mode 100644 nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java + create mode 100644 nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java + +diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +index 9c26cb7eb3..3a4d79eaea 100644 +--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java ++++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java +@@ -335,6 +335,9 @@ public class NiFiProperties extends ApplicationProperties { + // performance tracking defaults + public static final int DEFAULT_TRACK_PERFORMANCE_PERCENTAGE = 0; + ++ // root process group replacement ++ public static final String ROOT_PROCESS_GROUP_PLACEHOLDER ="nifi.process.group.root.placeholder"; ++ + // defaults + public static final Boolean DEFAULT_AUTO_RESUME_STATE = true; + public static final String DEFAULT_AUTHORIZER_CONFIGURATION_FILE = "conf/authorizers.xml"; +diff --git a/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +new file mode 100644 +index 0000000000..3039c97497 +--- /dev/null ++++ b/nifi-framework-api/src/main/java/org/apache/nifi/flow/FlowInitializationCallback.java +@@ -0,0 +1,9 @@ ++package org.apache.nifi.flow; ++ ++/** ++ * Simple callback interface invoked when the root process group has been ++ * loaded and the flow is fully initialized for the first time. ++ */ ++public interface FlowInitializationCallback { ++ void onRootGroupLoaded(); ++} +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +index b4a0be42d3..5100a3ba21 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +@@ -34,6 +34,7 @@ import org.apache.nifi.authorization.resource.ResourceType; + import org.apache.nifi.authorization.util.IdentityMapping; + import org.apache.nifi.authorization.util.IdentityMappingUtil; + import org.apache.nifi.components.PropertyValue; ++import org.apache.nifi.controller.StandardFlowService; + import org.apache.nifi.util.FlowInfo; + import org.apache.nifi.util.FlowParser; + import org.apache.nifi.util.NiFiProperties; +@@ -77,6 +78,8 @@ import java.util.concurrent.atomic.AtomicReference; + import java.util.regex.Matcher; + import java.util.regex.Pattern; + ++import static org.apache.nifi.util.NiFiProperties.ROOT_PROCESS_GROUP_PLACEHOLDER; ++ + public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { + + private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); +@@ -135,6 +138,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); + ++ // Register flow initialization hook ++ StandardFlowService.registerInitializationCallback(new FileAuthorizerInitializer(this)); ++ + try { + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); +@@ -764,6 +770,43 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide + } + } + ++ /** ++ * Replaces process group root references with the process group ID. ++ * Relevant when a static authorizations file is provided, which can ++ * then use "root" as a placeholder. ++ */ ++ public void replaceWithRootGroupId() throws JAXBException { ++ String placeholder = this.properties.getProperty(ROOT_PROCESS_GROUP_PLACEHOLDER, ""); ++ ++ if (!StringUtils.isNotBlank(placeholder)) { ++ if (rootGroupId == null) { ++ logger.info("Parsing flow as rootGroupId is not yet defined"); ++ parseFlow(); ++ } ++ if (rootGroupId != null) { ++ logger.info("Parsing root group with {}", rootGroupId); ++ Authorizations authorizations = this.authorizationsHolder.get().getAuthorizations(); ++ boolean authorizationsChanged = false; ++ for (Policy policy: authorizations.getPolicies().getPolicy()) { ++ String resource = policy.getResource(); ++ String processGroupRoot = ResourceType.ProcessGroup.getValue() + "/" + placeholder; ++ if (resource.endsWith(processGroupRoot)) { ++ int pos = resource.indexOf(processGroupRoot); ++ policy.setResource(resource.substring(0, pos) + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId); ++ authorizationsChanged = true; ++ } ++ } ++ if (authorizationsChanged) { ++ saveAndRefreshHolder(authorizations); ++ } ++ } else { ++ // this is not expected as this is called from the flow service ++ // once it has been configured ++ logger.info("rootGroupId still not established!"); ++ } ++ } ++ } ++ + /** + * Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations. + * +diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +new file mode 100644 +index 0000000000..f67328ef84 +--- /dev/null ++++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizerInitializer.java +@@ -0,0 +1,25 @@ ++package org.apache.nifi.authorization; ++ ++import org.apache.nifi.flow.FlowInitializationCallback; ++import org.slf4j.Logger; ++import org.slf4j.LoggerFactory; ++ ++ ++public class FileAuthorizerInitializer implements FlowInitializationCallback { ++ private static final Logger logger = LoggerFactory.getLogger(FileAuthorizerInitializer.class); ++private FileAccessPolicyProvider fileAccessPolicyProvider; ++ ++ public FileAuthorizerInitializer(FileAccessPolicyProvider fileAccessPolicyProvider) { ++ this.fileAccessPolicyProvider = fileAccessPolicyProvider; ++ } ++ ++ @Override ++ public void onRootGroupLoaded() { ++ try { ++ logger.info("Flow initialized; ensuring root group ID is recorded in authorizations.xml"); ++ this.fileAccessPolicyProvider.replaceWithRootGroupId(); ++ } catch (Exception e) { ++ logger.warn("Unable to update authorizations.xml with root group ID", e); ++ } ++ } ++} +\ No newline at end of file +diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +index 5de3486164..2873b26644 100644 +--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java ++++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/StandardFlowService.java +@@ -55,6 +55,7 @@ import org.apache.nifi.controller.serialization.FlowSynchronizationException; + import org.apache.nifi.controller.status.ProcessGroupStatus; + import org.apache.nifi.engine.FlowEngine; + import org.apache.nifi.events.BulletinFactory; ++import org.apache.nifi.flow.FlowInitializationCallback; + import org.apache.nifi.groups.BundleUpdateStrategy; + import org.apache.nifi.groups.ProcessGroup; + import org.apache.nifi.groups.RemoteProcessGroup; +@@ -148,6 +149,13 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + private static final String CONNECTION_EXCEPTION_MSG_PREFIX = "Failed to connect node to cluster"; + private static final Logger logger = LoggerFactory.getLogger(StandardFlowService.class); + ++ // Static callback registration for post-initialization hooks ++ private static volatile FlowInitializationCallback initializationCallback; ++ ++ public static void registerInitializationCallback(FlowInitializationCallback callback) { ++ initializationCallback = callback; ++ } ++ + public static StandardFlowService createStandaloneInstance( + final FlowController controller, + final NiFiProperties nifiProperties, +@@ -935,6 +943,15 @@ public class StandardFlowService implements FlowService, ProtocolHandler { + // start the processors as indicated by the dataflow + controller.onFlowInitialized(autoResumeState); + ++ // this should be done once the flow has been initialized ++ if (initializationCallback != null) { ++ try { ++ initializationCallback.onRootGroupLoaded(); ++ } catch (Exception e) { ++ logger.warn("Error invoking FlowInitializationCallback", e); ++ } ++ } ++ + loadSnippets(dataFlow.getSnippets()); + + controller.startHeartbeating(); From 5f1422e049292bf1e0fe77285078521f44dc1666 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 5 Dec 2025 12:58:43 +0100 Subject: [PATCH 8/9] correct/consolidate stringutils usage --- ...ace-process-groups-root-with-root-ID.patch | 23 +++++++------------ ...ace-process-groups-root-with-root-ID.patch | 6 ++--- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch index 58555e95a..0ea45f418 100644 --- a/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.4.0/0005-replace-process-groups-root-with-root-ID.patch @@ -1,4 +1,4 @@ -From cd8d70c7fc112bfe1cd4478bb477b15e70744da1 Mon Sep 17 00:00:00 2001 +From ed80d426e85c7b741d865f866092e89b61742c10 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 10 Oct 2025 15:28:56 +0200 Subject: replace process groups root with root ID @@ -43,18 +43,10 @@ index 0000000000..3039c97497 + void onRootGroupLoaded(); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index 5363bb5619..db377bb9d3 100644 +index 5363bb5619..a03a18d444 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -@@ -16,6 +16,7 @@ - */ - package org.apache.nifi.authorization; - -+import com.google.common.base.Strings; - import org.apache.commons.lang3.StringUtils; - import org.apache.nifi.authorization.annotation.AuthorizerContext; - import org.apache.nifi.authorization.exception.AuthorizationAccessException; -@@ -29,6 +30,7 @@ import org.apache.nifi.authorization.resource.ResourceType; +@@ -29,6 +29,7 @@ import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; import org.apache.nifi.components.PropertyValue; @@ -62,7 +54,7 @@ index 5363bb5619..db377bb9d3 100644 import org.apache.nifi.util.FlowInfo; import org.apache.nifi.util.FlowParser; import org.apache.nifi.util.NiFiProperties; -@@ -77,6 +79,8 @@ import java.util.concurrent.atomic.AtomicReference; +@@ -77,6 +78,8 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -71,7 +63,7 @@ index 5363bb5619..db377bb9d3 100644 public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider { private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class); -@@ -133,6 +137,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -133,6 +136,9 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { userGroupProviderLookup = initializationContext.getUserGroupProviderLookup(); @@ -81,7 +73,7 @@ index 5363bb5619..db377bb9d3 100644 try { final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); authorizationsSchema = schemaFactory.newSchema(FileAccessPolicyProvider.class.getResource(AUTHORIZATIONS_XSD)); -@@ -744,6 +751,42 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide +@@ -744,6 +750,43 @@ public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvide } } @@ -92,7 +84,8 @@ index 5363bb5619..db377bb9d3 100644 + */ + public void replaceWithRootGroupId() throws JAXBException { + String placeholder = this.properties.getProperty(ROOT_PROCESS_GROUP_PLACEHOLDER, ""); -+ if (!Strings.isNullOrEmpty(placeholder)) { ++ ++ if (StringUtils.isNotBlank(placeholder)) { + if (rootGroupId == null) { + logger.info("Parsing flow as rootGroupId is not yet defined"); + parseFlow(); diff --git a/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch b/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch index fb8859930..f3e8ef010 100644 --- a/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch +++ b/nifi/stackable/patches/2.6.0/0006-replace-process-groups-root-with-root-ID.patch @@ -1,4 +1,4 @@ -From 5abbf351e769a64019332335da5f7d12c6c85868 Mon Sep 17 00:00:00 2001 +From b9d72e6d64e8d813c846285da127c3fa9114f894 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Thu, 4 Dec 2025 15:07:07 +0100 Subject: replace process groups root with root ID @@ -43,7 +43,7 @@ index 0000000000..3039c97497 + void onRootGroupLoaded(); +} diff --git a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java -index b4a0be42d3..5100a3ba21 100644 +index b4a0be42d3..93d5f73a2c 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java +++ b/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAccessPolicyProvider.java @@ -34,6 +34,7 @@ import org.apache.nifi.authorization.resource.ResourceType; @@ -85,7 +85,7 @@ index b4a0be42d3..5100a3ba21 100644 + public void replaceWithRootGroupId() throws JAXBException { + String placeholder = this.properties.getProperty(ROOT_PROCESS_GROUP_PLACEHOLDER, ""); + -+ if (!StringUtils.isNotBlank(placeholder)) { ++ if (StringUtils.isNotBlank(placeholder)) { + if (rootGroupId == null) { + logger.info("Parsing flow as rootGroupId is not yet defined"); + parseFlow(); From ff97e7da1aec4c29c7bd435843aae1f34eceee3f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Dec 2025 15:09:52 +0100 Subject: [PATCH 9/9] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6085a0a5..d53baa9e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file. - superset: Add 6.0.0-rc2 ([#1337]). - hive: Build [hive-metastore-opa-authorizer](https://github.com/boschglobal/hive-metastore-opa-authorizer) from source and add to image ([#1340]). - hive: Add `4.2.0` ([#1356]). +- nifi: Add patches to replace process group root ID placeholder ([#1358]). ### Changed @@ -28,6 +29,7 @@ All notable changes to this project will be documented in this file. [#1354]: https://github.com/stackabletech/docker-images/pull/1354 [#1356]: https://github.com/stackabletech/docker-images/pull/1356 [#1357]: https://github.com/stackabletech/docker-images/pull/1357 +[#1358]: https://github.com/stackabletech/docker-images/pull/1358 ## [25.11.0] - 2025-11-07