Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,11 @@ public void verifyCanUpdateProperties(final Map<String, String> properties) {

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.");
}
Expand All @@ -342,22 +346,12 @@ public void verifyCanUpdateProperties(final Map<String, String> properties) {
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.");
}
}
}
Expand Down Expand Up @@ -811,7 +805,9 @@ private List<ValidationResult> validateParameterReferences(final ValidationConte
.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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@
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;
Expand Down Expand Up @@ -55,6 +57,9 @@
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 {
Expand All @@ -66,6 +71,7 @@ public void testGetValidationStatusWithTimeout() {
assertEquals(ValidationStatus.VALIDATING, status);
}

@Test
public void testOnParametersModified() {
final AtomicLong validationCount = new AtomicLong(0L);
final ValidationTrigger validationTrigger = new ValidationTrigger() {
Expand All @@ -89,29 +95,80 @@ protected void onPropertyModified(final PropertyDescriptor descriptor, final Str
}
};

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")
.description("")
.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);
Expand Down Expand Up @@ -191,30 +248,17 @@ private ValidationContext getServiceValidationContext(final ControllerServiceSta
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
Expand All @@ -228,7 +272,17 @@ public BundleCoordinate getBundleCoordinate() {

@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
Expand Down Expand Up @@ -290,6 +344,30 @@ protected void setParameterContext(final ParameterContext parameterContext) {
}
}


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;
Expand Down