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 dd6bf9c8e29 NIFI-16291 Added Deprecation Logging for Component-level 
Policies (#11622)
dd6bf9c8e29 is described below

commit dd6bf9c8e291f59775c0590592241c805b0e98c9
Author: David Handermann <[email protected]>
AuthorDate: Fri Sep 4 06:08:19 2026 -0500

    NIFI-16291 Added Deprecation Logging for Component-level Policies (#11622)
---
 .../ComponentAccessPolicyDeprecationLogger.java    | 134 ++++++++++++++++
 .../org/apache/nifi/controller/FlowController.java |   3 +
 ...ComponentAccessPolicyDeprecationLoggerTest.java | 177 +++++++++++++++++++++
 3 files changed, 314 insertions(+)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLogger.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLogger.java
new file mode 100644
index 00000000000..0f6876d968a
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLogger.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ResourceType;
+import org.apache.nifi.deprecation.log.DeprecationLogger;
+import org.apache.nifi.deprecation.log.DeprecationLoggerFactory;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * Log deprecation warnings for Access Policies configured against individual 
components. Authorization for
+ * individual components will be replaced with authorization for the 
Controller per NIP-39
+ */
+public final class ComponentAccessPolicyDeprecationLogger {
+
+    private static final DeprecationLogger deprecationLogger = 
DeprecationLoggerFactory.getLogger(ComponentAccessPolicyDeprecationLogger.class);
+
+    /** Resource Types that identify single component instances */
+    private static final Set<ResourceType> COMPONENT_RESOURCE_TYPES = Set.of(
+            ResourceType.ProcessGroup,
+            ResourceType.Processor,
+            ResourceType.ControllerService,
+            ResourceType.InputPort,
+            ResourceType.OutputPort,
+            ResourceType.Funnel,
+            ResourceType.Label,
+            ResourceType.RemoteProcessGroup,
+            ResourceType.ParameterContext,
+            ResourceType.ParameterProvider
+    );
+
+    /** Resource Types that can precede other Types */
+    private static final Set<ResourceType> RESOURCE_PREFIX_TYPES = Set.of(
+            ResourceType.Data,
+            ResourceType.DataTransfer,
+            ResourceType.Operation,
+            ResourceType.Policy,
+            ResourceType.ProvenanceData
+    );
+
+    private static final String RESOURCE_SEPARATOR = "/";
+
+    /**
+     * Log deprecation warnings when the configured Authorizer provides Access 
Policies for individual components
+     *
+     * @param authorizer Configured Authorizer
+     * @param rootGroupId Identifier of the root Process Group, for which 
Access Policies are not deprecated
+     */
+    public static void logComponentPolicies(final Authorizer authorizer, final 
String rootGroupId) {
+        if (authorizer instanceof final ManagedAuthorizer managedAuthorizer) {
+            final AccessPolicyProvider accessPolicyProvider = 
managedAuthorizer.getAccessPolicyProvider();
+            final Set<AccessPolicy> configuredAccessPolicies = 
accessPolicyProvider.getAccessPolicies();
+            final Set<AccessPolicy> componentPolicies = 
findComponentPolicies(configuredAccessPolicies, rootGroupId);
+
+            if (!componentPolicies.isEmpty()) {
+                deprecationLogger.warn("Found [{}] component Access Policies 
deprecated for removal in NIP-39", componentPolicies.size());
+            }
+        }
+    }
+
+    static Set<AccessPolicy> findComponentPolicies(final Set<AccessPolicy> 
policies, final String rootGroupId) {
+        final Set<AccessPolicy> componentPolicies = new LinkedHashSet<>();
+
+        for (final AccessPolicy policy : policies) {
+            final String resource = policy.getResource();
+            if (isComponentPolicy(resource, rootGroupId)) {
+                componentPolicies.add(policy);
+            }
+        }
+
+        return componentPolicies;
+    }
+
+    private static boolean isComponentPolicy(final String resource, final 
String rootGroupId) {
+        boolean componentPolicyFound = false;
+
+        final String componentResource = removeResourcePrefix(resource);
+
+        for (final ResourceType componentResourceType : 
COMPONENT_RESOURCE_TYPES) {
+            final String componentResourcePrefix = 
componentResourceType.getValue() + RESOURCE_SEPARATOR;
+
+            if (componentResource.startsWith(componentResourcePrefix)) {
+                if (ResourceType.ProcessGroup == componentResourceType) {
+                    final String groupIdentifier = 
componentResource.substring(componentResourcePrefix.length());
+                    if (groupIdentifier.equals(rootGroupId)) {
+                        // Root Group Identifier expected and ignored
+                        continue;
+                    } else {
+                        componentPolicyFound = true;
+                    }
+                } else {
+                    componentPolicyFound = true;
+                }
+                break;
+            }
+        }
+
+        return componentPolicyFound;
+    }
+
+    private static String removeResourcePrefix(final String resource) {
+        String resourceNormalized = resource;
+
+        for (final ResourceType resourcePrefixType : RESOURCE_PREFIX_TYPES) {
+            final String resourcePrefixValue = resourcePrefixType.getValue();
+            final String resourcePrefixSeparator = resourcePrefixValue + 
RESOURCE_SEPARATOR;
+
+            if (resource.startsWith(resourcePrefixSeparator)) {
+                resourceNormalized = 
resource.substring(resourcePrefixValue.length());
+                break;
+            }
+        }
+
+        return resourceNormalized;
+    }
+
+    private ComponentAccessPolicyDeprecationLogger() { }
+}
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
index fcf76a0ac9a..155b0520113 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/FlowController.java
@@ -29,6 +29,7 @@ import 
org.apache.nifi.asset.StandardAssetManagerInitializationContext;
 import org.apache.nifi.asset.StandardAssetReferenceLookup;
 import org.apache.nifi.asset.StandardConnectorAssetManager;
 import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.ComponentAccessPolicyDeprecationLogger;
 import org.apache.nifi.authorization.Resource;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
@@ -1618,6 +1619,8 @@ public class FlowController implements 
ReportingTaskProvider, FlowAnalysisRulePr
 
             final Runnable discoverPythonExtensions = () -> 
extensionManager.discoverNewPythonExtensions(pythonBundle);
             
timerDrivenEngineRef.get().scheduleWithFixedDelay(discoverPythonExtensions, 1, 
1, TimeUnit.MINUTES);
+
+            
ComponentAccessPolicyDeprecationLogger.logComponentPolicies(authorizer, 
flowManager.getRootGroupId());
         } finally {
             writeLock.unlock("onFlowInitialized");
         }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLoggerTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLoggerTest.java
new file mode 100644
index 00000000000..54cf5815015
--- /dev/null
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/ComponentAccessPolicyDeprecationLoggerTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.ResourceType;
+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.LinkedHashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+class ComponentAccessPolicyDeprecationLoggerTest {
+
+    private static final String RESOURCE_FORMAT = "%s%s/%s";
+
+    private static final String EMPTY_PREFIX = "";
+
+    private static final String ROOT_GROUP_ID = 
"a1d4c1b0-0000-1000-0000-000000000001";
+
+    private static final String CHILD_GROUP_ID = 
"a1d4c1b0-0000-1000-0000-000000000002";
+
+    private static final String COMPONENT_ID = 
"a1d4c1b0-0000-1000-0000-000000000003";
+
+    private static final String ROOT_GROUP_RESOURCE = 
getComponentResource(ResourceType.ProcessGroup, ROOT_GROUP_ID);
+
+    private static final String CHILD_GROUP_RESOURCE = 
getComponentResource(ResourceType.ProcessGroup, CHILD_GROUP_ID);
+
+    @Mock
+    private Authorizer authorizer;
+
+    @Mock
+    private ManagedAuthorizer managedAuthorizer;
+
+    @Mock
+    private AccessPolicyProvider accessPolicyProvider;
+
+    @Test
+    void testRootProcessGroupAndSharedResourcesNotDeprecated() {
+        final Set<AccessPolicy> policies = getPolicies(
+                ROOT_GROUP_RESOURCE,
+                getComponentResource(ResourceType.Data, 
ResourceType.ProcessGroup, ROOT_GROUP_ID),
+                getComponentResource(ResourceType.Policy, 
ResourceType.ProcessGroup, ROOT_GROUP_ID),
+                getComponentResource(ResourceType.Operation, 
ResourceType.ProcessGroup, ROOT_GROUP_ID),
+                getComponentResource(ResourceType.ProvenanceData, 
ResourceType.ProcessGroup, ROOT_GROUP_ID),
+                ResourceType.Flow.getValue(),
+                ResourceType.Controller.getValue(),
+                ResourceType.Tenant.getValue(),
+                ResourceType.Policy.getValue(),
+                ResourceType.Proxy.getValue(),
+                ResourceType.System.getValue(),
+                ResourceType.Counters.getValue(),
+                ResourceType.Provenance.getValue(),
+                ResourceType.ParameterContext.getValue()
+        );
+
+        assertEquals(Set.of(), getDeprecatedResources(policies, 
ROOT_GROUP_ID));
+    }
+
+    @Test
+    void testResourceTypesOutsideComponentScopeNotDeprecated() {
+        final Set<AccessPolicy> policies = getPolicies(
+                getComponentResource(ResourceType.RegistryClient, 
COMPONENT_ID),
+                getComponentResource(ResourceType.FlowAnalysisRule, 
COMPONENT_ID),
+                getComponentResource(ResourceType.ReportingTask, COMPONENT_ID),
+                getComponentResource(ResourceType.Connector, COMPONENT_ID)
+        );
+
+        assertEquals(Set.of(), getDeprecatedResources(policies, 
ROOT_GROUP_ID));
+    }
+
+    @Test
+    void testProcessGroupOtherThanRootGroupDeprecated() {
+        final String childGroupDataResource = 
getComponentResource(ResourceType.Data, ResourceType.ProcessGroup, 
CHILD_GROUP_ID);
+        final String childGroupPolicyResource = 
getComponentResource(ResourceType.Policy, ResourceType.ProcessGroup, 
CHILD_GROUP_ID);
+
+        final Set<AccessPolicy> policies = getPolicies(ROOT_GROUP_RESOURCE, 
CHILD_GROUP_RESOURCE, childGroupDataResource, childGroupPolicyResource);
+
+        assertEquals(Set.of(CHILD_GROUP_RESOURCE, childGroupDataResource, 
childGroupPolicyResource), getDeprecatedResources(policies, ROOT_GROUP_ID));
+        assertEquals(Set.of(ROOT_GROUP_RESOURCE, CHILD_GROUP_RESOURCE, 
childGroupDataResource, childGroupPolicyResource), 
getDeprecatedResources(policies, null));
+    }
+
+    @Test
+    void testComponentResourceTypesDeprecated() {
+        final Set<String> componentResources = Set.of(
+                getComponentResource(ResourceType.Processor, COMPONENT_ID),
+                getComponentResource(ResourceType.ControllerService, 
COMPONENT_ID),
+                getComponentResource(ResourceType.InputPort, COMPONENT_ID),
+                getComponentResource(ResourceType.OutputPort, COMPONENT_ID),
+                getComponentResource(ResourceType.Funnel, COMPONENT_ID),
+                getComponentResource(ResourceType.Label, COMPONENT_ID),
+                getComponentResource(ResourceType.RemoteProcessGroup, 
COMPONENT_ID),
+                getComponentResource(ResourceType.ParameterContext, 
COMPONENT_ID),
+                getComponentResource(ResourceType.ParameterProvider, 
COMPONENT_ID),
+                getComponentResource(ResourceType.Data, 
ResourceType.Processor, COMPONENT_ID),
+                getComponentResource(ResourceType.DataTransfer, 
ResourceType.InputPort, COMPONENT_ID),
+                getComponentResource(ResourceType.Operation, 
ResourceType.Processor, COMPONENT_ID),
+                getComponentResource(ResourceType.ProvenanceData, 
ResourceType.Processor, COMPONENT_ID)
+        );
+
+        final Set<AccessPolicy> policies = 
getPolicies(componentResources.toArray(new String[0]));
+
+        assertEquals(componentResources, getDeprecatedResources(policies, 
ROOT_GROUP_ID));
+    }
+
+    @Test
+    void testLogComponentPoliciesManagedAuthorizer() {
+        
when(managedAuthorizer.getAccessPolicyProvider()).thenReturn(accessPolicyProvider);
+        
when(accessPolicyProvider.getAccessPolicies()).thenReturn(getPolicies(getComponentResource(ResourceType.Processor,
 COMPONENT_ID)));
+
+        
ComponentAccessPolicyDeprecationLogger.logComponentPolicies(managedAuthorizer, 
ROOT_GROUP_ID);
+
+        verify(accessPolicyProvider).getAccessPolicies();
+    }
+
+    @Test
+    void testLogComponentPoliciesAuthorizerWithoutAccessPolicies() {
+        
ComponentAccessPolicyDeprecationLogger.logComponentPolicies(authorizer, 
ROOT_GROUP_ID);
+
+        verifyNoInteractions(authorizer);
+    }
+
+    private static String getComponentResource(final ResourceType 
resourceType, final String identifier) {
+        return RESOURCE_FORMAT.formatted(EMPTY_PREFIX, 
resourceType.getValue(), identifier);
+    }
+
+    private static String getComponentResource(final ResourceType 
prefixResourceType, final ResourceType resourceType, final String identifier) {
+        return RESOURCE_FORMAT.formatted(prefixResourceType.getValue(), 
resourceType.getValue(), identifier);
+    }
+
+    private static Set<String> getDeprecatedResources(final Set<AccessPolicy> 
policies, final String rootGroupId) {
+        final Set<String> resources = new LinkedHashSet<>();
+
+        for (final AccessPolicy policy : 
ComponentAccessPolicyDeprecationLogger.findComponentPolicies(policies, 
rootGroupId)) {
+            resources.add(policy.getResource());
+        }
+
+        return resources;
+    }
+
+    private static Set<AccessPolicy> getPolicies(final String... resources) {
+        final Set<AccessPolicy> policies = new LinkedHashSet<>();
+
+        for (final String resource : resources) {
+            final AccessPolicy policy = new AccessPolicy.Builder()
+                    .identifierGenerateFromSeed(resource)
+                    .resource(resource)
+                    .action(RequestAction.READ)
+                    .build();
+
+            policies.add(policy);
+        }
+
+        return policies;
+    }
+}

Reply via email to