NIFI-9959 Add UI Support for Sensitive Dynamic Properties (#6073)

* NIFI-9959 Added UI Support for Sensitive Dynamic Properties

- Added SupportsSensitiveDynamicProperties to DBCPConnectionPool and ScriptedReportingTask

* NIFI-9959 Added sensitive parameter argument for Controller Service descriptors

* NIFI-9959 Adjusted sensitive property descriptor handling to support changing status

* NIFI-9959 Added info icon for Sensitive Value field

* NIFI-9959 Corrected handling of descriptor for existing dynamic properties

* NIFI-9959 Cleaning up dialog markup.

Co-authored-by: Matt Gilman <matt.c.gilman@gmail.com>

This closes #6073
This commit is contained in:
exceptionfactory 2022-05-25 20:36:31 -05:00 committed by GitHub
parent f66540eb6d
commit 83316736f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 189 additions and 97 deletions

View File

@ -996,6 +996,12 @@ public abstract class AbstractComponentNode implements ComponentNode {
return foundApiDependency;
}
@Override
public boolean isSensitiveDynamicProperty(final String name) {
Objects.requireNonNull(name, "Property Name required");
return sensitiveDynamicPropertyNames.get().contains(name);
}
@Override
public PropertyDescriptor getPropertyDescriptor(final String name) {
try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getComponent().getClass(), getComponent().getIdentifier())) {

View File

@ -261,6 +261,11 @@ public interface ComponentNode extends ComponentAuthorizable {
*/
PropertyDescriptor getPropertyDescriptor(String name);
/**
* @param name Property Name
* @return Sensitive Dynamic Property status
*/
boolean isSensitiveDynamicProperty(String name);
@Override
default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {

View File

@ -616,9 +616,10 @@ public interface NiFiServiceFacade {
*
* @param id id
* @param property property
* @param sensitive requested sensitive status
* @return descriptor
*/
PropertyDescriptorDTO getProcessorPropertyDescriptor(String id, String property);
PropertyDescriptorDTO getProcessorPropertyDescriptor(String id, String property, boolean sensitive);
/**
* Gets all the Processor transfer objects for this controller.
@ -2046,9 +2047,10 @@ public interface NiFiServiceFacade {
*
* @param id id
* @param property property
* @param sensitive requested sensitive status
* @return property
*/
PropertyDescriptorDTO getControllerServicePropertyDescriptor(String id, String property);
PropertyDescriptorDTO getControllerServicePropertyDescriptor(String id, String property, boolean sensitive);
/**
* Gets the references for specified controller service.
@ -2168,9 +2170,10 @@ public interface NiFiServiceFacade {
*
* @param id id
* @param property property
* @param sensitive requested sensitive status
* @return descriptor
*/
PropertyDescriptorDTO getReportingTaskPropertyDescriptor(String id, String property);
PropertyDescriptorDTO getReportingTaskPropertyDescriptor(String id, String property, boolean sensitive);
/**
* Updates the specified reporting task.

View File

@ -3642,15 +3642,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
public PropertyDescriptorDTO getProcessorPropertyDescriptor(final String id, final String property) {
public PropertyDescriptorDTO getProcessorPropertyDescriptor(final String id, final String property, final boolean sensitive) {
final ProcessorNode processor = processorDAO.getProcessor(id);
PropertyDescriptor descriptor = processor.getPropertyDescriptor(property);
// return an invalid descriptor if the processor doesn't support this property
if (descriptor == null) {
descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
}
final PropertyDescriptor descriptor = getPropertyDescriptor(processor, property, sensitive);
return dtoFactory.createPropertyDescriptorDto(descriptor, processor.getProcessGroup().getIdentifier());
}
@ -4511,15 +4505,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
public PropertyDescriptorDTO getControllerServicePropertyDescriptor(final String id, final String property) {
public PropertyDescriptorDTO getControllerServicePropertyDescriptor(final String id, final String property, final boolean sensitive) {
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(id);
PropertyDescriptor descriptor = controllerService.getPropertyDescriptor(property);
// return an invalid descriptor if the controller service doesn't support this property
if (descriptor == null) {
descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
}
final PropertyDescriptor descriptor = getPropertyDescriptor(controllerService, property, sensitive);
final String groupId = controllerService.getProcessGroup() == null ? null : controllerService.getProcessGroup().getIdentifier();
return dtoFactory.createPropertyDescriptorDto(descriptor, groupId);
}
@ -4555,15 +4543,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
}
@Override
public PropertyDescriptorDTO getReportingTaskPropertyDescriptor(final String id, final String property) {
public PropertyDescriptorDTO getReportingTaskPropertyDescriptor(final String id, final String property, final boolean sensitive) {
final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(id);
PropertyDescriptor descriptor = reportingTask.getPropertyDescriptor(property);
// return an invalid descriptor if the reporting task doesn't support this property
if (descriptor == null) {
descriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
}
final PropertyDescriptor descriptor = getPropertyDescriptor(reportingTask, property, sensitive);
return dtoFactory.createPropertyDescriptorDto(descriptor, null);
}
@ -5887,6 +5869,29 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
};
}
private PropertyDescriptor getPropertyDescriptor(final ComponentNode componentNode, final String property, final boolean sensitive) {
final PropertyDescriptor propertyDescriptor;
final PropertyDescriptor componentDescriptor = componentNode.getPropertyDescriptor(property);
if (componentDescriptor == null) {
propertyDescriptor = new PropertyDescriptor.Builder().name(property).addValidator(Validator.INVALID).dynamic(true).build();
} else if (
componentDescriptor.isDynamic() && (
// Allow setting sensitive status for properties marked as sensitive in previous requests
componentNode.isSensitiveDynamicProperty(property) || (
// Allow setting sensitive status for properties not marked as sensitive in supporting components
!componentDescriptor.isSensitive() && componentNode.isSupportsSensitiveDynamicProperties()
)
)
) {
propertyDescriptor = new PropertyDescriptor.Builder().fromPropertyDescriptor(componentDescriptor).sensitive(sensitive).build();
} else {
propertyDescriptor = componentDescriptor;
}
return propertyDescriptor;
}
@Override
public void verifyPublicInputPortUniqueness(final String portId, final String portName) {
inputPortDAO.verifyPublicPortUniqueness(portId, portName);

View File

@ -281,12 +281,7 @@ public class ControllerServiceResource extends ApplicationResource {
});
// get the property descriptor
final PropertyDescriptorDTO descriptor = serviceFacade.getControllerServicePropertyDescriptor(id, propertyName);
// Adjust sensitive status for dynamic properties when sensitive status enabled
if (descriptor.isDynamic() && sensitive) {
descriptor.setSensitive(true);
}
final PropertyDescriptorDTO descriptor = serviceFacade.getControllerServicePropertyDescriptor(id, propertyName, sensitive);
// generate the response entity
final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();

View File

@ -427,12 +427,7 @@ public class ProcessorResource extends ApplicationResource {
});
// get the property descriptor
final PropertyDescriptorDTO descriptor = serviceFacade.getProcessorPropertyDescriptor(id, propertyName);
// Adjust sensitive status for dynamic properties when sensitive status enabled
if (descriptor.isDynamic() && sensitive) {
descriptor.setSensitive(true);
}
final PropertyDescriptorDTO descriptor = serviceFacade.getProcessorPropertyDescriptor(id, propertyName, sensitive);
// generate the response entity
final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();

View File

@ -267,12 +267,7 @@ public class ReportingTaskResource extends ApplicationResource {
});
// get the property descriptor
final PropertyDescriptorDTO descriptor = serviceFacade.getReportingTaskPropertyDescriptor(id, propertyName);
// Adjust sensitive status for dynamic properties when sensitive status enabled
if (descriptor.isDynamic() && sensitive) {
descriptor.setSensitive(true);
}
final PropertyDescriptorDTO descriptor = serviceFacade.getReportingTaskPropertyDescriptor(id, propertyName, sensitive);
// generate the response entity
final PropertyDescriptorEntity entity = new PropertyDescriptorEntity();

View File

@ -105,6 +105,7 @@
_) {
var groupId = null;
var supportsSensitiveDynamicProperties = false;
var propertyVerificationCallback = null;
var COMBO_MIN_WIDTH = 212;
var EDITOR_MIN_WIDTH = 212;
@ -1211,7 +1212,8 @@
contentType: 'application/json'
}).done(function (response) {
// load the descriptor and update the property
configurationOptions.descriptorDeferred(item.property).done(function (descriptorResponse) {
// Controller Service dynamic property descriptors will not be marked as sensitive
configurationOptions.descriptorDeferred(item.property, false).done(function (descriptorResponse) {
var descriptor = descriptorResponse.propertyDescriptor;
// store the descriptor for use later
@ -1600,6 +1602,10 @@
hidden: true
}));
// Delete property descriptor
var descriptors = table.data('descriptors');
delete descriptors[property.property];
// prevents standard edit logic
e.stopImmediatePropagation();
} else if (target.hasClass('go-to-service')) {
@ -1829,6 +1835,17 @@
return properties;
};
var getSensitiveDynamicPropertyNames = function (table) {
var sensitiveDynamicPropertyNames = [];
var descriptors = table.data('descriptors');
$.each(descriptors, function () {
if (nfCommon.isSensitiveProperty(this) === true && nfCommon.isDynamicProperty(this) === true) {
sensitiveDynamicPropertyNames.push(this.name);
}
});
return sensitiveDynamicPropertyNames;
};
/**
* Performs the filtering.
*
@ -2028,21 +2045,35 @@
var newPropertyDialogMarkup =
'<div id="new-property-dialog" class="dialog cancellable small-dialog hidden">' +
'<div class="dialog-content">' +
'<div>' +
'<div class="setting-name">Property name</div>' +
'<div class="setting">' +
'<div class="setting-name">Property name</div>' +
'<div class="setting-field new-property-name-container">' +
'<input class="new-property-name" type="text"/>' +
'</div>' +
'</div>' +
'<div class="setting">' +
'<div class="setting-name">Sensitive Value ' +
'<span class="fa fa-question-circle" alt="Info"' +
' title="Components must declare support for Sensitive Dynamic Properties to enable selection of Sensitive Value status.' +
' Components that flag dynamic properties as sensitive do not allow Sensitive Value status to be changed."' +
'>' +
'</span>' +
'</div>' +
'<div class="setting-field">' +
'<input id="value-sensitive-radio-button" type="radio" name="sensitive" value="sensitive" /> Yes' +
'<input id="value-not-sensitive-radio-button" type="radio" name="sensitive" value="plain" style="margin-left: 20px;"/> No' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
var newPropertyDialog = $(newPropertyDialogMarkup).appendTo(options.dialogContainer);
var newPropertyNameField = newPropertyDialog.find('input.new-property-name');
var valueSensitiveField = newPropertyDialog.find('#value-sensitive-radio-button');
var valueNotSensitiveField = newPropertyDialog.find('#value-not-sensitive-radio-button');
newPropertyDialog.modal({
headerText: 'Add Property',
scrollableContentStyle: 'scrollable',
buttons: [{
buttonText: 'Ok',
color: {
@ -2088,9 +2119,10 @@
}
});
if (existingItem === null) {
// load the descriptor and add the property
options.descriptorDeferred(propertyName).done(function (response) {
if (existingItem === null || existingItem.hidden === true) {
// load the descriptor with requested sensitive status
var sensitive = valueSensitiveField.prop('checked');
options.descriptorDeferred(propertyName, sensitive).done(function (response) {
var descriptor = response.propertyDescriptor;
// store the descriptor for use later
@ -2099,47 +2131,49 @@
descriptors[descriptor.name] = descriptor;
}
// add a row for the new property
var id = propertyData.getItems().length;
propertyData.addItem({
id: id,
hidden: false,
property: propertyName,
displayName: propertyName,
previousValue: null,
value: null,
type: 'userDefined'
});
// add the property when existing item not found
if (existingItem === null) {
// add a row for the new property
var id = propertyData.getItems().length;
propertyData.addItem({
id: id,
hidden: false,
property: propertyName,
displayName: propertyName,
previousValue: null,
value: null,
type: 'userDefined'
});
// select the new properties row
var row = propertyData.getRowById(id);
propertyGrid.setActiveCell(row, propertyGrid.getColumnIndex('value'));
propertyGrid.editActiveCell();
// select the new properties row
var row = propertyData.getRowById(id);
propertyGrid.setActiveCell(row, propertyGrid.getColumnIndex('value'));
propertyGrid.editActiveCell();
} else {
// if this row is currently hidden, clear the value and show it
propertyData.updateItem(existingItem.id, $.extend(existingItem, {
hidden: false,
previousValue: null,
value: null
}));
// select the new properties row
var row = propertyData.getRowById(existingItem.id);
propertyGrid.invalidateRow(row);
propertyGrid.render();
propertyGrid.setActiveCell(row, propertyGrid.getColumnIndex('value'));
propertyGrid.editActiveCell();
}
});
} else {
// if this row is currently hidden, clear the value and show it
if (existingItem.hidden === true) {
propertyData.updateItem(existingItem.id, $.extend(existingItem, {
hidden: false,
previousValue: null,
value: null
}));
// select the new properties row
var row = propertyData.getRowById(existingItem.id);
propertyGrid.setActiveCell(row, propertyGrid.getColumnIndex('value'));
propertyGrid.editActiveCell();
} else {
nfDialog.showOkDialog({
headerText: 'Property Exists',
dialogContent: 'A property with this name already exists.'
});
// select the existing properties row
var row = propertyData.getRowById(existingItem.id);
propertyGrid.setSelectedRows([row]);
propertyGrid.scrollRowIntoView(row);
}
nfDialog.showOkDialog({
headerText: 'Property Exists',
dialogContent: 'A property with this name already exists.'
});
// select the existing properties row
var row = propertyData.getRowById(existingItem.id);
propertyGrid.setSelectedRows([row]);
propertyGrid.scrollRowIntoView(row);
}
} else {
nfDialog.showOkDialog({
@ -2184,6 +2218,15 @@
// set the initial focus
newPropertyNameField.focus();
// Set initial Sensitive Value radio button status
valueSensitiveField.prop('checked', false);
valueNotSensitiveField.prop('checked', true);
// Set disabled status based on component support indicated
var sensitiveFieldDisabled = supportsSensitiveDynamicProperties !== true;
valueSensitiveField.prop('disabled', sensitiveFieldDisabled);
valueNotSensitiveField.prop('disabled', sensitiveFieldDisabled);
}).appendTo(addProperty);
// build the control to trigger verification
@ -2333,6 +2376,22 @@
return properties;
},
/**
* Get Sensitive Dynamic Property Names based on Property Descriptor status
*/
getSensitiveDynamicPropertyNames: function () {
var sensitiveDynamicPropertyNames = [];
this.each(function () {
// get the property grid data
var table = $(this).find('div.property-table');
sensitiveDynamicPropertyNames = getSensitiveDynamicPropertyNames(table);
return false;
});
return sensitiveDynamicPropertyNames;
},
/**
* Sets the current group id. This is used to indicate where inline Controller Services are created
* and to obtain the parameter context.
@ -2343,6 +2402,15 @@
});
},
/**
* Set Support status for Sensitive Dynamic Properties
*/
setSupportsSensitiveDynamicProperties: function (currentSupportsSensitiveDynamicProperties) {
return this.each(function () {
supportsSensitiveDynamicProperties = currentSupportsSensitiveDynamicProperties;
});
},
/**
* Sets the property verification callback.
*/

View File

@ -120,6 +120,7 @@
if ($.isEmptyObject(properties) === false) {
controllerServiceDto['properties'] = properties;
}
controllerServiceDto['sensitiveDynamicPropertyNames'] = $('#controller-service-properties').propertytable('getSensitiveDynamicPropertyNames');
// create the controller service entity
var controllerServiceEntity = {};
@ -1518,14 +1519,16 @@
* Gets a property descriptor for the controller service currently being configured.
*
* @param {type} propertyName
* @param {type} sensitive Requested sensitive status
*/
var getControllerServicePropertyDescriptor = function (propertyName) {
var getControllerServicePropertyDescriptor = function (propertyName, sensitive) {
var controllerServiceEntity = $('#controller-service-configuration').data('controllerServiceDetails');
return $.ajax({
type: 'GET',
url: controllerServiceEntity.uri + '/descriptors',
data: {
propertyName: propertyName
propertyName: propertyName,
sensitive: sensitive
},
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
@ -2113,6 +2116,7 @@
// load the property table
$('#controller-service-properties')
.propertytable('setGroupId', controllerService.parentGroupId)
.propertytable('setSupportsSensitiveDynamicProperties', controllerService.supportsSensitiveDynamicProperties)
.propertytable('loadProperties', controllerService.properties, controllerService.descriptors, controllerServiceHistory.propertyHistory)
.propertytable('setPropertyVerificationCallback', function (proposedProperties) {
nfVerify.verify(controllerService['id'], controllerServiceEntity['uri'], proposedProperties, referencedAttributes, handleVerificationResults, $('#controller-service-properties-verification-results-listing'));
@ -2257,6 +2261,7 @@
// load the property table
$('#controller-service-properties')
.propertytable('setGroupId', controllerService.parentGroupId)
.propertytable('setSupportsSensitiveDynamicProperties', controllerService.supportsSensitiveDynamicProperties)
.propertytable('loadProperties', controllerService.properties, controllerService.descriptors, controllerServiceHistory.propertyHistory);
// show the details

View File

@ -387,6 +387,10 @@
processorConfigDto['properties'] = properties;
}
if (processor.supportsSensitiveDynamicProperties === true) {
processorConfigDto['sensitiveDynamicPropertyNames'] = $('#processor-properties').propertytable('getSensitiveDynamicPropertyNames');
}
// create the processor dto
var processorDto = {};
processorDto['id'] = $('#processor-id').text();
@ -697,14 +701,15 @@
readOnly: false,
supportsGoTo: true,
dialogContainer: '#new-processor-property-container',
descriptorDeferred: function (propertyName) {
descriptorDeferred: function (propertyName, sensitive) {
var processor = $('#processor-configuration').data('processorDetails');
var d = nfProcessor.get(processor.id);
return $.ajax({
type: 'GET',
url: d.uri + '/descriptors',
data: {
propertyName: propertyName
propertyName: propertyName,
sensitive: sensitive
},
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
@ -1094,6 +1099,7 @@
// load the property table
$('#processor-properties')
.propertytable('setGroupId', processor.parentGroupId)
.propertytable('setSupportsSensitiveDynamicProperties', processor.supportsSensitiveDynamicProperties)
.propertytable('loadProperties', processor.config.properties, processor.config.descriptors, processorHistory.propertyHistory)
.propertytable('setPropertyVerificationCallback', function (proposedProperties) {
nfVerify.verify(processor['id'], processorResponse['uri'], proposedProperties, referencedAttributes, handleVerificationResults, $('#processor-properties-verification-results-listing'));

View File

@ -168,6 +168,7 @@
if ($.isEmptyObject(properties) === false) {
reportingTaskDto['properties'] = properties;
}
reportingTaskDto['sensitiveDynamicPropertyNames'] = $('#reporting-task-properties').propertytable('getSensitiveDynamicPropertyNames');
// create the reporting task entity
var reportingTaskEntity = {};
@ -313,14 +314,16 @@
* Gets a property descriptor for the controller service currently being configured.
*
* @param {type} propertyName
* @param {type} sensitive Requested sensitive status
*/
var getReportingTaskPropertyDescriptor = function (propertyName) {
var getReportingTaskPropertyDescriptor = function (propertyName, sensitive) {
var details = $('#reporting-task-configuration').data('reportingTaskDetails');
return $.ajax({
type: 'GET',
url: details.uri + '/descriptors',
data: {
propertyName: propertyName
propertyName: propertyName,
sensitive: sensitive
},
dataType: 'json'
}).fail(nfErrorHandler.handleAjaxError);
@ -643,6 +646,7 @@
// load the property table
$('#reporting-task-properties')
.propertytable('setGroupId', null)
.propertytable('setSupportsSensitiveDynamicProperties', reportingTask.supportsSensitiveDynamicProperties)
.propertytable('loadProperties', reportingTask.properties, reportingTask.descriptors, reportingTaskHistory.propertyHistory)
.propertytable('setPropertyVerificationCallback', function (proposedProperties) {
nfVerify.verify(reportingTask['id'], reportingTaskEntity['uri'], proposedProperties, referencedAttributes, handleVerificationResults, $('#reporting-task-properties-verification-results-listing'));
@ -767,6 +771,7 @@
// load the property table
$('#reporting-task-properties')
.propertytable('setGroupId', null)
.propertytable('setSupportsSensitiveDynamicProperties', reportingTask.supportsSensitiveDynamicProperties)
.propertytable('loadProperties', reportingTask.properties, reportingTask.descriptors, reportingTaskHistory.propertyHistory);
// show the details

View File

@ -20,6 +20,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.nifi.annotation.behavior.DynamicProperty;
import org.apache.nifi.annotation.behavior.Restricted;
import org.apache.nifi.annotation.behavior.Restriction;
import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
@ -54,6 +55,7 @@ import java.util.Map;
/**
* A Reporting task whose body is provided by a script (via supported JSR-223 script engines)
*/
@SupportsSensitiveDynamicProperties
@Tags({"reporting", "script", "execute", "groovy", "python", "jython", "jruby", "ruby", "javascript", "js", "lua", "luaj"})
@CapabilityDescription("Provides reporting and status information to a script. ReportingContext, ComponentLog, and VirtualMachineMetrics objects are made available "
+ "as variables (context, log, and vmMetrics, respectively) to the script for further processing. The context makes various information available such "

View File

@ -22,6 +22,7 @@ import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.apache.nifi.annotation.behavior.DynamicProperties;
import org.apache.nifi.annotation.behavior.DynamicProperty;
import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
import org.apache.nifi.annotation.behavior.SupportsSensitiveDynamicProperties;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnDisabled;
@ -70,6 +71,7 @@ import static org.apache.nifi.components.ConfigVerificationResult.Outcome.SUCCES
* Implementation of for Database Connection Pooling Service. Apache DBCP is used for connection pooling functionality.
*
*/
@SupportsSensitiveDynamicProperties
@Tags({ "dbcp", "jdbc", "database", "connection", "pooling", "store" })
@CapabilityDescription("Provides Database Connection Pooling Service. Connections can be asked from pool and returned after usage.")
@DynamicProperties({