NIFI-2542: - Ensuring transitive referencing components are able to be returned. - Ensuring we can enable/disable services with transitive referencing components. - Ensuring we cannot enable/disable services with unauthorized referencing components.

NIFI-2543: - Ensuring we have permissions before attempting to reload a controller service.

This closes #837

Signed-off-by: jpercivall <joepercivall@yahoo.com>
This commit is contained in:
Matt Gilman 2016-08-11 10:52:04 -04:00 committed by jpercivall
parent 25a2fac453
commit fa639e2596
3 changed files with 150 additions and 75 deletions

View File

@ -226,19 +226,32 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
return COUNTERS_AUTHORIZABLE; return COUNTERS_AUTHORIZABLE;
} }
@Override private ConfiguredComponent findControllerServiceReferencingComponent(final ControllerServiceReference referencingComponents, final String id) {
public Authorizable getControllerServiceReferencingComponent(String controllerSeriveId, String id) {
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerSeriveId);
final ControllerServiceReference referencingComponents = controllerService.getReferences();
ConfiguredComponent reference = null; ConfiguredComponent reference = null;
for (final ConfiguredComponent component : referencingComponents.getReferencingComponents()) { for (final ConfiguredComponent component : referencingComponents.getReferencingComponents()) {
if (component.getIdentifier().equals(id)) { if (component.getIdentifier().equals(id)) {
reference = component; reference = component;
break; break;
} }
if (component instanceof ControllerServiceNode) {
final ControllerServiceNode refControllerService = (ControllerServiceNode) component;
reference = findControllerServiceReferencingComponent(refControllerService.getReferences(), id);
if (reference != null) {
break;
}
}
} }
return reference;
}
@Override
public Authorizable getControllerServiceReferencingComponent(String controllerSeriveId, String id) {
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerSeriveId);
final ControllerServiceReference referencingComponents = controllerService.getReferences();
final ConfiguredComponent reference = findControllerServiceReferencingComponent(referencingComponents, id);
if (reference == null) { if (reference == null) {
throw new ResourceNotFoundException("Unable to find referencing component with id " + id); throw new ResourceNotFoundException("Unable to find referencing component with id " + id);
} }

View File

@ -1676,13 +1676,32 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final RevisionUpdate<ControllerServiceDTO> snapshot = updateComponent(revision, final RevisionUpdate<ControllerServiceDTO> snapshot = updateComponent(revision,
controllerService, controllerService,
() -> controllerServiceDAO.updateControllerService(controllerServiceDTO), () -> controllerServiceDAO.updateControllerService(controllerServiceDTO),
cs -> dtoFactory.createControllerServiceDto(cs)); cs -> {
final ControllerServiceDTO dto = dtoFactory.createControllerServiceDto(cs);
final ControllerServiceReference ref = controllerService.getReferences();
final ControllerServiceReferencingComponentsEntity referencingComponentsEntity =
createControllerServiceReferencingComponentsEntity(ref, Sets.newHashSet(controllerService.getIdentifier()));
dto.setReferencingComponents(referencingComponentsEntity.getControllerServiceReferencingComponents());
return dto;
});
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService); final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId())); final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletins); return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletins);
} }
private Set<ConfiguredComponent> findAllReferencingComponents(final ControllerServiceReference reference) {
final Set<ConfiguredComponent> referencingComponents = new HashSet<>(reference.getReferencingComponents());
for (final ConfiguredComponent referencingComponent : reference.getReferencingComponents()) {
if (referencingComponent instanceof ControllerServiceNode) {
referencingComponents.addAll(findAllReferencingComponents(((ControllerServiceNode) referencingComponent).getReferences()));
}
}
return referencingComponents;
}
@Override @Override
public ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingComponents( public ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingComponents(
final Map<String, Revision> referenceRevisions, final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) { final Map<String, Revision> referenceRevisions, final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) {
@ -1705,13 +1724,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
updatedRevisions.put(component.getIdentifier(), currentRevision.incrementRevision(requestRevision.getClientId())); updatedRevisions.put(component.getIdentifier(), currentRevision.incrementRevision(requestRevision.getClientId()));
} }
// return the current revision if the component wasn't updated // ensure the revision for all referencing components is included regardless of whether they were updated in this request
for (final Map.Entry<String, Revision> entry : referenceRevisions.entrySet()) { for (final ConfiguredComponent component : findAllReferencingComponents(updatedReference)) {
final String componentId = entry.getKey(); updatedRevisions.putIfAbsent(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
if (!updatedRevisions.containsKey(componentId)) {
final Revision currentRevision = revisionManager.getRevision(componentId);
updatedRevisions.put(componentId, currentRevision);
}
} }
final ControllerServiceReferencingComponentsEntity entity = createControllerServiceReferencingComponentsEntity(updatedReference, updatedRevisions); final ControllerServiceReferencingComponentsEntity entity = createControllerServiceReferencingComponentsEntity(updatedReference, updatedRevisions);
@ -1757,6 +1772,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
for (final ConfiguredComponent component : reference.getReferencingComponents()) { for (final ConfiguredComponent component : reference.getReferencingComponents()) {
referencingRevisions.put(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier())); referencingRevisions.put(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
} }
return createControllerServiceReferencingComponentsEntity(reference, referencingRevisions); return createControllerServiceReferencingComponentsEntity(reference, referencingRevisions);
} }
@ -1806,7 +1822,12 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
// if we haven't encountered this service before include it's referencing components // if we haven't encountered this service before include it's referencing components
if (!dto.getReferenceCycle()) { if (!dto.getReferenceCycle()) {
final ControllerServiceReferencingComponentsEntity references = createControllerServiceReferencingComponentsEntity(node.getReferences(), revisions, visited); final ControllerServiceReference refReferences = node.getReferences();
final Map<String, Revision> referencingRevisions = new HashMap<>(revisions);
for (final ConfiguredComponent component : refReferences.getReferencingComponents()) {
referencingRevisions.putIfAbsent(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
}
final ControllerServiceReferencingComponentsEntity references = createControllerServiceReferencingComponentsEntity(refReferences, referencingRevisions, visited);
dto.setReferencingComponents(references.getControllerServiceReferencingComponents()); dto.setReferencingComponents(references.getControllerServiceReferencingComponents());
} }

View File

@ -109,7 +109,7 @@ nf.ControllerService = (function () {
}; };
/** /**
* Reloads the specified controller service. It's referencing and referenced * Reloads the specified controller service if we have read permissions. It's referencing and referenced
* components are NOT reloaded. * components are NOT reloaded.
* *
* @param {jQuery} serviceTable * @param {jQuery} serviceTable
@ -123,8 +123,9 @@ nf.ControllerService = (function () {
// this may happen if controller service A references another controller // this may happen if controller service A references another controller
// service B that has been removed. attempting to enable/disable/remove A // service B that has been removed. attempting to enable/disable/remove A
// will attempt to reload B which is no longer a known service // will attempt to reload B which is no longer a known service. also ensure
if (nf.Common.isUndefined(controllerServiceEntity)) { // we have permissions to reload the service
if (nf.Common.isUndefined(controllerServiceEntity) || controllerServiceEntity.permissions.canRead === false) {
return $.Deferred(function (deferred) { return $.Deferred(function (deferred) {
deferred.reject(); deferred.reject();
}).promise(); }).promise();
@ -457,7 +458,8 @@ nf.ControllerService = (function () {
if (serviceTwist.hasClass('collapsed')) { if (serviceTwist.hasClass('collapsed')) {
var controllerServiceGrid = serviceTable.data('gridInstance'); var controllerServiceGrid = serviceTable.data('gridInstance');
var controllerServiceData = controllerServiceGrid.getData(); var controllerServiceData = controllerServiceGrid.getData();
var referencingService = controllerServiceData.getItemById(referencingComponent.id); var referencingServiceEntity = controllerServiceData.getItemById(referencingComponent.id);
var referencingService = referencingServiceEntity.component;
// create the markup for the references // create the markup for the references
createReferencingComponents(serviceTable, referencingServiceReferencesContainer, referencingService.referencingComponents); createReferencingComponents(serviceTable, referencingServiceReferencesContainer, referencingService.referencingComponents);
@ -673,6 +675,33 @@ nf.ControllerService = (function () {
return ids; return ids;
}; };
/**
* Gathers all referencing component revisions.
*
* @param referencingComponents
* @param referencingComponentRevisions
* @param serviceOnly - true includes only services, false includes only schedulable components
*/
var getReferencingComponentRevisions = function (referencingComponents, referencingComponentRevisions, serviceOnly) {
// include the revision of each referencing component
$.each(referencingComponents, function (_, referencingComponentEntity) {
var referencingComponent = referencingComponentEntity.component;
if (serviceOnly) {
if (referencingComponent.referenceType === 'ControllerService') {
referencingComponentRevisions[referencingComponentEntity.id] = nf.Client.getRevision(referencingComponentEntity);
}
} else {
if (referencingComponent.referenceType !== 'ControllerService') {
referencingComponentRevisions[referencingComponentEntity.id] = nf.Client.getRevision(referencingComponentEntity);
}
}
// recurse
getReferencingComponentRevisions(referencingComponent.referencingComponents, referencingComponentRevisions, serviceOnly);
});
};
/** /**
* Updates the scheduled state of the processors/reporting tasks referencing * Updates the scheduled state of the processors/reporting tasks referencing
* the specified controller service. * the specified controller service.
@ -683,17 +712,15 @@ nf.ControllerService = (function () {
* @param {function} pollCondition * @param {function} pollCondition
*/ */
var updateReferencingSchedulableComponents = function (serviceTable, controllerServiceEntity, running, pollCondition) { var updateReferencingSchedulableComponents = function (serviceTable, controllerServiceEntity, running, pollCondition) {
var referencingRevisions = {};
getReferencingComponentRevisions(controllerServiceEntity.component.referencingComponents, referencingRevisions, false);
var referenceEntity = { var referenceEntity = {
'id': controllerServiceEntity.id, 'id': controllerServiceEntity.id,
'state': running ? 'RUNNING' : 'STOPPED', 'state': running ? 'RUNNING' : 'STOPPED',
'referencingComponentRevisions': {} 'referencingComponentRevisions': referencingRevisions
}; };
// include the revision of each referencing component
$.each(controllerServiceEntity.component.referencingComponents, function (_, referencingComponent) {
referenceEntity.referencingComponentRevisions[referencingComponent.id] = nf.Client.getRevision(referencingComponent);
});
// issue the request to update the referencing components // issue the request to update the referencing components
var updated = $.ajax({ var updated = $.ajax({
type: 'PUT', type: 'PUT',
@ -956,18 +983,16 @@ nf.ControllerService = (function () {
* @param {function} pollCondition * @param {function} pollCondition
*/ */
var updateReferencingServices = function (serviceTable, controllerServiceEntity, enabled, pollCondition) { var updateReferencingServices = function (serviceTable, controllerServiceEntity, enabled, pollCondition) {
var referencingRevisions = {};
getReferencingComponentRevisions(controllerServiceEntity.component.referencingComponents, referencingRevisions, true);
// build the reference entity // build the reference entity
var referenceEntity = { var referenceEntity = {
'id': controllerServiceEntity.id, 'id': controllerServiceEntity.id,
'state': enabled ? 'ENABLED' : 'DISABLED', 'state': enabled ? 'ENABLED' : 'DISABLED',
'referencingComponentRevisions': {} 'referencingComponentRevisions': referencingRevisions
}; };
// include the revision of each referencing component
$.each(controllerServiceEntity.component.referencingComponents, function (_, referencingComponent) {
referenceEntity.referencingComponentRevisions[referencingComponent.id] = nf.Client.getRevision(referencingComponent);
});
// issue the request to update the referencing components // issue the request to update the referencing components
var updated = $.ajax({ var updated = $.ajax({
type: 'PUT', type: 'PUT',
@ -1029,34 +1054,20 @@ nf.ControllerService = (function () {
var referencingComponentsContainer = $('#disable-controller-service-referencing-components'); var referencingComponentsContainer = $('#disable-controller-service-referencing-components');
createReferencingComponents(serviceTable, referencingComponentsContainer, controllerService.referencingComponents); createReferencingComponents(serviceTable, referencingComponentsContainer, controllerService.referencingComponents);
var hasUnauthorized = false;
$.each(controllerService.referencingComponents, function (_, referencingComponent) {
if (referencingComponent.permissions.canRead === false || referencingComponent.permissions.canWrite === false) {
hasUnauthorized = true;
return false;
}
});
// build the button model // build the button model
var buttons = []; var buttons = [{
buttonText: 'Disable',
if (hasUnauthorized === false) { color: {
buttons.push({ base: '#728E9B',
buttonText: 'Disable', hover: '#004849',
color: { text: '#ffffff'
base: '#728E9B', },
hover: '#004849', handler: {
text: '#ffffff' click: function () {
}, disableHandler(serviceTable);
handler: {
click: function () {
disableHandler(serviceTable);
}
} }
}); }
} }, {
buttons.push({
buttonText: 'Cancel', buttonText: 'Cancel',
color: { color: {
base: '#E3E8EB', base: '#E3E8EB',
@ -1066,7 +1077,7 @@ nf.ControllerService = (function () {
handler: { handler: {
click: closeModal click: closeModal
} }
}); }];
// show the dialog // show the dialog
$('#disable-controller-service-dialog').modal('setButtonModel', buttons).modal('show'); $('#disable-controller-service-dialog').modal('setButtonModel', buttons).modal('show');
@ -1197,6 +1208,17 @@ nf.ControllerService = (function () {
}]); }]);
}; };
// ensure we have access to all referencing components before attempting the sequence
if (hasUnauthorizedReferencingComponent(controllerService.referencingComponents)) {
setCloseButton();
nf.Dialog.showOkDialog({
headerText: 'Controller Service',
dialogContent: 'Unable to disable due to unauthorized referencing components.'
});
return;
}
$('#disable-progress-label').text('Steps to disable ' + controllerService.name); $('#disable-progress-label').text('Steps to disable ' + controllerService.name);
var disableReferencingSchedulable = $('#disable-referencing-schedulable').addClass('ajax-loading'); var disableReferencingSchedulable = $('#disable-referencing-schedulable').addClass('ajax-loading');
@ -1247,6 +1269,31 @@ nf.ControllerService = (function () {
}); });
}; };
/**
* Determines if any of the specified referencing components are not authorized.
*
* @param referencingComponents referencing components
* @returns {boolean}
*/
var hasUnauthorizedReferencingComponent = function (referencingComponents) {
var hasUnauthorized = false;
$.each(referencingComponents, function (_, referencingComponentEntity) {
if (referencingComponentEntity.permissions.canRead === false || referencingComponentEntity.permissions.canWrite === false) {
hasUnauthorized = true;
return false;
}
var referencingComponent = referencingComponentEntity.component;
if (hasUnauthorizedReferencingComponent(referencingComponent.referencingComponents)) {
hasUnauthorized = true;
return false;
}
});
return hasUnauthorized;
};
/** /**
* Handles the enable action of the enable controller service dialog. * Handles the enable action of the enable controller service dialog.
* *
@ -1263,26 +1310,9 @@ nf.ControllerService = (function () {
var controllerServiceEntity = controllerServiceData.getItemById(controllerServiceId); var controllerServiceEntity = controllerServiceData.getItemById(controllerServiceId);
var controllerService = controllerServiceEntity.component; var controllerService = controllerServiceEntity.component;
var hasUnauthorized = false;
$.each(controllerService.referencingComponents, function (_, referencingComponent) {
if (referencingComponent.permissions.canRead === false || referencingComponent.permissions.canWrite === false) {
hasUnauthorized = true;
return false;
}
});
// determine if we want to also activate referencing components // determine if we want to also activate referencing components
var scope = $('#enable-controller-service-scope').combo('getSelectedOption').value; var scope = $('#enable-controller-service-scope').combo('getSelectedOption').value;
// ensure appropriate access
if (scope === config.serviceAndReferencingComponents && hasUnauthorized) {
nf.Dialog.showOkDialog({
headerText: 'Controller Service',
dialogContent: 'Unable to enable due to unauthorized referencing components.'
});
return;
}
// update visibility // update visibility
if (scope === config.serviceOnly) { if (scope === config.serviceOnly) {
$('#enable-controller-service-progress li.referencing-component').hide(); $('#enable-controller-service-progress li.referencing-component').hide();
@ -1331,6 +1361,17 @@ nf.ControllerService = (function () {
}]); }]);
}; };
// ensure appropriate access
if (scope === config.serviceAndReferencingComponents && hasUnauthorizedReferencingComponent(controllerService.referencingComponents)) {
setCloseButton();
nf.Dialog.showOkDialog({
headerText: 'Controller Service',
dialogContent: 'Unable to enable due to unauthorized referencing components.'
});
return;
}
$('#enable-progress-label').text('Steps to enable ' + controllerService.name); $('#enable-progress-label').text('Steps to enable ' + controllerService.name);
var enableControllerService = $('#enable-controller-service').addClass('ajax-loading'); var enableControllerService = $('#enable-controller-service').addClass('ajax-loading');
@ -1504,7 +1545,7 @@ nf.ControllerService = (function () {
}; };
/** /**
* Identifies descritpors that reference controller services. * Identifies descriptors that reference controller services.
* *
* @param {object} component * @param {object} component
*/ */