This is an automated email from the ASF dual-hosted git repository.

jgresock 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 9237b4e  NIFI-9722: Do not throw an Exception from 
verifyCanUpdateProperties when property descriptor & parameter descriptor's 
sensitivities don't match - instead allow the set to happen and let processor 
become invalid. Also, allow values such as abc#{param} for ghost processors.
9237b4e is described below

commit 9237b4e923eeac6016d0ac605abf36c745ef0c63
Author: Mark Payne <[email protected]>
AuthorDate: Wed Feb 23 16:18:22 2022 -0500

    NIFI-9722: Do not throw an Exception from verifyCanUpdateProperties when 
property descriptor & parameter descriptor's sensitivities don't match - 
instead allow the set to happen and let processor become invalid. Also, allow 
values such as abc#{param} for ghost processors.
    
    Signed-off-by: Joe Gresock <[email protected]>
    
    This closes #5795.
---
 .../nifi/controller/AbstractComponentNode.java     |  24 ++--
 .../nifi/controller/TestAbstractComponentNode.java | 134 ++++++++++++++++-----
 2 files changed, 116 insertions(+), 42 deletions(-)

diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
index 943a3d3..edf26e4 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
@@ -331,7 +331,11 @@ public abstract class AbstractComponentNode implements 
ComponentNode {
 
             final PropertyDescriptor descriptor = 
getPropertyDescriptor(propertyName);
 
-            if (descriptor.isSensitive()) {
+            // We don't want to allow a sensitive property to reference a 
parameter unless the value is solely a parameter reference. I.e.,
+            // #{abc} is ok but password#{abc} is not.
+            // However, for "ghost" components (isExtensionMissing() == true) 
we need to allow this, because we consider all properties sensitive.
+            // If we don't allow this, we'll fail to even create the ghost 
component.
+            if (descriptor.isSensitive() && !isExtensionMissing()) {
                 if (referenceList.size() > 1) {
                     throw new IllegalArgumentException("The property '" + 
descriptor.getDisplayName() + "' cannot reference more than one Parameter 
because it is a sensitive property.");
                 }
@@ -342,22 +346,12 @@ public abstract class AbstractComponentNode implements 
ComponentNode {
                         throw new IllegalArgumentException("The property '" + 
descriptor.getDisplayName() + "' is a sensitive property so it can reference a 
Parameter only if there is no other " +
                             "context around the value. For instance, the value 
'#{abc}' is allowed but 'password#{abc}' is not allowed.");
                     }
-
-                    final ParameterContext parameterContext = 
getParameterContext();
-                    if (parameterContext != null) {
-                        final Optional<Parameter> parameter = 
parameterContext.getParameter(reference.getParameterName());
-                        if (parameter.isPresent() && 
!parameter.get().getDescriptor().isSensitive()) {
-                            throw new IllegalArgumentException("The property 
'" + descriptor.getDisplayName() + "' is a sensitive property, so it can only 
reference Parameters that are sensitive.");
-                        }
-                    }
                 }
             }
 
-            if (descriptor.getControllerServiceDefinition() != null) {
-                if (!referenceList.isEmpty()) {
-                    throw new IllegalArgumentException("The property '" + 
descriptor.getDisplayName() + "' cannot reference a Parameter because the 
property is a Controller Service reference. " +
-                        "Allowing Controller Service references to make use of 
Parameters could result in security issues and a poor user experience. As a 
result, this is not allowed.");
-                }
+            if (descriptor.getControllerServiceDefinition() != null && 
!referenceList.isEmpty()) {
+                throw new IllegalArgumentException("The property '" + 
descriptor.getDisplayName() + "' cannot reference a Parameter because the 
property is a Controller Service reference. " +
+                    "Allowing Controller Service references to make use of 
Parameters could result in security issues and a poor user experience. As a 
result, this is not allowed.");
             }
         }
     }
@@ -811,7 +805,9 @@ public abstract class AbstractComponentNode implements 
ComponentNode {
                             .valid(false)
                             .explanation("Property references Parameter '" + 
paramName + "' but the currently selected Parameter Context does not have a 
Parameter with that name")
                             .build());
+                    continue;
                 }
+
                 final Optional<Parameter> parameterRef = 
parameterContext.getParameter(paramName);
                 if (parameterRef.isPresent()) {
                     final ParameterDescriptor parameterDescriptor = 
parameterRef.get().getDescriptor();
diff --git 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
index 4db5892..d43a450 100644
--- 
a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
+++ 
b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/test/java/org/apache/nifi/controller/TestAbstractComponentNode.java
@@ -25,7 +25,9 @@ import org.apache.nifi.components.PropertyDescriptor;
 import org.apache.nifi.components.PropertyValue;
 import org.apache.nifi.components.ValidationContext;
 import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.components.Validator;
 import org.apache.nifi.components.validation.EnablingServiceValidationResult;
+import org.apache.nifi.components.validation.ValidationState;
 import org.apache.nifi.components.validation.ValidationStatus;
 import org.apache.nifi.components.validation.ValidationTrigger;
 import org.apache.nifi.controller.service.ControllerServiceNode;
@@ -55,6 +57,9 @@ import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 public class TestAbstractComponentNode {
@@ -66,6 +71,7 @@ public class TestAbstractComponentNode {
         assertEquals(ValidationStatus.VALIDATING, status);
     }
 
+    @Test
     public void testOnParametersModified() {
         final AtomicLong validationCount = new AtomicLong(0L);
         final ValidationTrigger validationTrigger = new ValidationTrigger() {
@@ -89,10 +95,6 @@ public class TestAbstractComponentNode {
             }
         };
 
-        final Map<String, String> properties = new HashMap<>();
-        properties.put("abc", "#{abc}");
-        node.setProperties(properties);
-
         final ParameterContext context = Mockito.mock(ParameterContext.class);
         final ParameterDescriptor paramDescriptor = new 
ParameterDescriptor.Builder()
             .name("abc")
@@ -100,18 +102,73 @@ public class TestAbstractComponentNode {
             .sensitive(false)
             .build();
         final Parameter param = new Parameter(paramDescriptor, "123");
-        Mockito.doReturn(param).when(context).getParameter("abc");
+        Mockito.doReturn(Optional.of(param)).when(context).getParameter("abc");
+        node.setParameterContext(context);
+
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("abc", "#{abc}");
+        node.setProperties(properties);
+
+        assertEquals(1, propertyModifications.size());
+        PropertyModification mod = propertyModifications.get(0);
+        assertNull(mod.getPreviousValue());
+        assertEquals("123", mod.getUpdatedValue());
+        propertyModifications.clear();
 
         final Map<String, ParameterUpdate> updatedParameters = new HashMap<>();
-        updatedParameters.put("abc", new MockParameterUpdate("abc", "xyz", 
"123", false));
+        updatedParameters.put("abc", new MockParameterUpdate("abc", 
"old-value", "123", false));
         node.onParametersModified(updatedParameters);
 
         assertEquals(1, propertyModifications.size());
-        final PropertyModification mod = propertyModifications.get(0);
-        assertEquals("xyz", mod.getPreviousValue());
+        mod = propertyModifications.get(0);
+        assertEquals("old-value", mod.getPreviousValue());
         assertEquals("123", mod.getUpdatedValue());
     }
 
+    @Test
+    public void testMismatchedSensitiveFlags() {
+        final LocalComponentNode node = new LocalComponentNode();
+
+        final ParameterContext context = Mockito.mock(ParameterContext.class);
+        final ParameterDescriptor paramDescriptor = new 
ParameterDescriptor.Builder()
+            .name("abc")
+            .description("")
+            .sensitive(true)
+            .build();
+        final Parameter param = new Parameter(paramDescriptor, "123");
+        Mockito.doReturn(Optional.of(param)).when(context).getParameter("abc");
+        node.setParameterContext(context);
+
+        final String propertyValue = "#{abc}";
+        final PropertyDescriptor propertyDescriptor = new 
PropertyDescriptor.Builder()
+            .name("abc")
+            .sensitive(false)
+            .dynamic(true)
+            .addValidator(Validator.VALID)
+            .build();
+
+        final Map<String, String> properties = new HashMap<>();
+        properties.put("abc", propertyValue);
+        node.verifyCanUpdateProperties(properties);
+        node.setProperties(properties);
+
+        final ValidationContext validationContext = 
Mockito.mock(ValidationContext.class);
+        
Mockito.when(validationContext.getProperties()).thenReturn(Collections.singletonMap(propertyDescriptor,
 propertyValue));
+        
Mockito.when(validationContext.getAllProperties()).thenReturn(properties);
+        
Mockito.when(validationContext.isDependencySatisfied(Mockito.any(PropertyDescriptor.class),
 Mockito.any(Function.class))).thenReturn(true);
+        
Mockito.when(validationContext.getReferencedParameters(Mockito.anyString())).thenReturn(Collections.singleton("abc"));
+        
Mockito.when(validationContext.isParameterDefined("abc")).thenReturn(true);
+
+        final ValidationState validationState = 
node.performValidation(validationContext);
+        assertSame(ValidationStatus.INVALID, validationState.getStatus());
+
+        final Collection<ValidationResult> results = 
validationState.getValidationErrors();
+        assertEquals(1, results.size());
+        final ValidationResult result = results.iterator().next();
+        assertFalse(result.isValid());
+        
assertTrue(result.getExplanation().toLowerCase().contains("sensitivity"));
+    }
+
     @Test(timeout = 10000)
     public void testValidationTriggerPaused() throws InterruptedException {
         final AtomicLong validationCount = new AtomicLong(0L);
@@ -191,30 +248,17 @@ public class TestAbstractComponentNode {
         return context;
     }
 
-    private static class ValidationControlledAbstractComponentNode extends 
AbstractComponentNode {
-        private final long pauseMillis;
+    private static class LocalComponentNode extends AbstractComponentNode {
         private volatile ParameterContext paramContext = null;
 
-        public ValidationControlledAbstractComponentNode(final long 
pauseMillis, final ValidationTrigger validationTrigger) {
-            this(pauseMillis, validationTrigger, 
Mockito.mock(ControllerServiceProvider.class));
+        public LocalComponentNode() {
+            this(Mockito.mock(ControllerServiceProvider.class), 
Mockito.mock(ValidationTrigger.class));
         }
 
-        public ValidationControlledAbstractComponentNode(final long 
pauseMillis, final ValidationTrigger validationTrigger, final 
ControllerServiceProvider controllerServiceProvider) {
+        public LocalComponentNode(final ControllerServiceProvider 
controllerServiceProvider, final ValidationTrigger validationTrigger) {
             super("id", Mockito.mock(ValidationContextFactory.class), 
controllerServiceProvider, "unit test component",
-                    
ValidationControlledAbstractComponentNode.class.getCanonicalName(), 
Mockito.mock(ComponentVariableRegistry.class), 
Mockito.mock(ReloadComponent.class),
-                    Mockito.mock(ExtensionManager.class), validationTrigger, 
false);
-
-            this.pauseMillis = pauseMillis;
-        }
-
-        @Override
-        protected Collection<ValidationResult> 
computeValidationErrors(ValidationContext context) {
-            try {
-                Thread.sleep(pauseMillis);
-            } catch (final InterruptedException ie) {
-            }
-
-            return null;
+                
ValidationControlledAbstractComponentNode.class.getCanonicalName(), 
Mockito.mock(ComponentVariableRegistry.class), 
Mockito.mock(ReloadComponent.class),
+                Mockito.mock(ExtensionManager.class), validationTrigger, 
false);
         }
 
         @Override
@@ -228,7 +272,17 @@ public class TestAbstractComponentNode {
 
         @Override
         public ConfigurableComponent getComponent() {
-            return Mockito.mock(ConfigurableComponent.class);
+            final ConfigurableComponent component = 
Mockito.mock(ConfigurableComponent.class);
+            
Mockito.when(component.getPropertyDescriptor(Mockito.anyString())).thenAnswer(invocation
 -> {
+                final String propertyName = invocation.getArgument(0, 
String.class);
+                return new PropertyDescriptor.Builder()
+                    .name(propertyName)
+                    .addValidator(Validator.VALID)
+                    .dynamic(true)
+                    .build();
+            });
+
+            return component;
         }
 
         @Override
@@ -290,6 +344,30 @@ public class TestAbstractComponentNode {
         }
     }
 
+
+    private static class ValidationControlledAbstractComponentNode extends 
LocalComponentNode {
+        private final long pauseMillis;
+
+        public ValidationControlledAbstractComponentNode(final long 
pauseMillis, final ValidationTrigger validationTrigger) {
+            this(pauseMillis, validationTrigger, 
Mockito.mock(ControllerServiceProvider.class));
+        }
+
+        public ValidationControlledAbstractComponentNode(final long 
pauseMillis, final ValidationTrigger validationTrigger, final 
ControllerServiceProvider controllerServiceProvider) {
+            super(controllerServiceProvider, validationTrigger);
+            this.pauseMillis = pauseMillis;
+        }
+
+        @Override
+        protected Collection<ValidationResult> 
computeValidationErrors(ValidationContext context) {
+            try {
+                Thread.sleep(pauseMillis);
+            } catch (final InterruptedException ie) {
+            }
+
+            return null;
+        }
+    }
+
     private static class PropertyModification {
         private final PropertyDescriptor propertyDescriptor;
         private final String previousValue;

Reply via email to