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;
}
@Override
public Authorizable getControllerServiceReferencingComponent(String controllerSeriveId, String id) {
final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerSeriveId);
final ControllerServiceReference referencingComponents = controllerService.getReferences();
private ConfiguredComponent findControllerServiceReferencingComponent(final ControllerServiceReference referencingComponents, final String id) {
ConfiguredComponent reference = null;
for (final ConfiguredComponent component : referencingComponents.getReferencingComponents()) {
if (component.getIdentifier().equals(id)) {
reference = component;
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) {
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,
controllerService,
() -> 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 List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
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
public ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingComponents(
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()));
}
// return the current revision if the component wasn't updated
for (final Map.Entry<String, Revision> entry : referenceRevisions.entrySet()) {
final String componentId = entry.getKey();
if (!updatedRevisions.containsKey(componentId)) {
final Revision currentRevision = revisionManager.getRevision(componentId);
updatedRevisions.put(componentId, currentRevision);
}
// ensure the revision for all referencing components is included regardless of whether they were updated in this request
for (final ConfiguredComponent component : findAllReferencingComponents(updatedReference)) {
updatedRevisions.putIfAbsent(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
}
final ControllerServiceReferencingComponentsEntity entity = createControllerServiceReferencingComponentsEntity(updatedReference, updatedRevisions);
@ -1757,6 +1772,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
for (final ConfiguredComponent component : reference.getReferencingComponents()) {
referencingRevisions.put(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier()));
}
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 (!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());
}

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.
*
* @param {jQuery} serviceTable
@ -123,8 +123,9 @@ nf.ControllerService = (function () {
// this may happen if controller service A references another controller
// service B that has been removed. attempting to enable/disable/remove A
// will attempt to reload B which is no longer a known service
if (nf.Common.isUndefined(controllerServiceEntity)) {
// will attempt to reload B which is no longer a known service. also ensure
// we have permissions to reload the service
if (nf.Common.isUndefined(controllerServiceEntity) || controllerServiceEntity.permissions.canRead === false) {
return $.Deferred(function (deferred) {
deferred.reject();
}).promise();
@ -457,7 +458,8 @@ nf.ControllerService = (function () {
if (serviceTwist.hasClass('collapsed')) {
var controllerServiceGrid = serviceTable.data('gridInstance');
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
createReferencingComponents(serviceTable, referencingServiceReferencesContainer, referencingService.referencingComponents);
@ -673,6 +675,33 @@ nf.ControllerService = (function () {
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
* the specified controller service.
@ -683,17 +712,15 @@ nf.ControllerService = (function () {
* @param {function} pollCondition
*/
var updateReferencingSchedulableComponents = function (serviceTable, controllerServiceEntity, running, pollCondition) {
var referencingRevisions = {};
getReferencingComponentRevisions(controllerServiceEntity.component.referencingComponents, referencingRevisions, false);
var referenceEntity = {
'id': controllerServiceEntity.id,
'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
var updated = $.ajax({
type: 'PUT',
@ -956,18 +983,16 @@ nf.ControllerService = (function () {
* @param {function} pollCondition
*/
var updateReferencingServices = function (serviceTable, controllerServiceEntity, enabled, pollCondition) {
var referencingRevisions = {};
getReferencingComponentRevisions(controllerServiceEntity.component.referencingComponents, referencingRevisions, true);
// build the reference entity
var referenceEntity = {
'id': controllerServiceEntity.id,
'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
var updated = $.ajax({
type: 'PUT',
@ -1029,34 +1054,20 @@ nf.ControllerService = (function () {
var referencingComponentsContainer = $('#disable-controller-service-referencing-components');
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
var buttons = [];
if (hasUnauthorized === false) {
buttons.push({
buttonText: 'Disable',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
disableHandler(serviceTable);
}
var buttons = [{
buttonText: 'Disable',
color: {
base: '#728E9B',
hover: '#004849',
text: '#ffffff'
},
handler: {
click: function () {
disableHandler(serviceTable);
}
});
}
buttons.push({
}
}, {
buttonText: 'Cancel',
color: {
base: '#E3E8EB',
@ -1066,7 +1077,7 @@ nf.ControllerService = (function () {
handler: {
click: closeModal
}
});
}];
// show the dialog
$('#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);
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.
*
@ -1263,26 +1310,9 @@ nf.ControllerService = (function () {
var controllerServiceEntity = controllerServiceData.getItemById(controllerServiceId);
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
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
if (scope === config.serviceOnly) {
$('#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);
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
*/