From 17fbf55972fa81e7f5cccfd01fe833dbdc6326f7 Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Thu, 11 Aug 2016 10:52:04 -0400 Subject: [PATCH 1/2] NIFI-2542: - Ensuring transitive referencing components are able to be returned. NIFI-2543: - Ensuring we have permissions before attempting to reload a controller service. --- .../apache/nifi/web/StandardNiFiServiceFacade.java | 7 ++++++- .../webapp/js/nf/canvas/nf-controller-service.js | 12 +++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 9db15c0bd52a..43c99a97f02d 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -1806,7 +1806,12 @@ private ControllerServiceReferencingComponentsEntity createControllerServiceRefe // 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 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()); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js index 1a32c8efa15b..dc4f68277025 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js @@ -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); @@ -1504,7 +1506,7 @@ nf.ControllerService = (function () { }; /** - * Identifies descritpors that reference controller services. + * Identifies descriptors that reference controller services. * * @param {object} component */ From a8a055b9527a597908231eaa836a5e36d6739ade Mon Sep 17 00:00:00 2001 From: Matt Gilman Date: Thu, 11 Aug 2016 19:49:41 -0400 Subject: [PATCH 2/2] NIFI-2542: - Ensuring we can enable/disable services with transitive referencing components. - Ensuring we cannot enable/disable services with unauthorized referencing components. --- .../StandardAuthorizableLookup.java | 23 ++- .../nifi/web/StandardNiFiServiceFacade.java | 32 +++- .../js/nf/canvas/nf-controller-service.js | 151 +++++++++++------- 3 files changed, 137 insertions(+), 69 deletions(-) diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java index 32862b204e4e..58e79665d037 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java @@ -226,19 +226,32 @@ public Authorizable getCounters() { 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); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 43c99a97f02d..56d6595ab851 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -1676,13 +1676,32 @@ public ControllerServiceEntity updateControllerService(final Revision revision, final RevisionUpdate 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 bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId())); return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletins); } + private Set findAllReferencingComponents(final ControllerServiceReference reference) { + final Set 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 referenceRevisions, final String controllerServiceId, final ScheduledState scheduledState, final ControllerServiceState controllerServiceState) { @@ -1705,13 +1724,9 @@ public RevisionUpdate update() { updatedRevisions.put(component.getIdentifier(), currentRevision.incrementRevision(requestRevision.getClientId())); } - // return the current revision if the component wasn't updated - for (final Map.Entry 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 @@ private ControllerServiceReferencingComponentsEntity createControllerServiceRefe for (final ConfiguredComponent component : reference.getReferencingComponents()) { referencingRevisions.put(component.getIdentifier(), revisionManager.getRevision(component.getIdentifier())); } + return createControllerServiceReferencingComponentsEntity(reference, referencingRevisions); } diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js index dc4f68277025..44ee3c04cd8f 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js @@ -675,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. @@ -685,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', @@ -958,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', @@ -1031,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', @@ -1068,7 +1077,7 @@ nf.ControllerService = (function () { handler: { click: closeModal } - }); + }]; // show the dialog $('#disable-controller-service-dialog').modal('setButtonModel', buttons).modal('show'); @@ -1199,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'); @@ -1249,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. * @@ -1265,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(); @@ -1333,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');