You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by mc...@apache.org on 2018/09/19 19:30:06 UTC

[1/4] nifi git commit: NIFI-375: Added operation policy

Repository: nifi
Updated Branches:
  refs/heads/master 9161b1787 -> f570cb980


http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
index 0b8fd82..b41cbfb 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-canvas-utils.js
@@ -1109,7 +1109,7 @@
                 return true;
             }
 
-            if (nfCanvasUtils.canModify(selection) === false) {
+            if (nfCanvasUtils.canOperate(selection) === false) {
                 return false;
             }
 
@@ -1159,7 +1159,7 @@
                 return true;
             }
 
-            if (nfCanvasUtils.canModify(selection) === false) {
+            if (nfCanvasUtils.canOperate(selection) === false) {
                 return false;
             }
 
@@ -1188,7 +1188,7 @@
                 }
 
                 // not a PG, verify permissions to modify
-                if (nfCanvasUtils.canModify(selected) === false) {
+                if (nfCanvasUtils.canOperate(selected) === false) {
                     return false;
                 }
 
@@ -1227,7 +1227,7 @@
                 }
 
                 // not a PG, verify permissions to modify
-                if (nfCanvasUtils.canModify(selected) === false) {
+                if (nfCanvasUtils.canOperate(selected) === false) {
                     return false;
                 }
 
@@ -1282,7 +1282,8 @@
                 return false;
             }
 
-            if (nfCanvasUtils.canModify(selection) === false || nfCanvasUtils.canRead(selection) === false) {
+            if ((nfCanvasUtils.canModify(selection) === false || nfCanvasUtils.canRead(selection) === false)
+                    && nfCanvasUtils.canOperate(selection) === false) {
                 return false;
             }
 
@@ -1319,7 +1320,8 @@
                 return false;
             }
 
-            if (nfCanvasUtils.canModify(selection) === false || nfCanvasUtils.canRead(selection) === false) {
+            if ((nfCanvasUtils.canModify(selection) === false || nfCanvasUtils.canRead(selection) === false)
+                    && nfCanvasUtils.canOperate(selection) === false) {
                 return false;
             }
 
@@ -1473,6 +1475,21 @@
         },
 
         /**
+         * Determines whether the components in the specified selection can be operated.
+         *
+         * @argument {selection} selection      The selection
+         * @return {boolean}            Whether the selection can be operated
+         */
+        canOperate: function (selection) {
+            var selectionSize = selection.size();
+            var writableSize = selection.filter(function (d) {
+                return d.permissions.canWrite || (d.operatePermissions && d.operatePermissions.canWrite);
+            }).size();
+
+            return selectionSize === writableSize;
+        },
+
+        /**
          * Determines whether the specified selection is in a state to support modification.
          *
          * @argument {selection} selection      The selection

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
index 508651d..77984f6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-context-menu.js
@@ -173,7 +173,7 @@
             return false;
         }
 
-        if (nfCanvasUtils.canModify(selection) === false) {
+        if (nfCanvasUtils.canOperate(selection) === false) {
             return false;
         }
 
@@ -639,7 +639,7 @@
      * @param {selection} selection
      */
     var canStartTransmission = function (selection) {
-        if (nfCanvasUtils.canModify(selection) === false) {
+        if (nfCanvasUtils.canOperate(selection) === false) {
             return false;
         }
 
@@ -652,7 +652,7 @@
      * @param {selection} selection
      */
     var canStopTransmission = function (selection) {
-        if (nfCanvasUtils.canModify(selection) === false) {
+        if (nfCanvasUtils.canOperate(selection) === false) {
             return false;
         }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-service.js
----------------------------------------------------------------------
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 e8c4eb2..3938d94 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
@@ -428,9 +428,16 @@
      *
      * @param {jQuery} serviceTable
      * @param {jQuery} referenceContainer
-     * @param {array} referencingComponents
+     * @param {object} controllerServiceEntity
      */
-    var createReferencingComponents = function (serviceTable, referenceContainer, referencingComponents) {
+    var createReferencingComponents = function (serviceTable, referenceContainer, controllerServiceEntity) {
+
+        if (controllerServiceEntity.permissions.canRead === false) {
+            referenceContainer.append('<div class="unset">Unauthorized</div>');
+            return;
+        }
+
+        var referencingComponents = controllerServiceEntity.component.referencingComponents;
         if (nfCommon.isEmpty(referencingComponents)) {
             referenceContainer.append('<div class="unset">No referencing components.</div>');
             return;
@@ -454,7 +461,7 @@
         var unauthorized = $('<ul class="referencing-component-listing clear"></ul>');
         $.each(referencingComponents, function (_, referencingComponentEntity) {
             // check the access policy for this referencing component
-            if (referencingComponentEntity.permissions.canRead === false || referencingComponentEntity.permissions.canWrite === false) {
+            if (referencingComponentEntity.permissions.canRead === false) {
                 var unauthorizedReferencingComponent = $('<div class="unset"></div>').text(referencingComponentEntity.id);
                 unauthorized.append(unauthorizedReferencingComponent);
             } else {
@@ -479,7 +486,7 @@
                     var processorBulletins = $('<div class="referencing-component-bulletins"></div>').addClass(referencingComponent.id + '-bulletins');
 
                     // type
-                    var processorType = $('<span class="referencing-component-type"></span>').text(nfCommon.substringAfterLast(referencingComponent.type, '.'));
+                    var processorType = $('<span class="referencing-component-type"></span>').text(referencingComponent.type);
 
                     // active thread count
                     var processorActiveThreadCount = $('<span class="referencing-component-active-thread-count"></span>').addClass(referencingComponent.id + '-active-threads');
@@ -517,8 +524,7 @@
 
                                 // get the controller service and expand its referencing services
                                 getControllerService(referencingComponent.id, controllerServiceData).done(function (controllerServiceEntity) {
-                                    var cs = controllerServiceEntity.component;
-                                    createReferencingComponents(serviceTable, referencingServiceReferencesContainer, cs.referencingComponents);
+                                    createReferencingComponents(serviceTable, referencingServiceReferencesContainer, controllerServiceEntity);
                                 });
                             }
                         } else {
@@ -540,7 +546,7 @@
                     var serviceBulletins = $('<div class="referencing-component-bulletins"></div>').addClass(referencingComponent.id + '-bulletins');
 
                     // type
-                    var serviceType = $('<span class="referencing-component-type"></span>').text(nfCommon.substringAfterLast(referencingComponent.type, '.'));
+                    var serviceType = $('<span class="referencing-component-type"></span>').text(referencingComponent.type);
 
                     // service
                     var serviceItem = $('<li></li>').append(serviceTwist).append(serviceState).append(serviceBulletins).append(serviceLink).append(serviceType).append(referencingServiceReferencesContainer);
@@ -636,15 +642,12 @@
         var updateControllerServiceEntity = {
             'revision': nfClient.getRevision(controllerServiceEntity),
             'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
-            'component': {
-                'id': controllerServiceEntity.id,
-                'state': enabled ? 'ENABLED' : 'DISABLED'
-            }
+            'state': enabled ? 'ENABLED' : 'DISABLED'
         };
 
         var updated = $.ajax({
             type: 'PUT',
-            url: controllerServiceEntity.uri,
+            url: controllerServiceEntity.uri + '/run-status',
             data: JSON.stringify(updateControllerServiceEntity),
             dataType: 'json',
             contentType: 'application/json'
@@ -655,7 +658,13 @@
         // wait until the polling of each service finished
         return $.Deferred(function (deferred) {
             updated.done(function () {
-                var serviceUpdated = pollService(controllerServiceEntity, function (service, bulletins) {
+                if (controllerServiceEntity.permissions.canRead === false) {
+                    // Can not use GET request to wait for completion. Just resolve blindly. The PUT request has been finished anyway.
+                    deferred.resolve();
+                    return;
+                }
+
+                var serviceUpdated = pollService(controllerServiceEntity, function (latestControllerServiceEntity, bulletins) {
                     if ($.isArray(bulletins)) {
                         if (enabled) {
                             updateBulletins(bulletins, $('#enable-controller-service-bulletins'));
@@ -665,13 +674,14 @@
                     }
 
                     // the condition is met once the service is (ENABLING or ENABLED)/DISABLED
+                    var runStatus = latestControllerServiceEntity.status.runStatus;
                     if (enabled) {
-                        return service.state === 'ENABLING' || service.state === 'ENABLED';
+                        return runStatus === 'ENABLING' || runStatus === 'ENABLED';
                     } else {
-                        return service.state === 'DISABLED';
+                        return runStatus === 'DISABLED';
                     }
                 }, function (service) {
-                    return nfCanvasUtils.queryBulletins([service.id]);
+                    return nfCanvasUtils.queryBulletins([controllerServiceEntity.id]);
                 }, pollCondition);
 
                 // once the service has updated, resolve and render the updated service
@@ -710,7 +720,7 @@
             });
         };
 
-        // check the referencing servcies
+        // check the referencing services
         checkReferencingServices(controllerService.referencingComponents);
         return ids;
     };
@@ -868,8 +878,8 @@
                 });
 
                 $.when(bulletins, service).done(function (bulletinResponse, serviceResult) {
-                    var serviceResponse = serviceResult[0];
-                    conditionMet(serviceResponse.component, bulletinResponse.bulletinBoard.bulletins);
+                    var latestControllerServiceEntity = serviceResult[0];
+                    conditionMet(latestControllerServiceEntity, bulletinResponse.bulletinBoard.bulletins);
                 }).fail(function (xhr, status, error) {
                     deferred.reject();
                     nfErrorHandler.handleAjaxError(xhr, status, error);
@@ -877,8 +887,8 @@
             };
 
             // tests to if the condition has been met
-            var conditionMet = function (service, bulletins) {
-                if (completeCondition(service, bulletins)) {
+            var conditionMet = function (latestControllerServiceEntity, bulletins) {
+                if (completeCondition(latestControllerServiceEntity, bulletins)) {
                     deferred.resolve();
                 } else {
                     if (typeof pollCondition === 'function' && pollCondition()) {
@@ -891,7 +901,7 @@
 
             // poll for the status of the referencing components
             bulletinDeferred(controllerService).done(function (response) {
-                conditionMet(controllerService, response.bulletinBoard.bulletins);
+                conditionMet(controllerServiceEntity, response.bulletinBoard.bulletins);
             }).fail(function (xhr, status, error) {
                 deferred.reject();
                 nfErrorHandler.handleAjaxError(xhr, status, error);
@@ -908,8 +918,8 @@
      */
     var stopReferencingSchedulableComponents = function (controllerServiceEntity, pollCondition) {
         // continue to poll the service until all schedulable components have stopped
-        return pollService(controllerServiceEntity, function (service, bulletins) {
-            var referencingComponents = service.referencingComponents;
+        return pollService(controllerServiceEntity, function (latestControllerServiceEntity, bulletins) {
+            var referencingComponents = latestControllerServiceEntity.component.referencingComponents;
 
             var stillRunning = false;
             $.each(referencingComponents, function (_, referencingComponentEntity) {
@@ -956,8 +966,8 @@
      */
     var enableReferencingServices = function (controllerServiceEntity, pollCondition) {
         // continue to poll the service until all referencing services are enabled
-        return pollService(controllerServiceEntity, function (service, bulletins) {
-            var referencingComponents = service.referencingComponents;
+        return pollService(controllerServiceEntity, function (latestControllerServiceEntity, bulletins) {
+            var referencingComponents = latestControllerServiceEntity.component.referencingComponents;
 
             var notEnabled = false;
             $.each(referencingComponents, function (_, referencingComponentEntity) {
@@ -1001,8 +1011,8 @@
      */
     var disableReferencingServices = function (controllerServiceEntity, pollCondition) {
         // continue to poll the service until all referencing services are disabled
-        return pollService(controllerServiceEntity, function (service, bulletins) {
-            var referencingComponents = service.referencingComponents;
+        return pollService(controllerServiceEntity, function (latestControllerServiceEntity, bulletins) {
+            var referencingComponents = latestControllerServiceEntity.component.referencingComponents;
 
             var notDisabled = false;
             $.each(referencingComponents, function (_, referencingComponentEntity) {
@@ -1110,14 +1120,14 @@
      *
      * @argument {object} controllerService The controller service to disable
      */
-    var showDisableControllerServiceDialog = function (serviceTable, controllerService) {
+    var showDisableControllerServiceDialog = function (serviceTable, controllerServiceEntity) {
         // populate the disable controller service dialog
-        $('#disable-controller-service-id').text(controllerService.id);
-        $('#disable-controller-service-name').text(controllerService.name);
+        $('#disable-controller-service-id').text(controllerServiceEntity.id);
+        $('#disable-controller-service-name').text(nfCommon.getComponentName(controllerServiceEntity));
 
         // load the controller referencing components list
         var referencingComponentsContainer = $('#disable-controller-service-referencing-components');
-        createReferencingComponents(serviceTable, referencingComponentsContainer, controllerService.referencingComponents);
+        createReferencingComponents(serviceTable, referencingComponentsContainer, controllerServiceEntity);
 
         // build the button model
         var buttons = [{
@@ -1148,7 +1158,7 @@
         $('#disable-controller-service-dialog').modal('setButtonModel', buttons).modal('show');
 
         // load the bulletins
-        nfCanvasUtils.queryBulletins([controllerService.id]).done(function (response) {
+        nfCanvasUtils.queryBulletins([controllerServiceEntity.id]).done(function (response) {
             updateBulletins(response.bulletinBoard.bulletins, $('#disable-controller-service-bulletins'));
         });
 
@@ -1162,14 +1172,14 @@
      * @param {object} serviceTable
      * @param {object} controllerService
      */
-    var showEnableControllerServiceDialog = function (serviceTable, controllerService) {
+    var showEnableControllerServiceDialog = function (serviceTable, controllerServiceEntity) {
         // populate the disable controller service dialog
-        $('#enable-controller-service-id').text(controllerService.id);
-        $('#enable-controller-service-name').text(controllerService.name);
+        $('#enable-controller-service-id').text(controllerServiceEntity.id);
+        $('#enable-controller-service-name').text(nfCommon.getComponentName(controllerServiceEntity));
 
         // load the controller referencing components list
         var referencingComponentsContainer = $('#enable-controller-service-referencing-components');
-        createReferencingComponents(serviceTable, referencingComponentsContainer, controllerService.referencingComponents);
+        createReferencingComponents(serviceTable, referencingComponentsContainer, controllerServiceEntity);
 
         // build the button model
         var buttons = [{
@@ -1200,7 +1210,7 @@
         $('#enable-controller-service-dialog').modal('setButtonModel', buttons).modal('show');
 
         // load the bulletins
-        nfCanvasUtils.queryBulletins([controllerService.id]).done(function (response) {
+        nfCanvasUtils.queryBulletins([controllerServiceEntity.id]).done(function (response) {
             updateBulletins(response.bulletinBoard.bulletins, $('#enable-controller-service-bulletins'));
         });
 
@@ -1274,17 +1284,21 @@
         };
 
         // ensure we have access to all referencing components before attempting the sequence
-        if (hasUnauthorizedReferencingComponent(controllerService.referencingComponents)) {
+        if (hasUnauthorizedReferencingComponent(controllerServiceEntity)) {
             setCloseButton();
 
             nfDialog.showOkDialog({
                 headerText: 'Controller Service',
-                dialogContent: 'Unable to disable due to unauthorized referencing components.'
+                dialogContent: controllerServiceEntity.permissions.canRead === false
+                                    // Unknown references.
+                                    ? 'Unable to disable due to a lack of read permission to see referencing components.'
+                                    // Unauthorized references.
+                                    : '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 ' + nfCommon.getComponentName(controllerServiceEntity));
         var disableReferencingSchedulable = $('#disable-referencing-schedulable').addClass('ajax-loading');
 
         $.Deferred(function (deferred) {
@@ -1321,7 +1335,9 @@
                 disableReferencingSchedulable.removeClass('ajax-loading').addClass('ajax-error');
             });
         }).always(function () {
-            reloadControllerServiceAndReferencingComponents(serviceTable, controllerService);
+            if (controllerServiceEntity.permissions.canRead === true) {
+                reloadControllerServiceAndReferencingComponents(serviceTable, controllerService);
+            }
             setCloseButton();
 
             // inform the user if the action was canceled
@@ -1335,22 +1351,22 @@
     };
 
     /**
-     * Determines if any of the specified referencing components are not authorized.
+     * Determines if any of the specified referencing components are not authorized to enable/disable.
      *
-     * @param referencingComponents referencing components
+     * @param {object} ControllerServiceEntity having referencingComponents referencing components
      * @returns {boolean}
      */
-    var hasUnauthorizedReferencingComponent = function (referencingComponents) {
-        var hasUnauthorized = false;
+    var hasUnauthorizedReferencingComponent = function (controllerServiceEntity) {
 
-        $.each(referencingComponents, function (_, referencingComponentEntity) {
-            if (referencingComponentEntity.permissions.canRead === false || referencingComponentEntity.permissions.canWrite === false) {
-                hasUnauthorized = true;
-                return false;
-            }
+        if (controllerServiceEntity.permissions.canRead === false
+                || (controllerServiceEntity.permissions.canWrite === false && controllerServiceEntity.operatePermissions.canWrite === false)) {
+            return true;
+        }
 
-            var referencingComponent = referencingComponentEntity.component;
-            if (hasUnauthorizedReferencingComponent(referencingComponent.referencingComponents)) {
+        var hasUnauthorized = false;
+        var referencingComponents = controllerServiceEntity.component.referencingComponents;
+        $.each(referencingComponents, function (_, referencingComponentEntity) {
+            if (hasUnauthorizedReferencingComponent(referencingComponentEntity)) {
                 hasUnauthorized = true;
                 return false;
             }
@@ -1427,17 +1443,21 @@
         };
 
         // ensure appropriate access
-        if (scope === config.serviceAndReferencingComponents && hasUnauthorizedReferencingComponent(controllerService.referencingComponents)) {
+        if (scope === config.serviceAndReferencingComponents && hasUnauthorizedReferencingComponent(controllerServiceEntity)) {
             setCloseButton();
 
             nfDialog.showOkDialog({
                 headerText: 'Controller Service',
-                dialogContent: 'Unable to enable due to unauthorized referencing components.'
+                dialogContent: controllerServiceEntity.permissions.canRead === false
+                                    // Unknown references.
+                                    ? 'Unable to enable due to a lack of read permission to see referencing components.'
+                                    // Unauthorized references.
+                                    : '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 ' + nfCommon.getComponentName(controllerServiceEntity));
         var enableControllerService = $('#enable-controller-service').addClass('ajax-loading');
 
         $.Deferred(function (deferred) {
@@ -1484,7 +1504,9 @@
                 });
             }
         }).always(function () {
-            reloadControllerServiceAndReferencingComponents(serviceTable, controllerService);
+            if (controllerServiceEntity.permissions.canRead === true) {
+                reloadControllerServiceAndReferencingComponents(serviceTable, controllerService);
+            }
             setCloseButton();
 
             // inform the user if the action was canceled
@@ -1883,7 +1905,7 @@
                 var referenceContainer = $('#controller-service-referencing-components');
 
                 // load the controller referencing components list
-                createReferencingComponents(serviceTable, referenceContainer, controllerService.referencingComponents);
+                createReferencingComponents(serviceTable, referenceContainer, controllerServiceEntity);
 
                 var buttons = [{
                     buttonText: 'Apply',
@@ -2064,7 +2086,7 @@
                 var referenceContainer = $('#controller-service-referencing-components');
 
                 // load the controller referencing components list
-                createReferencingComponents(serviceTable, referenceContainer, controllerService.referencingComponents);
+                createReferencingComponents(serviceTable, referenceContainer, controllerServiceEntity);
 
                 var buttons = [{
                     buttonText: 'Ok',
@@ -2131,7 +2153,7 @@
          * @param {object} controllerServiceEntity
          */
         enable: function (serviceTable, controllerServiceEntity) {
-            showEnableControllerServiceDialog(serviceTable, controllerServiceEntity.component);
+            showEnableControllerServiceDialog(serviceTable, controllerServiceEntity);
         },
 
         /**
@@ -2141,7 +2163,7 @@
          * @param {object} controllerServiceEntity
          */
         disable: function (serviceTable, controllerServiceEntity) {
-            showDisableControllerServiceDialog(serviceTable, controllerServiceEntity.component);
+            showDisableControllerServiceDialog(serviceTable, controllerServiceEntity);
         },
 
         /**

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
index e85d7a8..adf5bd1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-controller-services.js
@@ -719,16 +719,12 @@
      * @returns {String}
      */
     var groupIdFormatter = function (row, cell, value, columnDef, dataContext) {
-        if (!dataContext.permissions.canRead) {
-            return '';
-        }
-
-        if (nfCommon.isDefinedAndNotNull(dataContext.component.parentGroupId)) {
+        if (nfCommon.isDefinedAndNotNull(dataContext.parentGroupId)) {
             // see if this listing is based off a selected process group
             var selection = nfCanvasUtils.getSelection();
             if (selection.empty() === false) {
                 var selectedData = selection.datum();
-                if (selectedData.id === dataContext.component.parentGroupId) {
+                if (selectedData.id === dataContext.parentGroupId) {
                     if (selectedData.permissions.canRead) {
                         return nfCommon.escapeHtml(selectedData.component.name);
                     } else {
@@ -740,9 +736,9 @@
             // there's either no selection or the service is defined in an ancestor component
             var breadcrumbs = nfNgBridge.injector.get('breadcrumbsCtrl').getBreadcrumbs();
 
-            var processGroupLabel = dataContext.component.parentGroupId;
+            var processGroupLabel = dataContext.parentGroupId;
             $.each(breadcrumbs, function (_, breadcrumbEntity) {
-                if (breadcrumbEntity.id === dataContext.component.parentGroupId) {
+                if (breadcrumbEntity.id === dataContext.parentGroupId) {
                     processGroupLabel = breadcrumbEntity.label;
                     return false;
                 }
@@ -786,8 +782,8 @@
             return isAuthorized;
         };
 
-        if (nfCommon.isDefinedAndNotNull(dataContext.component.parentGroupId)) {
-            return canWriteProcessGroupParent(dataContext.component.parentGroupId);
+        if (nfCommon.isDefinedAndNotNull(dataContext.parentGroupId)) {
+            return canWriteProcessGroupParent(dataContext.parentGroupId);
         } else {
             return nfCommon.canModifyController();
         }
@@ -819,17 +815,17 @@
                     return aType === bType ? 0 : aType > bType ? 1 : -1;
                 } else if (sortDetails.columnId === 'state') {
                     var aState;
-                    if (a.component.validationStatus === 'VALIDATING') {
+                    if (a.status.validationStatus === 'VALIDATING') {
                         aState = 'Validating';
-                    } else if (a.component.validationStatus === 'INVALID') {
+                    } else if (a.status.validationStatus === 'INVALID') {
                         aState = 'Invalid';
                     } else {
                         aState = nfCommon.isDefinedAndNotNull(a.component[sortDetails.columnId]) ? a.component[sortDetails.columnId] : '';
                     }
                     var bState;
-                    if (b.component.validationStatus === 'VALIDATING') {
+                    if (b.status.validationStatus === 'VALIDATING') {
                         bState = 'Validating';
-                    } else if (b.component.validationStatus === 'INVALID') {
+                    } else if (b.status.validationStatus === 'INVALID') {
                         bState = 'Invalid';
                     } else {
                         bState = nfCommon.isDefinedAndNotNull(b.component[sortDetails.columnId]) ? b.component[sortDetails.columnId] : '';
@@ -891,29 +887,25 @@
         };
 
         var controllerServiceStateFormatter = function (row, cell, value, columnDef, dataContext) {
-            if (!dataContext.permissions.canRead) {
-                return '';
-            }
-            
             // determine the appropriate label
             var icon = '', label = '';
-            if (dataContext.component.validationStatus === 'VALIDATING') {
+            if (dataContext.status.validationStatus === 'VALIDATING') {
                 icon = 'validating fa fa-spin fa-circle-notch';
                 label = 'Validating';
-            } else if (dataContext.component.validationStatus === 'INVALID') {
+            } else if (dataContext.status.validationStatus === 'INVALID') {
                 icon = 'invalid fa fa-warning';
                 label = 'Invalid';
             } else {
-                if (dataContext.component.state === 'DISABLED') {
+                if (dataContext.status.runStatus === 'DISABLED') {
                     icon = 'disabled icon icon-enable-false"';
                     label = 'Disabled';
-                } else if (dataContext.component.state === 'DISABLING') {
+                } else if (dataContext.status.runStatus === 'DISABLING') {
                     icon = 'disabled icon icon-enable-false"';
                     label = 'Disabling';
-                } else if (dataContext.component.state === 'ENABLED') {
+                } else if (dataContext.status.runStatus === 'ENABLED') {
                     icon = 'enabled fa fa-flash';
                     label = 'Enabled';
-                } else if (dataContext.component.state === 'ENABLING') {
+                } else if (dataContext.status.runStatus === 'ENABLING') {
                     icon = 'enabled fa fa-flash';
                     label = 'Enabling';
                 }
@@ -927,54 +919,57 @@
         var controllerServiceActionFormatter = function (row, cell, value, columnDef, dataContext) {
             var markup = '';
 
-            if (dataContext.permissions.canRead) {
-                var definedByCurrentGroup = false;
-                if (nfCommon.isDefinedAndNotNull(dataContext.component.parentGroupId)) {
-                    // when opened in the process group context, the current group is store in #process-group-id
-                    if (dataContext.component.parentGroupId === $('#process-group-id').text()) {
-                        definedByCurrentGroup = true;
-                    }
-                } else {
-                    // when there is no parent group, the service is defined at the controller level and should be editable
+            var canRead = dataContext.permissions.canRead;
+            var canWrite = dataContext.permissions.canWrite;
+            var canOperate = canWrite || (dataContext.operatePermissions && dataContext.operatePermissions.canWrite);
+
+            var definedByCurrentGroup = false;
+            if (nfCommon.isDefinedAndNotNull(dataContext.parentGroupId)) {
+                // when opened in the process group context, the current group is store in #process-group-id
+                if (dataContext.parentGroupId === $('#process-group-id').text()) {
                     definedByCurrentGroup = true;
                 }
+            } else {
+                // when there is no parent group, the service is defined at the controller level and should be editable
+                definedByCurrentGroup = true;
+            }
 
-                if (definedByCurrentGroup === true) {
-                    if (dataContext.permissions.canWrite) {
-                        // write permission... allow actions based on the current state of the service
-                        if (dataContext.component.state === 'ENABLED' || dataContext.component.state === 'ENABLING') {
-                            markup += '<div class="pointer view-controller-service fa fa-gear" title="View Configuration"></div>';
-                            markup += '<div class="pointer disable-controller-service icon icon-enable-false" title="Disable"></div>';
-                        } else if (dataContext.component.state === 'DISABLED') {
-                            markup += '<div class="pointer edit-controller-service fa fa-gear" title="Configure"></div>';
-
-                            // if there are no validation errors allow enabling
-                            if (nfCommon.isEmpty(dataContext.component.validationErrors)) {
-                                markup += '<div class="pointer enable-controller-service fa fa-flash" title="Enable"></div>';
-                            }
-
-                            if (dataContext.component.multipleVersionsAvailable === true) {
-                                markup += '<div title="Change Version" class="pointer change-version-controller-service fa fa-exchange"></div>';
-                            }
+            if (definedByCurrentGroup === true) {
+                // If the service is in the current process group, allow actions based on the current state of the service and permissions
+                var isDisabled = dataContext.status.runStatus === 'DISABLED';
 
-                            if (canWriteControllerServiceParent(dataContext)) {
-                                markup += '<div class="pointer delete-controller-service fa fa-trash" title="Remove"></div>';
-                            }
-                        }
-
-                        if (dataContext.component.persistsState === true) {
-                            markup += '<div title="View State" class="pointer view-state-controller-service fa fa-tasks"></div>';
-                        }
+                if (canRead) {
+                    if (canWrite && isDisabled) {
+                        markup += '<div class="pointer edit-controller-service fa fa-gear" title="Configure"></div>';
                     } else {
-                        // no write permission... allow viewing configuration if in current group
-                        if (definedByCurrentGroup === true) {
-                            markup += '<div class="pointer view-controller-service fa fa-gear" title="View Configuration"></div>';
-                        }
+                        markup += '<div class="pointer view-controller-service fa fa-gear" title="View Configuration"></div>';
                     }
-                } else {
-                    // not defined in current group... show go to arrow
-                    markup += '<div class="pointer go-to-controller-service fa fa-long-arrow-right" title="Go To"></div>';
                 }
+
+                if (canOperate) {
+                    if (dataContext.status.runStatus === 'ENABLED' || dataContext.status.runStatus === 'ENABLING') {
+                        markup += '<div class="pointer disable-controller-service icon icon-enable-false" title="Disable"></div>';
+                    } else if (isDisabled && dataContext.status.validationStatus === 'VALID') {
+                        // if there are no validation errors allow enabling
+                        markup += '<div class="pointer enable-controller-service fa fa-flash" title="Enable"></div>';
+                    }
+                }
+
+                if (isDisabled && canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+                    markup += '<div title="Change Version" class="pointer change-version-controller-service fa fa-exchange"></div>';
+                }
+
+                if (isDisabled && canRead && canWrite && canWriteControllerServiceParent(dataContext)) {
+                    markup += '<div class="pointer delete-controller-service fa fa-trash" title="Remove"></div>';
+                }
+
+                if (canRead && canWrite && dataContext.component.persistsState === true) {
+                    markup += '<div title="View State" class="pointer view-state-controller-service fa fa-tasks"></div>';
+                }
+
+            } else {
+                // not defined in current group... show go to arrow
+                markup += '<div class="pointer go-to-controller-service fa fa-long-arrow-right" title="Go To"></div>';
             }
 
             // allow policy configuration conditionally
@@ -1101,16 +1096,16 @@
                     $('#shell-close-button').click();
                 } else if (target.hasClass('go-to-controller-service')) {
                     // load the parent group of the selected service
-                    nfProcessGroup.enterGroup(controllerServiceEntity.component.parentGroupId);
+                    nfProcessGroup.enterGroup(controllerServiceEntity.parentGroupId);
 
                     // open/select the specific service
                     $.Deferred(function (deferred) {
                         if ($('#process-group-configuration').is(':visible')) {
-                            nfProcessGroupConfiguration.loadConfiguration(controllerServiceEntity.component.parentGroupId).done(function () {
+                            nfProcessGroupConfiguration.loadConfiguration(controllerServiceEntity.parentGroupId).done(function () {
                                 deferred.resolve();
                             });
                         } else {
-                            nfProcessGroupConfiguration.showConfiguration(controllerServiceEntity.component.parentGroupId).done(function () {
+                            nfProcessGroupConfiguration.showConfiguration(controllerServiceEntity.parentGroupId).done(function () {
                                 deferred.resolve();
                             });
                         }
@@ -1130,9 +1125,9 @@
                             artifact: controllerServiceEntity.component.bundle.artifact,
                             version: controllerServiceEntity.component.bundle.version
                     })).done(function() {
-                         if (nfCommon.isDefinedAndNotNull(controllerServiceEntity.component.parentGroupId)) {
+                         if (nfCommon.isDefinedAndNotNull(controllerServiceEntity.parentGroupId)) {
                              var groupId;
-                             var processGroup = nfProcessGroup.get(controllerServiceEntity.component.parentGroupId);
+                             var processGroup = nfProcessGroup.get(controllerServiceEntity.parentGroupId);
                              if (nfCommon.isDefinedAndNotNull(processGroup)) {
                                  groupId = processGroup.id;
                              } else {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
index f71236e..7649a98 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
@@ -530,6 +530,10 @@
                 value: 'write-component',
                 description: 'Allows users to modify component configuration details'
             }, {
+                text: 'operate the component',
+                value: 'operate-component',
+                description: 'Allows users to operate components by changing component run status (start/stop/enable/disable), remote port transmission status, or terminating processor threads'
+            }, {
                 text: 'view provenance',
                 value: 'read-provenance',
                 description: 'Allows users to view provenance events generated by this component'
@@ -568,6 +572,9 @@
                         $('#selected-policy-action').text('read');
                     } else if (option.value === 'write-component') {
                         $('#selected-policy-action').text('write');
+                    } else if (option.value === 'operate-component') {
+                        $('#selected-policy-action').text('write');
+                        resource = ('operation/' + resource);
                     } else if (option.value === 'read-data') {
                         $('#selected-policy-action').text('read');
                         resource = ('data/' + resource);

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group-ports.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group-ports.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group-ports.js
index cad61d0..de9fab5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group-ports.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group-ports.js
@@ -299,8 +299,8 @@
         var remoteProcessGroupId = $('#remote-process-group-ports-id').text();
         var remoteProcessGroup = d3.select('#id-' + remoteProcessGroupId);
 
-        // if can modify, support updating the remote group port
-        if (nfCanvasUtils.canModify(remoteProcessGroup)) {
+        // if can operate, support updating the remote group port transmission status
+        if (nfCanvasUtils.canOperate(remoteProcessGroup)) {
 
             var createTransmissionSwitch = function (port) {
                 var transmissionSwitch;
@@ -330,33 +330,35 @@
             var transmissionSwitch = createTransmissionSwitch(port);
             transmissionSwitch.appendTo(portContainerEditContainer);
 
-            // only support configuration when the remote port exists
-            if (port.exists === true && port.connected === true) {
-                // create the button for editing the ports configuration
-                var editRemotePort = $('<button class="button edit-remote-port fa fa-pencil"></button>').click(function () {
-                    var portName = $('#' + portId + '-name').text();
-                    var portConcurrentTasks = $('#' + portId + '-concurrent-tasks').text();
-                    var portCompression = $('#' + portId + '-compression').text() === 'Yes';
-                    var batchCount = $('#' + portId + '-batch-count').text();
-                    var batchSize = $('#' + portId + '-batch-size').text();
-                    var batchDuration = $('#' + portId + '-batch-duration').text();
-
-                    // show the configuration dialog
-                    configureRemotePort(port.id, portName, portConcurrentTasks, portCompression, batchCount, batchSize, batchDuration, portType);
-                }).appendTo(portContainerEditContainer);
-
-                // show/hide the edit button as appropriate
-                if (port.transmitting === true) {
-                    editRemotePort.hide();
-                } else {
-                    editRemotePort.show();
+            // only support configuration when the remote port exists and if can modify
+            if (nfCanvasUtils.canModify(remoteProcessGroup)) {
+                if (port.exists === true && port.connected === true) {
+                    // create the button for editing the ports configuration
+                    var editRemotePort = $('<button class="button edit-remote-port fa fa-pencil"></button>').click(function () {
+                        var portName = $('#' + portId + '-name').text();
+                        var portConcurrentTasks = $('#' + portId + '-concurrent-tasks').text();
+                        var portCompression = $('#' + portId + '-compression').text() === 'Yes';
+                        var batchCount = $('#' + portId + '-batch-count').text();
+                        var batchSize = $('#' + portId + '-batch-size').text();
+                        var batchDuration = $('#' + portId + '-batch-duration').text();
+
+                        // show the configuration dialog
+                        configureRemotePort(port.id, portName, portConcurrentTasks, portCompression, batchCount, batchSize, batchDuration, portType);
+                    }).appendTo(portContainerEditContainer);
+
+                    // show/hide the edit button as appropriate
+                    if (port.transmitting === true) {
+                        editRemotePort.hide();
+                    } else {
+                        editRemotePort.show();
+                    }
+                } else if (port.exists === false) {
+                    $('<div class="remote-port-removed"/>').appendTo(portContainerEditContainer).qtip($.extend({},
+                        nfCommon.config.tooltipConfig,
+                        {
+                            content: 'This port has been removed.'
+                        }));
                 }
-            } else if (port.exists === false) {
-                $('<div class="remote-port-removed"/>').appendTo(portContainerEditContainer).qtip($.extend({},
-                    nfCommon.config.tooltipConfig,
-                    {
-                        content: 'This port has been removed.'
-                    }));
             }
 
             // only allow modifications to transmission when the swtich is defined
@@ -376,11 +378,7 @@
                     var remoteProcessGroupPortEntity = {
                         'revision': nfClient.getRevision(remoteProcessGroupData),
                         'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
-                        'remoteProcessGroupPort': {
-                            id: port.id,
-                            groupId: remoteProcessGroupId,
-                            transmitting: isTransmitting
-                        }
+                        'state': isTransmitting ? "TRANSMITTING" : "STOPPED"
                     };
 
                     // determine the type of port this is
@@ -393,7 +391,7 @@
                     $.ajax({
                         type: 'PUT',
                         data: JSON.stringify(remoteProcessGroupPortEntity),
-                        url: remoteProcessGroupData.uri + portContextPath + encodeURIComponent(port.id),
+                        url: remoteProcessGroupData.uri + portContextPath + encodeURIComponent(port.id) + "/run-status",
                         dataType: 'json',
                         contentType: 'application/json'
                     }).done(function (response) {
@@ -468,7 +466,7 @@
         } else {
             // show the disabled transmission switch
             if (port.transmitting === true) {
-                (nfNgBridge.injector.get('$compile')($('<md-switch style="margin:0px" class="md-primary disabled-active-transmission" aria-label="Toggle port transmission"></md-switch>'))(nfNgBridge.rootScope)).appendTo(portContainerEditContainer);
+                (nfNgBridge.injector.get('$compile')($('<md-switch style="margin:0px" class="md-primary enabled-inactive-transmission" aria-label="Toggle port transmission"></md-switch>'))(nfNgBridge.rootScope)).appendTo(portContainerEditContainer);
             } else {
                 (nfNgBridge.injector.get('$compile')($('<md-switch ng-disabled="true" style="margin:0px" class="md-primary disabled-inactive-transmission" aria-label="Toggle port transmission"></md-switch>'))(nfNgBridge.rootScope)).appendTo(portContainerEditContainer);
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
index ce3755c..e89a6e1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-remote-process-group.js
@@ -659,7 +659,7 @@
     };
 
     var hasIssues = function (d) {
-        return !nfCommon.isEmpty(d.component.authorizationIssues) || !nfCommon.isEmpty(d.component.validationErrors);
+        return d.status.validationStatus === 'INVALID';
     };
 
     var getIssues = function (d) {
@@ -723,41 +723,36 @@
         // authorization issues
         // --------------------
 
-        // TODO - only consider state from the status
         // update the process groups transmission status
         updated.select('text.remote-process-group-transmission-status')
             .text(function (d) {
                 var icon = '';
-                if (d.permissions.canRead) {
-                    if (hasIssues(d)) {
-                        icon = '\uf071';
-                    } else if (d.component.transmitting === true) {
-                        icon = '\uf140';
-                    } else {
-                        icon = '\ue80a';
-                    }
+                if (hasIssues(d)) {
+                    icon = '\uf071';
+                } else if (d.status.transmissionStatus === 'Transmitting') {
+                    icon = '\uf140';
+                } else {
+                    icon = '\ue80a';
                 }
                 return icon;
             })
             .attr('font-family', function (d) {
                 var family = '';
-                if (d.permissions.canRead) {
-                    if (hasIssues(d) || d.component.transmitting) {
-                        family = 'FontAwesome';
-                    } else {
-                        family = 'flowfont';
-                    }
+                if (hasIssues(d) || d.status.transmissionStatus === 'Transmitting') {
+                    family = 'FontAwesome';
+                } else {
+                    family = 'flowfont';
                 }
                 return family;
             })
             .classed('invalid', function (d) {
-                return d.permissions.canRead && hasIssues(d);
+                return hasIssues(d);
             })
             .classed('transmitting', function (d) {
-                return d.permissions.canRead && !hasIssues(d) && d.component.transmitting === true;
+                return !hasIssues(d) && d.status.transmissionStatus === 'Transmitting';
             })
             .classed('not-transmitting', function (d) {
-                return d.permissions.canRead && !hasIssues(d) && d.component.transmitting === false;
+                return !hasIssues(d) && d.status.transmissionStatus !== 'Transmitting';
             })
             .each(function (d) {
                 // get the tip

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
index 0d8d92d..32ea231 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-reporting-task.js
@@ -247,22 +247,22 @@
         var entity = {
             'revision': nfClient.getRevision(reportingTaskEntity),
             'disconnectedNodeAcknowledged': nfStorage.isDisconnectionAcknowledged(),
-            'component': {
-                'id': reportingTaskEntity.id,
-                'state': running === true ? 'RUNNING' : 'STOPPED'
-            }
+            'state': running === true ? 'RUNNING' : 'STOPPED'
         };
 
         return $.ajax({
             type: 'PUT',
-            url: reportingTaskEntity.uri,
+            url: reportingTaskEntity.uri + '/run-status',
             data: JSON.stringify(entity),
             dataType: 'json',
             contentType: 'application/json'
         }).done(function (response) {
             // update the task
             renderReportingTask(response);
-            nfControllerService.reloadReferencedServices(getControllerServicesTable(), response.component);
+            // component can be null if the user only has 'operate' permission without 'read'.
+            if (nfCommon.isDefinedAndNotNull(response.component)) {
+                nfControllerService.reloadReferencedServices(getControllerServicesTable(), response.component);
+            }
         }).fail(nfErrorHandler.handleAjaxError);
     };
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
index 2ba7952..a96267f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-settings.js
@@ -1006,23 +1006,19 @@
         };
 
         var reportingTaskRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
-            if (!dataContext.permissions.canRead) {
-                return '';
-            }
-
             // determine the appropriate label
             var icon = '', label = '';
-            if (dataContext.component.validationStatus === 'VALIDATING') {
+            if (dataContext.status.validationStatus === 'VALIDATING') {
                 icon = 'validating fa fa-spin fa-circle-notch';
                 label = 'Validating';
-            } else if (dataContext.component.validationStatus === 'INVALID') {
+            } else if (dataContext.status.validationStatus === 'INVALID') {
                 icon = 'invalid fa fa-warning';
                 label = 'Invalid';
             } else {
-                if (dataContext.component.state === 'STOPPED') {
+                if (dataContext.status.runStatus === 'STOPPED') {
                     label = 'Stopped';
                     icon = 'fa fa-stop stopped';
-                } else if (dataContext.component.state === 'RUNNING') {
+                } else if (dataContext.status.runStatus === 'RUNNING') {
                     label = 'Running';
                     icon = 'fa fa-play running';
                 } else {
@@ -1033,8 +1029,8 @@
 
             // include the active thread count if appropriate
             var activeThreadCount = '';
-            if (nfCommon.isDefinedAndNotNull(dataContext.component.activeThreadCount) && dataContext.component.activeThreadCount > 0) {
-                activeThreadCount = '(' + dataContext.component.activeThreadCount + ')';
+            if (nfCommon.isDefinedAndNotNull(dataContext.status.activeThreadCount) && dataContext.status.activeThreadCount > 0) {
+                activeThreadCount = '(' + dataContext.status.activeThreadCount + ')';
             }
 
             // format the markup
@@ -1045,29 +1041,38 @@
         var reportingTaskActionFormatter = function (row, cell, value, columnDef, dataContext) {
             var markup = '';
 
-            if (dataContext.permissions.canRead && dataContext.permissions.canWrite) {
-                if (dataContext.component.state === 'RUNNING') {
+            var canWrite = dataContext.permissions.canWrite;
+            var canRead = dataContext.permissions.canRead;
+            var canOperate = dataContext.operatePermissions.canWrite || canWrite;
+            var isStopped = dataContext.status.runStatus === 'STOPPED';
+
+            if (dataContext.status.runStatus === 'RUNNING') {
+                if (canOperate) {
                     markup += '<div title="Stop" class="pointer stop-reporting-task fa fa-stop"></div>';
-                } else if (dataContext.component.state === 'STOPPED' || dataContext.component.state === 'DISABLED') {
-                    markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil"></div>';
+                }
 
-                    // support starting when stopped and no validation errors
-                    if (dataContext.component.state === 'STOPPED' && nfCommon.isEmpty(dataContext.component.validationErrors)) {
-                        markup += '<div title="Start" class="pointer start-reporting-task fa fa-play"></div>';
-                    }
+            } else if (isStopped || dataContext.status.runStatus === 'DISABLED') {
 
-                    if (dataContext.component.multipleVersionsAvailable === true) {
-                        markup += '<div title="Change Version" class="pointer change-version-reporting-task fa fa-exchange"></div>';
-                    }
+                if (canRead && canWrite) {
+                    markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil"></div>';
+                }
 
-                    if (nfCommon.canModifyController()) {
-                        markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash"></div>';
-                    }
+                // support starting when stopped and no validation errors
+                if (canOperate && dataContext.status.runStatus === 'STOPPED' && dataContext.status.validationStatus === 'VALID') {
+                    markup += '<div title="Start" class="pointer start-reporting-task fa fa-play"></div>';
                 }
 
-                if (dataContext.component.persistsState === true) {
-                    markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks"></div>';
+                if (canRead && canWrite && dataContext.component.multipleVersionsAvailable === true) {
+                    markup += '<div title="Change Version" class="pointer change-version-reporting-task fa fa-exchange"></div>';
                 }
+
+                if (canRead && canWrite && nfCommon.canModifyController()) {
+                    markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash"></div>';
+                }
+            }
+
+            if (canRead && canWrite && dataContext.component.persistsState === true) {
+                markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks"></div>';
             }
 
             // allow policy configuration conditionally
@@ -1173,7 +1178,7 @@
                 } else if (target.hasClass('delete-reporting-task')) {
                     nfReportingTask.promptToDeleteReportingTask(reportingTaskEntity);
                 } else if (target.hasClass('view-state-reporting-task')) {
-                    var canClear = reportingTaskEntity.component.state === 'STOPPED' && reportingTaskEntity.component.activeThreadCount === 0;
+                    var canClear = reportingTaskEntity.status.runStatus === 'STOPPED' && reportingTaskEntity.status.activeThreadCount === 0;
                     nfComponentState.showState(reportingTaskEntity, canClear);
                 } else if (target.hasClass('change-version-reporting-task')) {
                     nfComponentVersion.promptForVersionChange(reportingTaskEntity);

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
index 83fba59..c07b95d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-common.js
@@ -1640,6 +1640,16 @@
                 })
                 .map(policyTypeListing, d3.map);
             return nest.get(value)[0];
+        },
+
+        /**
+         * Get component name from an entity safely.
+         *
+         * @param {object} entity    The component entity
+         * @returns {String}         The component name if it can be read, otherwise entity id
+         */
+        getComponentName: function (entity) {
+            return entity.permissions.canRead === true ? entity.component.name : entity.id;
         }
     };
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
index 48e56f3..04f4807 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/users/nf-users-table.js
@@ -574,6 +574,9 @@
         } else if (resource.startsWith('/data')) {
             resource = nfCommon.substringAfterFirst(resource, '/data');
             policyLabel += 'Data policy for ';
+        } else if (resource.startsWith('/operation')) {
+            resource = nfCommon.substringAfterFirst(resource, '/operation');
+            policyLabel += 'Operate policy for ';
         } else {
             policyLabel += 'Component policy for ';
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
index 29b7242..cdf4047 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/pom.xml
@@ -45,6 +45,7 @@
         <module>nifi-authorizer</module>
         <module>nifi-properties-loader</module>
         <module>nifi-standard-prioritizers</module>
+        <module>nifi-mock-authorizer</module>
     </modules>
     <dependencies>
         <dependency>


[3/4] nifi git commit: NIFI-375: Added operation policy

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMerger.java
index f14b696..099f2ae 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMerger.java
@@ -128,7 +128,8 @@ public class ControllerServiceEntityMerger implements ComponentEntityMerger<Cont
 
         final Map<String, Integer> activeThreadCounts = new HashMap<>();
         final Map<String, String> states = new HashMap<>();
-        final Map<String, PermissionsDTO> canReads = new HashMap<>();
+        final Map<String, PermissionsDTO> permissionsHolder = new HashMap<>();
+        final Map<String, PermissionsDTO> operatePermissionsHolder = new HashMap<>();
         for (final Map.Entry<NodeIdentifier, Set<ControllerServiceReferencingComponentEntity>> nodeEntry : referencingComponentMap.entrySet()) {
             final Set<ControllerServiceReferencingComponentEntity> nodeReferencingComponents = nodeEntry.getValue();
 
@@ -160,15 +161,8 @@ public class ControllerServiceEntityMerger implements ComponentEntityMerger<Cont
                     }
 
                     // handle read permissions
-                    final PermissionsDTO mergedPermissions = canReads.get(nodeReferencingComponentEntity.getId());
-                    final PermissionsDTO permissions = nodeReferencingComponentEntity.getPermissions();
-                    if (permissions != null) {
-                        if (mergedPermissions == null) {
-                            canReads.put(nodeReferencingComponentEntity.getId(), permissions);
-                        } else {
-                            PermissionsDtoMerger.mergePermissions(mergedPermissions, permissions);
-                        }
-                    }
+                    mergePermissions(permissionsHolder, nodeReferencingComponentEntity, nodeReferencingComponentEntity.getPermissions());
+                    mergePermissions(operatePermissionsHolder, nodeReferencingComponentEntity, nodeReferencingComponentEntity.getOperatePermissions());
                 }
             }
         }
@@ -176,7 +170,8 @@ public class ControllerServiceEntityMerger implements ComponentEntityMerger<Cont
         // go through each referencing components
         if (referencingComponents != null) {
             for (final ControllerServiceReferencingComponentEntity referencingComponent : referencingComponents) {
-                final PermissionsDTO permissions = canReads.get(referencingComponent.getId());
+                final PermissionsDTO permissions = permissionsHolder.get(referencingComponent.getId());
+                final PermissionsDTO operatePermissions = operatePermissionsHolder.get(referencingComponent.getId());
                 if (permissions != null && permissions.getCanRead() != null && permissions.getCanRead()) {
                     final Integer activeThreadCount = activeThreadCounts.get(referencingComponent.getId());
                     if (activeThreadCount != null) {
@@ -203,12 +198,24 @@ public class ControllerServiceEntityMerger implements ComponentEntityMerger<Cont
                     mergeControllerServiceReferencingComponent(referencingComponent, nodeEntities);
                 } else {
                     referencingComponent.setPermissions(permissions);
+                    referencingComponent.setOperatePermissions(operatePermissions);
                     referencingComponent.setComponent(null);
                 }
             }
         }
     }
 
+    private static void mergePermissions(Map<String, PermissionsDTO> permissionsHolder, ControllerServiceReferencingComponentEntity nodeReferencingComponentEntity, PermissionsDTO permissions) {
+        final PermissionsDTO mergedPermissions = permissionsHolder.get(nodeReferencingComponentEntity.getId());
+        if (permissions != null) {
+            if (mergedPermissions == null) {
+                permissionsHolder.put(nodeReferencingComponentEntity.getId(), permissions);
+            } else {
+                PermissionsDtoMerger.mergePermissions(mergedPermissions, permissions);
+            }
+        }
+    }
+
     private static void mergeControllerServiceReferencingComponent(ControllerServiceReferencingComponentEntity clientEntity, Map<NodeIdentifier,
             ControllerServiceReferencingComponentEntity> nodeEntities) {
         final Map<String, Map<NodeIdentifier, PropertyDescriptorDTO>> propertyDescriptorMap = new HashMap<>();

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/PermissionsDtoMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/PermissionsDtoMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/PermissionsDtoMerger.java
index 688a594..2c30888 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/PermissionsDtoMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/PermissionsDtoMerger.java
@@ -27,6 +27,9 @@ public class PermissionsDtoMerger {
      * @param entityPermissions       an {@link PermissionsDTO} to be merged
      */
     public static void mergePermissions(PermissionsDTO mergedEntityPermissions, PermissionsDTO entityPermissions) {
+        if (mergedEntityPermissions == null || entityPermissions == null) {
+            return;
+        }
         if (mergedEntityPermissions.getCanRead() && !entityPermissions.getCanRead()) {
             mergedEntityPermissions.setCanRead(false);
         }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
index d15e4f6..b44f8d6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/StatusMerger.java
@@ -304,6 +304,7 @@ public class StatusMerger {
             target.setId(toMerge.getId());
             target.setName(toMerge.getName());
             target.setTargetUri(toMerge.getTargetUri());
+            target.setValidationStatus(toMerge.getValidationStatus());
         }
 
         merge(target.getAggregateSnapshot(), targetReadablePermission, toMerge.getAggregateSnapshot(), toMergeReadablePermission);

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMergerSpec.groovy
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMergerSpec.groovy b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMergerSpec.groovy
index f616891..bb1d595 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMergerSpec.groovy
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/ControllerServiceEntityMergerSpec.groovy
@@ -49,42 +49,96 @@ class ControllerServiceEntityMergerSpec extends Specification {
         where:
         nodeEntityMap                                    ||
                 expectedMergedEntity
+
         // Simple ControllerServiceEntity merging
-        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
+        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1',
+                permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                 component: new ControllerServiceDTO()),
-         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: false, canWrite: false),
+
+         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: false, canWrite: false),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                  component: new ControllerServiceDTO()),
-         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: false),
                  component: new ControllerServiceDTO())] ||
-                new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: false, canWrite: false))
+
+                new ControllerServiceEntity(id: '1',
+                        permissions: new PermissionsDTO(canRead: false, canWrite: false),
+                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: false))
+
+
         // Controller Reference merging for canRead==false
-        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1',
+                permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                        permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                         component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))])),
-         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: false, canWrite: false),
+
+         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                         permissions: new PermissionsDTO(canRead: false, canWrite: false),
+                         operatePermissions: new PermissionsDTO(canRead: false, canWrite: false),
                          component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))])),
-         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                         permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                         operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                          component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))]))] ||
-                new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+                new ControllerServiceEntity(id: '1',
+                        permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                         bulletins: [],
                         component: new ControllerServiceDTO(validationErrors: [], validationStatus: "VALID",
-                                referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: false, canWrite: false))]))
+                                referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                                        permissions: new PermissionsDTO(canRead: false, canWrite: false),
+                                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: false))]))
+
+
         // Controller Reference merging for canRead==true
-        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+        [(createNodeIdentifier(1)): new ControllerServiceEntity(id: '1',
+                permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                        permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                         component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))])),
-         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+         (createNodeIdentifier(2)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                         permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                         operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                          component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))])),
-         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
-                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+         (createNodeIdentifier(3)): new ControllerServiceEntity(id: '1',
+                 permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                 operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
+                 component: new ControllerServiceDTO(referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                         permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                         operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                          component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 1, state: ControllerServiceState.ENABLING.name()))]))] ||
-                new ControllerServiceEntity(id: '1', permissions: new PermissionsDTO(canRead: true, canWrite: true),
+
+                new ControllerServiceEntity(id: '1',
+                        permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                         bulletins: [],
                         component: new ControllerServiceDTO(validationErrors: [], validationStatus: "VALID",
-                                referencingComponents: [new ControllerServiceReferencingComponentEntity(permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                                referencingComponents: [new ControllerServiceReferencingComponentEntity(
+                                        permissions: new PermissionsDTO(canRead: true, canWrite: true),
+                                        operatePermissions: new PermissionsDTO(canRead: false, canWrite: true),
                                         component: new ControllerServiceReferencingComponentDTO(activeThreadCount: 3, state: ControllerServiceState.ENABLING.name()))]))
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/RemoteProcessGroupEntityMergerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/RemoteProcessGroupEntityMergerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/RemoteProcessGroupEntityMergerTest.java
index 3faa622..ac73df7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/RemoteProcessGroupEntityMergerTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/test/java/org/apache/nifi/cluster/manager/RemoteProcessGroupEntityMergerTest.java
@@ -40,9 +40,13 @@ public class RemoteProcessGroupEntityMergerTest {
         final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
         final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
 
-        final PermissionsDTO permissed = new PermissionsDTO();
-        permissed.setCanRead(true);
-        permissed.setCanWrite(true);
+        final PermissionsDTO permissions = new PermissionsDTO();
+        permissions.setCanRead(true);
+        permissions.setCanWrite(true);
+
+        final PermissionsDTO opsPermissions = new PermissionsDTO();
+        opsPermissions.setCanRead(false);
+        opsPermissions.setCanWrite(false);
 
         final RemoteProcessGroupStatusDTO status = new RemoteProcessGroupStatusDTO();
         status.setAggregateSnapshot(new RemoteProcessGroupStatusSnapshotDTO());
@@ -71,7 +75,8 @@ public class RemoteProcessGroupEntityMergerTest {
         rpg1.setContents(contents1);
 
         final RemoteProcessGroupEntity entity1 = new RemoteProcessGroupEntity();
-        entity1.setPermissions(permissed);
+        entity1.setPermissions(permissions);
+        entity1.setOperatePermissions(opsPermissions);
         entity1.setStatus(status);
         entity1.setComponent(rpg1);
 
@@ -99,7 +104,8 @@ public class RemoteProcessGroupEntityMergerTest {
         rpg2.setContents(contents2);
 
         final RemoteProcessGroupEntity entity2 = new RemoteProcessGroupEntity();
-        entity2.setPermissions(permissed);
+        entity2.setPermissions(permissions);
+        entity2.setOperatePermissions(opsPermissions);
         entity2.setStatus(status);
         entity2.setComponent(rpg2);
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
index a70c8ef..90c87fc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/pom.xml
@@ -219,6 +219,12 @@
             <groupId>org.apache.nifi</groupId>
             <artifactId>nifi-authorizer</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock-authorizer</artifactId>
+            <version>1.8.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     <build>
         <plugins>

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
deleted file mode 100644
index 9b50b50..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.authorization;
-
-import org.apache.nifi.authorization.exception.AuthorizationAccessException;
-import org.apache.nifi.authorization.exception.AuthorizerCreationException;
-import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Mock implementation of AbstractPolicyBasedAuthorizer.
- */
-public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer {
-
-    private Set<Group> groups = new HashSet<>();
-    private Set<User> users = new HashSet<>();
-    private Set<AccessPolicy> policies = new HashSet<>();
-
-    public MockPolicyBasedAuthorizer() {
-
-    }
-
-    public MockPolicyBasedAuthorizer(Set<Group> groups, Set<User> users, Set<AccessPolicy> policies) {
-        if (groups != null) {
-            this.groups.addAll(groups);
-        }
-        if (users != null) {
-            this.users.addAll(users);
-        }
-        if (policies != null) {
-            this.policies.addAll(policies);
-        }
-    }
-
-    @Override
-    public Group doAddGroup(Group group) throws AuthorizationAccessException {
-        groups.add(group);
-        return group;
-    }
-
-    @Override
-    public Group getGroup(String identifier) throws AuthorizationAccessException {
-        return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
-        deleteGroup(group);
-        return addGroup(group);
-    }
-
-    @Override
-    public Group deleteGroup(Group group) throws AuthorizationAccessException {
-        groups.remove(group);
-        return group;
-    }
-
-    @Override
-    public Set<Group> getGroups() throws AuthorizationAccessException {
-        return groups;
-    }
-
-    @Override
-    public User doAddUser(User user) throws AuthorizationAccessException {
-        users.add(user);
-        return user;
-    }
-
-    @Override
-    public User getUser(String identifier) throws AuthorizationAccessException {
-        return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
-        return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get();
-    }
-
-    @Override
-    public User doUpdateUser(User user) throws AuthorizationAccessException {
-        deleteUser(user);
-        return addUser(user);
-    }
-
-    @Override
-    public User deleteUser(User user) throws AuthorizationAccessException {
-        users.remove(user);
-        return user;
-    }
-
-    @Override
-    public Set<User> getUsers() throws AuthorizationAccessException {
-        return users;
-    }
-
-    @Override
-    protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        policies.add(accessPolicy);
-        return accessPolicy;
-    }
-
-    @Override
-    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
-        return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        deleteAccessPolicy(accessPolicy);
-        return addAccessPolicy(accessPolicy);
-    }
-
-    @Override
-    public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
-        policies.remove(policy);
-        return policy;
-    }
-
-    @Override
-    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-        return policies;
-    }
-
-    @Override
-    public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
-        return new UsersAndAccessPolicies() {
-            @Override
-            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
-                return null;
-            }
-
-            @Override
-            public User getUser(String identity) {
-                return getUserByIdentity(identity);
-            }
-
-            @Override
-            public Set<Group> getGroups(String userIdentity) {
-                User user = getUserByIdentity(userIdentity);
-                if (user == null) {
-                    return new HashSet<>();
-                } else {
-                    return groups.stream()
-                            .filter(g -> g.getUsers().contains(user.getIdentifier()))
-                            .collect(Collectors.toSet());
-                }
-            }
-        };
-    }
-
-    @Override
-    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-
-    }
-
-    @Override
-    public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
-
-    }
-
-    @Override
-    public void preDestruction() throws AuthorizerDestructionException {
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/pom.xml
new file mode 100644
index 0000000..73ab439
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/pom.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Licensed to the Apache Software Foundation (ASF) under one or more contributor
+    license agreements. See the NOTICE file distributed with this work for additional
+    information regarding copyright ownership. The ASF licenses this file to
+    You under the Apache License, Version 2.0 (the "License"); you may not use
+    this file except in compliance with the License. You may obtain a copy of
+    the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required
+    by applicable law or agreed to in writing, software distributed under the
+    License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
+    OF ANY KIND, either express or implied. See the License for the specific
+    language governing permissions and limitations under the License. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>nifi-framework</artifactId>
+        <groupId>org.apache.nifi</groupId>
+        <version>1.8.0-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>nifi-mock-authorizer</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-framework-api</artifactId>
+        </dependency>
+    </dependencies>
+</project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/src/main/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/src/main/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/src/main/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
new file mode 100644
index 0000000..b2a9662
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-mock-authorizer/src/main/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Mock implementation of AbstractPolicyBasedAuthorizer.
+ */
+public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer implements AuthorizationAuditor {
+
+    private Set<Group> groups = new HashSet<>();
+    private Set<User> users = new HashSet<>();
+    private Set<AccessPolicy> policies = new HashSet<>();
+
+    private Set<AuthorizationRequest> audited = new HashSet();
+
+    public MockPolicyBasedAuthorizer() {
+
+    }
+
+    public MockPolicyBasedAuthorizer(Set<Group> groups, Set<User> users, Set<AccessPolicy> policies) {
+        if (groups != null) {
+            this.groups.addAll(groups);
+        }
+        if (users != null) {
+            this.users.addAll(users);
+        }
+        if (policies != null) {
+            this.policies.addAll(policies);
+        }
+    }
+
+    @Override
+    public Group doAddGroup(Group group) throws AuthorizationAccessException {
+        groups.add(group);
+        return group;
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws AuthorizationAccessException {
+        return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
+        deleteGroup(group);
+        return addGroup(group);
+    }
+
+    @Override
+    public Group deleteGroup(Group group) throws AuthorizationAccessException {
+        groups.remove(group);
+        return group;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return groups;
+    }
+
+    @Override
+    public User doAddUser(User user) throws AuthorizationAccessException {
+        users.add(user);
+        return user;
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException {
+        return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+        return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get();
+    }
+
+    @Override
+    public User doUpdateUser(User user) throws AuthorizationAccessException {
+        deleteUser(user);
+        return addUser(user);
+    }
+
+    @Override
+    public User deleteUser(User user) throws AuthorizationAccessException {
+        users.remove(user);
+        return user;
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return users;
+    }
+
+    @Override
+    protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        policies.add(accessPolicy);
+        return accessPolicy;
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
+        return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get();
+    }
+
+    @Override
+    public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
+        deleteAccessPolicy(accessPolicy);
+        return addAccessPolicy(accessPolicy);
+    }
+
+    @Override
+    public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
+        policies.remove(policy);
+        return policy;
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
+        return policies;
+    }
+
+    @Override
+    public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
+        return new UsersAndAccessPolicies() {
+            @Override
+            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
+                return policies.stream()
+                        .filter(policy -> policy.getResource().equals(resourceIdentifier) && policy.getAction().equals(action))
+                        .findFirst().orElse(null);
+            }
+
+            @Override
+            public User getUser(String identity) {
+                return getUserByIdentity(identity);
+            }
+
+            @Override
+            public Set<Group> getGroups(String userIdentity) {
+                User user = getUserByIdentity(userIdentity);
+                if (user == null) {
+                    return new HashSet<>();
+                } else {
+                    return groups.stream()
+                            .filter(g -> g.getUsers().contains(user.getIdentifier()))
+                            .collect(Collectors.toSet());
+                }
+            }
+        };
+    }
+
+    @Override
+    public void auditAccessAttempt(AuthorizationRequest request, AuthorizationResult result) {
+        audited.add(request);
+    }
+
+    public boolean isAudited(AuthorizationRequest request) {
+        return audited.contains(request);
+    }
+
+    @Override
+    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+
+    }
+
+    @Override
+    public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/authorization/StandardAuthorizableLookup.java
----------------------------------------------------------------------
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 88d8c46..2d79006 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
@@ -26,6 +26,7 @@ import org.apache.nifi.authorization.resource.ProvenanceDataAuthorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
 import org.apache.nifi.authorization.resource.ResourceType;
 import org.apache.nifi.authorization.resource.RestrictedComponentsAuthorizableFactory;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.resource.TenantAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.bundle.BundleCoordinate;
@@ -468,63 +469,59 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
     }
 
     @Override
-    public Authorizable getAuthorizableFromResource(String resource) {
+    public Authorizable getAuthorizableFromResource(final String resource) {
         // parse the resource type
-        ResourceType resourceType = null;
-        for (ResourceType type : ResourceType.values()) {
-            if (resource.equals(type.getValue()) || resource.startsWith(type.getValue() + "/")) {
-                resourceType = type;
-            }
-        }
-
+        final ResourceType resourceType = ResourceType.fromRawValue(resource);
         if (resourceType == null) {
             throw new ResourceNotFoundException("Unrecognized resource: " + resource);
         }
 
         // if this is a policy, data or a provenance event resource, there should be another resource type
-        if (ResourceType.Policy.equals(resourceType) || ResourceType.Data.equals(resourceType) || ResourceType.DataTransfer.equals(resourceType) || ResourceType.ProvenanceData.equals(resourceType)) {
-            final ResourceType primaryResourceType = resourceType;
-            resourceType = null;
+        switch (resourceType) {
+            case Policy:
+            case Data:
+            case DataTransfer:
+            case ProvenanceData:
+            case Operation:
 
-            // get the resource type
-            resource = StringUtils.substringAfter(resource, primaryResourceType.getValue());
+                // get the resource type
+                final String baseResource = StringUtils.substringAfter(resource, resourceType.getValue());
+                final ResourceType baseResourceType = ResourceType.fromRawValue(baseResource);
 
-            for (ResourceType type : ResourceType.values()) {
-                if (resource.equals(type.getValue()) || resource.startsWith(type.getValue() + "/")) {
-                    resourceType = type;
+                if (baseResourceType == null) {
+                    throw new ResourceNotFoundException("Unrecognized base resource: " + resource);
                 }
-            }
 
-            if (resourceType == null) {
-                throw new ResourceNotFoundException("Unrecognized base resource: " + resource);
-            }
+                switch (resourceType) {
+                    case Policy:
+                        return new AccessPolicyAuthorizable(getAccessPolicy(baseResourceType, resource));
+                    case Data:
+                        return new DataAuthorizable(getAccessPolicy(baseResourceType, resource));
+                    case DataTransfer:
+                        return new DataTransferAuthorizable(getAccessPolicy(baseResourceType, resource));
+                    case ProvenanceData:
+                        return new ProvenanceDataAuthorizable(getAccessPolicy(baseResourceType, resource));
+                    case Operation:
+                        return new OperationAuthorizable(getAccessPolicy(baseResourceType, resource));
+                }
 
-            // must either be a policy, event, or data transfer
-            if (ResourceType.Policy.equals(primaryResourceType)) {
-                return new AccessPolicyAuthorizable(getAccessPolicy(resourceType, resource));
-            } else if (ResourceType.Data.equals(primaryResourceType)) {
-                return new DataAuthorizable(getAccessPolicy(resourceType, resource));
-            } else if (ResourceType.ProvenanceData.equals(primaryResourceType)) {
-                return new ProvenanceDataAuthorizable(getAccessPolicy(resourceType, resource));
-            } else {
-                return new DataTransferAuthorizable(getAccessPolicy(resourceType, resource));
-            }
-        } else if (ResourceType.RestrictedComponents.equals(resourceType)) {
-            final String slashRequiredPermission = StringUtils.substringAfter(resource, resourceType.getValue());
+            case RestrictedComponents:
+                final String slashRequiredPermission = StringUtils.substringAfter(resource, resourceType.getValue());
 
-            if (slashRequiredPermission.startsWith("/")) {
-                final RequiredPermission requiredPermission = RequiredPermission.valueOfPermissionIdentifier(slashRequiredPermission.substring(1));
+                if (slashRequiredPermission.startsWith("/")) {
+                    final RequiredPermission requiredPermission = RequiredPermission.valueOfPermissionIdentifier(slashRequiredPermission.substring(1));
+
+                    if (requiredPermission == null) {
+                        throw new ResourceNotFoundException("Unrecognized resource: " + resource);
+                    }
 
-                if (requiredPermission == null) {
-                    throw new ResourceNotFoundException("Unrecognized resource: " + resource);
+                    return getRestrictedComponents(requiredPermission);
+                } else {
+                    return getRestrictedComponents();
                 }
 
-                return getRestrictedComponents(requiredPermission);
-            } else {
-                return getRestrictedComponents();
-            }
-        } else {
-            return getAccessPolicy(resourceType, resource);
+            default:
+                return getAccessPolicy(resourceType, resource);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
----------------------------------------------------------------------
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 700185c..b8ac88a 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
@@ -40,6 +40,7 @@ import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.EnforcePolicyPermissionsThroughBaseResource;
 import org.apache.nifi.authorization.resource.ResourceFactory;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.cluster.coordination.ClusterCoordinator;
@@ -668,10 +669,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 });
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processorNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processorNode));
         final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processorNode.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processorNode.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     private void awaitValidationCompletion(final ComponentNode component) {
@@ -796,10 +798,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 port -> dtoFactory.createPortDto(port));
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(inputPortNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(inputPortNode));
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(inputPortNode.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPortNode.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -811,10 +814,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 port -> dtoFactory.createPortDto(port));
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(outputPortNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(outputPortNode), NiFiUserUtils.getNiFiUser());
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(outputPortNode.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPortNode.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -827,11 +831,13 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 remoteProcessGroup -> dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup));
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
         final RevisionDTO updateRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
-        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroupNode.getIdentifier()));
+        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroupNode,
+                controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroupNode.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroupNode.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), updateRevision, permissions, status, bulletinEntities);
+        return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), updateRevision, permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -846,8 +852,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 remoteGroupPort -> dtoFactory.createRemoteProcessGroupPortDto(remoteGroupPort));
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
         final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
-        return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions);
+        return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions, operatePermissions);
     }
 
     @Override
@@ -862,8 +869,9 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 remoteGroupPort -> dtoFactory.createRemoteProcessGroupPortDto(remoteGroupPort));
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroupNode);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroupNode));
         final RevisionDTO updatedRevision = dtoFactory.createRevisionDTO(snapshot.getLastModification());
-        return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions);
+        return entityFactory.createRemoteProcessGroupPortEntity(snapshot.getComponent(), updatedRevision, permissions, operatePermissions);
     }
 
     @Override
@@ -1198,6 +1206,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     public ProcessorEntity deleteProcessor(final Revision revision, final String processorId) {
         final ProcessorNode processor = processorDAO.getProcessor(processorId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
         final ProcessorDTO snapshot = deleteComponent(
                 revision,
                 processor.getResource(),
@@ -1205,7 +1214,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createProcessorDto(processor));
 
-        return entityFactory.createProcessorEntity(snapshot, null, permissions, null, null);
+        return entityFactory.createProcessorEntity(snapshot, null, permissions, operatePermissions, null, null);
     }
 
     @Override
@@ -1479,6 +1488,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     public PortEntity deleteInputPort(final Revision revision, final String inputPortId) {
         final Port port = inputPortDAO.getPort(inputPortId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
         final PortDTO snapshot = deleteComponent(
                 revision,
                 port.getResource(),
@@ -1486,13 +1496,14 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createPortDto(port));
 
-        return entityFactory.createPortEntity(snapshot, null, permissions, null, null);
+        return entityFactory.createPortEntity(snapshot, null, permissions, operatePermissions, null, null);
     }
 
     @Override
     public PortEntity deleteOutputPort(final Revision revision, final String outputPortId) {
         final Port port = outputPortDAO.getPort(outputPortId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
         final PortDTO snapshot = deleteComponent(
                 revision,
                 port.getResource(),
@@ -1500,7 +1511,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createPortDto(port));
 
-        return entityFactory.createPortEntity(snapshot, null, permissions, null, null);
+        return entityFactory.createPortEntity(snapshot, null, permissions, operatePermissions, null, null);
     }
 
     @Override
@@ -1537,6 +1548,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     public RemoteProcessGroupEntity deleteRemoteProcessGroup(final Revision revision, final String remoteProcessGroupId) {
         final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
         final RemoteProcessGroupDTO snapshot = deleteComponent(
                 revision,
                 remoteProcessGroup.getResource(),
@@ -1544,7 +1556,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createRemoteProcessGroupDto(remoteProcessGroup));
 
-        return entityFactory.createRemoteProcessGroupEntity(snapshot, null, permissions, null, null);
+        return entityFactory.createRemoteProcessGroupEntity(snapshot, null, permissions, operatePermissions, null, null);
     }
 
     @Override
@@ -1602,10 +1614,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final ProcessorNode processor = processorDAO.getProcessor(processorDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
         final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processorDTO.getId()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processorDTO.getId()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -1852,10 +1865,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final Port port = inputPortDAO.getPort(inputPortDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(port.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -1868,10 +1882,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final Port port = outputPortDAO.getPort(outputPortDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port));
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(port.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -1900,10 +1915,12 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(remoteProcessGroupDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
-        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroup.getIdentifier()));
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
+        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroup, controllerFacade.getRemoteProcessGroupStatus(remoteProcessGroup.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroup.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, status, bulletinEntities);
+        return entityFactory.createRemoteProcessGroupEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()),
+                permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -2194,9 +2211,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletinEntities);
+        return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
     }
 
     @Override
@@ -2217,9 +2235,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 });
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(controllerServiceDTO.getId()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletinEntities);
+        return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
     }
 
 
@@ -2327,10 +2346,8 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final Set<ControllerServiceReferencingComponentEntity> componentEntities = new HashSet<>();
         for (final ComponentNode refComponent : referencingComponents) {
-            PermissionsDTO permissions = null;
-            if (refComponent instanceof Authorizable) {
-                permissions = dtoFactory.createPermissionsDto(refComponent);
-            }
+            final PermissionsDTO permissions = dtoFactory.createPermissionsDto(refComponent);
+            final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(refComponent));
 
             final Revision revision = revisions.get(refComponent.getIdentifier());
             final FlowModification flowMod = new FlowModification(revision, modifier);
@@ -2358,7 +2375,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 }
             }
 
-            componentEntities.add(entityFactory.createControllerServiceReferencingComponentEntity(dto, revisionDto, permissions));
+            componentEntities.add(entityFactory.createControllerServiceReferencingComponentEntity(refComponent.getIdentifier(), dto, revisionDto, permissions, operatePermissions));
         }
 
         final ControllerServiceReferencingComponentsEntity entity = new ControllerServiceReferencingComponentsEntity();
@@ -2370,6 +2387,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     public ControllerServiceEntity deleteControllerService(final Revision revision, final String controllerServiceId) {
         final ControllerServiceNode controllerService = controllerServiceDAO.getControllerService(controllerServiceId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(controllerService);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(controllerService));
         final ControllerServiceDTO snapshot = deleteComponent(
                 revision,
                 controllerService.getResource(),
@@ -2377,7 +2395,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createControllerServiceDto(controllerService));
 
-        return entityFactory.createControllerServiceEntity(snapshot, null, permissions, null);
+        return entityFactory.createControllerServiceEntity(snapshot, null, permissions, operatePermissions, null);
     }
 
 
@@ -2576,9 +2594,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskDTO.getId());
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletinEntities);
+        return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
     }
 
     @Override
@@ -2594,15 +2613,17 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 });
 
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, bulletinEntities);
+        return entityFactory.createReportingTaskEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
     }
 
     @Override
     public ReportingTaskEntity deleteReportingTask(final Revision revision, final String reportingTaskId) {
         final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
         final ReportingTaskDTO snapshot = deleteComponent(
                 revision,
                 reportingTask.getResource(),
@@ -2610,7 +2631,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 true,
                 dtoFactory.createReportingTaskDto(reportingTask));
 
-        return entityFactory.createReportingTaskEntity(snapshot, null, permissions, null);
+        return entityFactory.createReportingTaskEntity(snapshot, null, permissions, operatePermissions, null);
     }
 
     @Override
@@ -2850,10 +2871,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     private ProcessorEntity createProcessorEntity(final ProcessorNode processor, final NiFiUser user) {
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(processor.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(processor, user);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(processor));
         final ProcessorStatusDTO status = dtoFactory.createProcessorStatusDto(controllerFacade.getProcessorStatus(processor.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(processor.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createProcessorEntity(dtoFactory.createProcessorDto(processor), revision, permissions, status, bulletinEntities);
+        return entityFactory.createProcessorEntity(dtoFactory.createProcessorDto(processor), revision, permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -3353,19 +3375,21 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     private PortEntity createInputPortEntity(final Port port) {
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(port.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port, NiFiUserUtils.getNiFiUser());
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port), NiFiUserUtils.getNiFiUser());
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getInputPortStatus(port.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, operatePermissions, status, bulletinEntities);
     }
 
     private PortEntity createOutputPortEntity(final Port port) {
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(port.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(port, NiFiUserUtils.getNiFiUser());
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(port), NiFiUserUtils.getNiFiUser());
         final PortStatusDTO status = dtoFactory.createPortStatusDto(controllerFacade.getOutputPortStatus(port.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(port.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, status, bulletinEntities);
+        return entityFactory.createPortEntity(dtoFactory.createPortDto(port), revision, permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -3445,10 +3469,11 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     private RemoteProcessGroupEntity createRemoteGroupEntity(final RemoteProcessGroup rpg, final NiFiUser user) {
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(rpg.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(rpg, user);
-        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(controllerFacade.getRemoteProcessGroupStatus(rpg.getIdentifier()));
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(rpg), user);
+        final RemoteProcessGroupStatusDTO status = dtoFactory.createRemoteProcessGroupStatusDto(rpg, controllerFacade.getRemoteProcessGroupStatus(rpg.getIdentifier()));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(rpg.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createRemoteProcessGroupEntity(dtoFactory.createRemoteProcessGroupDto(rpg), revision, permissions, status, bulletinEntities);
+        return entityFactory.createRemoteProcessGroupEntity(dtoFactory.createRemoteProcessGroupDto(rpg), revision, permissions, operatePermissions, status, bulletinEntities);
     }
 
     @Override
@@ -3498,7 +3523,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     public RemoteProcessGroupStatusEntity getRemoteProcessGroupStatus(final String id) {
         final RemoteProcessGroup remoteProcessGroup = remoteProcessGroupDAO.getRemoteProcessGroup(id);
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(remoteProcessGroup);
-        final RemoteProcessGroupStatusDTO dto = dtoFactory.createRemoteProcessGroupStatusDto(controllerFacade.getRemoteProcessGroupStatus(id));
+        final RemoteProcessGroupStatusDTO dto = dtoFactory.createRemoteProcessGroupStatusDto(remoteProcessGroup, controllerFacade.getRemoteProcessGroupStatus(id));
         return entityFactory.createRemoteProcessGroupStatusEntity(dto, permissions);
     }
 
@@ -3573,9 +3598,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(serviceNode.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(serviceNode, NiFiUserUtils.getNiFiUser());
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(serviceNode), NiFiUserUtils.getNiFiUser());
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(serviceNode.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createControllerServiceEntity(dto, revision, permissions, bulletinEntities);
+        return entityFactory.createControllerServiceEntity(dto, revision, permissions, operatePermissions, bulletinEntities);
     }
 
     @Override
@@ -3664,9 +3690,10 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     private ReportingTaskEntity createReportingTaskEntity(final ReportingTaskNode reportingTask) {
         final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(reportingTask.getIdentifier()));
         final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
+        final PermissionsDTO operatePermissions = dtoFactory.createPermissionsDto(new OperationAuthorizable(reportingTask));
         final List<BulletinDTO> bulletins = dtoFactory.createBulletinDtos(bulletinRepository.findBulletinsForSource(reportingTask.getIdentifier()));
         final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-        return entityFactory.createReportingTaskEntity(dtoFactory.createReportingTaskDto(reportingTask), revision, permissions, bulletinEntities);
+        return entityFactory.createReportingTaskEntity(dtoFactory.createReportingTaskDto(reportingTask), revision, permissions, operatePermissions, bulletinEntities);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
index 2d2f5b9..668feba 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
@@ -28,6 +28,7 @@ import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.ComponentAuthorizable;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.controller.ScheduledState;
 import org.apache.nifi.controller.service.ControllerServiceState;
@@ -44,6 +45,7 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
 import org.apache.nifi.web.api.entity.ComponentStateEntity;
 import org.apache.nifi.web.api.entity.ControllerServiceEntity;
 import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
+import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
 import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.entity.UpdateControllerServiceReferenceRequestEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
@@ -437,7 +439,7 @@ public class ControllerServiceResource extends ApplicationResource {
             value = "Updates a controller services references",
             response = ControllerServiceReferencingComponentsEntity.class,
             authorizations = {
-                    @Authorization(value = "Write - /{component-type}/{uuid} - For each referencing component specified")
+                    @Authorization(value = "Write - /{component-type}/{uuid} or /operate/{component-type}/{uuid} - For each referencing component specified")
             }
     )
     @ApiResponses(
@@ -524,7 +526,7 @@ public class ControllerServiceResource extends ApplicationResource {
                 lookup -> {
                     requestReferencingRevisions.entrySet().stream().forEach(e -> {
                         final Authorizable controllerService = lookup.getControllerServiceReferencingComponent(id, e.getKey());
-                        controllerService.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+                        OperationAuthorizable.authorizeOperation(controllerService, authorizer, NiFiUserUtils.getNiFiUser());
                     });
                 },
                 () -> serviceFacade.verifyUpdateControllerServiceReferencingComponents(requestUpdateReferenceRequest.getId(), verifyScheduledState, verifyControllerServiceState),
@@ -741,6 +743,91 @@ public class ControllerServiceResource extends ApplicationResource {
         );
     }
 
+    /**
+     * Updates the operational status for the specified controller service with the specified values.
+     *
+     * @param httpServletRequest      request
+     * @param id                      The id of the controller service to update.
+     * @param requestRunStatus    A runStatusEntity.
+     * @return A controllerServiceEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a controller service",
+            response = ControllerServiceEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /controller-services/{uuid} or /operation/controller-services/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRunStatus(
+            @Context HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The controller service id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The controller service run status.",
+                    required = true
+            ) final ControllerServiceRunStatusEntity requestRunStatus) {
+
+        if (requestRunStatus == null) {
+            throw new IllegalArgumentException("Controller service run status must be specified.");
+        }
+
+        if (requestRunStatus.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRunStatus.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRunStatus);
+        }  else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRunStatus,
+                requestRevision,
+                lookup -> {
+                    // authorize the service
+                    final Authorizable authorizable = lookup.getControllerService(id).getAuthorizable();
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, NiFiUserUtils.getNiFiUser());
+                },
+                () -> serviceFacade.verifyUpdateControllerService(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+                (revision, runStatusEntity) -> {
+                    // update the controller service
+                    final ControllerServiceEntity entity = serviceFacade.updateControllerService(revision, createDTOWithDesiredRunStatus(id, runStatusEntity.getState()));
+                    populateRemainingControllerServiceEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private ControllerServiceDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+        final ControllerServiceDTO dto = new ControllerServiceDTO();
+        dto.setId(id);
+        dto.setState(runStatus);
+        return dto;
+    }
+
     // setters
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {
         this.serviceFacade = serviceFacade;


[4/4] nifi git commit: NIFI-375: Added operation policy

Posted by mc...@apache.org.
NIFI-375: Added operation policy

The operation policy allows that a user to operate components even if they does not have direct READ/WRITE
permission of the component.

Following operations are controlled by the new operate policy:
- Start/stop/enable/disable Processors, ControllerServices,
ReportingTasks, Input/OuputPorts
- Enable/disable transmission of RemoteInput/OutputPorts and
RemoteProcessGroups
- Terminate Processor threads

Refactored what API exposes

The previous commit let API exposes few fields in DTO. But we should
avoid returning partial DTO as it complicates authorization logic.

Instead, this commit adds StatusDTO for ReportingTaskEntity and
ControllerServiceEntity, so that it can be returned regardless of having
READ permission. Component DTO can only be returned with a READ
permission.

Refactor RPG same as ControllerService.

WIP incorporating review comments.

Incorporated review comments

- Cleaned up merger classes
- Recreate DTO instance at each function during two phase commmit

Restrict enabling ControllerService without read permission

Revert the last commit.

Fix review comments.

- Renamed confusing static method names and its parameters
- Removed unnecessary permission checks from UI condition

Fixed delete action display condition.

Fixed NPE at Summary.

Apply operation policy to activateControllerServices.

Removed OperationPermissible from ComponentEntity.

This closes #2990


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/f570cb98
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/f570cb98
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/f570cb98

Branch: refs/heads/master
Commit: f570cb980ddbb8292044ba3ff30304ed157dc839
Parents: 9161b17
Author: Koji Kawamura <ij...@apache.org>
Authored: Fri Aug 24 17:58:25 2018 +0900
Committer: Matt Gilman <ma...@gmail.com>
Committed: Wed Sep 19 15:28:41 2018 -0400

----------------------------------------------------------------------
 .../src/main/asciidoc/administration-guide.adoc |   4 +
 .../nifi-framework/nifi-authorizer/pom.xml      |   8 +
 .../MockPolicyBasedAuthorizer.java              | 196 -------------
 .../nifi/web/api/dto/ControllerServiceDTO.java  |   2 +-
 ...ontrollerServiceReferencingComponentDTO.java |   2 +-
 .../web/api/dto/status/ComponentStatusDTO.java  |  82 ++++++
 .../dto/status/ControllerServiceStatusDTO.java  |  37 +++
 .../dto/status/RemoteProcessGroupStatusDTO.java |  14 +
 .../api/dto/status/ReportingTaskStatusDTO.java  |  37 +++
 .../api/entity/ComponentRunStatusEntity.java    |  86 ++++++
 .../web/api/entity/ControllerServiceEntity.java |  54 +++-
 ...rollerServiceReferencingComponentEntity.java |  20 +-
 .../ControllerServiceRunStatusEntity.java       |  48 ++++
 .../web/api/entity/OperationPermissible.java    |  32 +++
 .../apache/nifi/web/api/entity/PortEntity.java  |  20 +-
 .../web/api/entity/PortRunStatusEntity.java     |  48 ++++
 .../nifi/web/api/entity/ProcessorEntity.java    |  20 +-
 .../api/entity/ProcessorRunStatusEntity.java    |  48 ++++
 .../api/entity/RemotePortRunStatusEntity.java   |  48 ++++
 .../api/entity/RemoteProcessGroupEntity.java    |  21 +-
 .../entity/RemoteProcessGroupPortEntity.java    |  20 +-
 .../web/api/entity/ReportingTaskEntity.java     |  39 ++-
 .../entity/ReportingTaskRunStatusEntity.java    |  48 ++++
 .../nifi-framework-authorization/pom.xml        |   6 +
 .../resource/OperationAuthorizable.java         |  85 ++++++
 .../authorization/resource/ResourceFactory.java |  27 ++
 .../authorization/resource/ResourceType.java    |  21 +-
 .../resource/OperationAuthorizableTest.java     | 226 +++++++++++++++
 .../cluster/manager/ComponentEntityMerger.java  |   6 +
 .../manager/ControllerServiceEntityMerger.java  |  29 +-
 .../cluster/manager/PermissionsDtoMerger.java   |   3 +
 .../nifi/cluster/manager/StatusMerger.java      |   1 +
 .../ControllerServiceEntityMergerSpec.groovy    |  94 +++++--
 .../RemoteProcessGroupEntityMergerTest.java     |  16 +-
 .../nifi-framework/nifi-framework-core/pom.xml  |   6 +
 .../MockPolicyBasedAuthorizer.java              | 183 ------------
 .../nifi-framework/nifi-mock-authorizer/pom.xml |  31 ++
 .../MockPolicyBasedAuthorizer.java              | 196 +++++++++++++
 .../StandardAuthorizableLookup.java             |  81 +++---
 .../nifi/web/StandardNiFiServiceFacade.java     |  97 ++++---
 .../nifi/web/api/ControllerServiceResource.java |  91 +++++-
 .../org/apache/nifi/web/api/FlowResource.java   |  21 +-
 .../apache/nifi/web/api/InputPortResource.java  |  90 ++++++
 .../apache/nifi/web/api/OutputPortResource.java |  91 ++++++
 .../apache/nifi/web/api/ProcessorResource.java  |  93 +++++-
 .../web/api/RemoteProcessGroupResource.java     | 282 +++++++++++++++++++
 .../nifi/web/api/ReportingTaskResource.java     |  90 ++++++
 .../org/apache/nifi/web/api/dto/DtoFactory.java |  43 ++-
 .../apache/nifi/web/api/dto/EntityFactory.java  |  44 ++-
 .../nifi/web/controller/ControllerFacade.java   |   7 +
 .../StandardAuthorizableLookupTest.java         |  69 +++++
 .../src/main/webapp/js/nf/canvas/nf-actions.js  |  42 +--
 .../main/webapp/js/nf/canvas/nf-canvas-utils.js |  29 +-
 .../main/webapp/js/nf/canvas/nf-context-menu.js |   6 +-
 .../js/nf/canvas/nf-controller-service.js       | 144 ++++++----
 .../js/nf/canvas/nf-controller-services.js      | 135 +++++----
 .../webapp/js/nf/canvas/nf-policy-management.js |   7 +
 .../nf/canvas/nf-remote-process-group-ports.js  |  68 +++--
 .../js/nf/canvas/nf-remote-process-group.js     |  33 +--
 .../webapp/js/nf/canvas/nf-reporting-task.js    |  12 +-
 .../src/main/webapp/js/nf/canvas/nf-settings.js |  59 ++--
 .../src/main/webapp/js/nf/nf-common.js          |  10 +
 .../main/webapp/js/nf/users/nf-users-table.js   |   3 +
 .../nifi-framework/pom.xml                      |   1 +
 64 files changed, 2709 insertions(+), 803 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-docs/src/main/asciidoc/administration-guide.adoc
----------------------------------------------------------------------
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index d67a3e1..18702fb 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -1538,6 +1538,10 @@ Component level access policies govern the following component level authorizati
 |Allows users to modify component configuration details
 |`resource="/<component-type>/<component-UUID>" action="W"`
 
+|operate the component
+|Allows users to operate components by changing component run status (start/stop/enable/disable), remote port transmission status, or terminating processor threads
+|`resource="/operation/<component-type>/<component-UUID>" action="W"`
+
 |view provenance
 |Allows users to view provenance events generated by this component
 |`resource="/provenance-data/<component-type>/<component-UUID>" action="R"`

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
index 0d4467a..feb2a15 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/pom.xml
@@ -96,6 +96,14 @@
             <artifactId>nifi-properties-loader</artifactId>
         </dependency>
 
+        <!-- Test dependencies -->
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock-authorizer</artifactId>
+            <version>1.8.0-SNAPSHOT</version>
+            <scope>test</scope>
+        </dependency>
+
         <!-- Spock testing dependencies-->
         <dependency>
             <groupId>org.spockframework</groupId>

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
deleted file mode 100644
index b2a9662..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/test/java/org/apache/nifi/authorization/MockPolicyBasedAuthorizer.java
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.nifi.authorization;
-
-import org.apache.nifi.authorization.exception.AuthorizationAccessException;
-import org.apache.nifi.authorization.exception.AuthorizerCreationException;
-import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
-
-import java.util.HashSet;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * Mock implementation of AbstractPolicyBasedAuthorizer.
- */
-public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer implements AuthorizationAuditor {
-
-    private Set<Group> groups = new HashSet<>();
-    private Set<User> users = new HashSet<>();
-    private Set<AccessPolicy> policies = new HashSet<>();
-
-    private Set<AuthorizationRequest> audited = new HashSet();
-
-    public MockPolicyBasedAuthorizer() {
-
-    }
-
-    public MockPolicyBasedAuthorizer(Set<Group> groups, Set<User> users, Set<AccessPolicy> policies) {
-        if (groups != null) {
-            this.groups.addAll(groups);
-        }
-        if (users != null) {
-            this.users.addAll(users);
-        }
-        if (policies != null) {
-            this.policies.addAll(policies);
-        }
-    }
-
-    @Override
-    public Group doAddGroup(Group group) throws AuthorizationAccessException {
-        groups.add(group);
-        return group;
-    }
-
-    @Override
-    public Group getGroup(String identifier) throws AuthorizationAccessException {
-        return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
-        deleteGroup(group);
-        return addGroup(group);
-    }
-
-    @Override
-    public Group deleteGroup(Group group) throws AuthorizationAccessException {
-        groups.remove(group);
-        return group;
-    }
-
-    @Override
-    public Set<Group> getGroups() throws AuthorizationAccessException {
-        return groups;
-    }
-
-    @Override
-    public User doAddUser(User user) throws AuthorizationAccessException {
-        users.add(user);
-        return user;
-    }
-
-    @Override
-    public User getUser(String identifier) throws AuthorizationAccessException {
-        return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
-        return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get();
-    }
-
-    @Override
-    public User doUpdateUser(User user) throws AuthorizationAccessException {
-        deleteUser(user);
-        return addUser(user);
-    }
-
-    @Override
-    public User deleteUser(User user) throws AuthorizationAccessException {
-        users.remove(user);
-        return user;
-    }
-
-    @Override
-    public Set<User> getUsers() throws AuthorizationAccessException {
-        return users;
-    }
-
-    @Override
-    protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        policies.add(accessPolicy);
-        return accessPolicy;
-    }
-
-    @Override
-    public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
-        return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get();
-    }
-
-    @Override
-    public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
-        deleteAccessPolicy(accessPolicy);
-        return addAccessPolicy(accessPolicy);
-    }
-
-    @Override
-    public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
-        policies.remove(policy);
-        return policy;
-    }
-
-    @Override
-    public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
-        return policies;
-    }
-
-    @Override
-    public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
-        return new UsersAndAccessPolicies() {
-            @Override
-            public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
-                return policies.stream()
-                        .filter(policy -> policy.getResource().equals(resourceIdentifier) && policy.getAction().equals(action))
-                        .findFirst().orElse(null);
-            }
-
-            @Override
-            public User getUser(String identity) {
-                return getUserByIdentity(identity);
-            }
-
-            @Override
-            public Set<Group> getGroups(String userIdentity) {
-                User user = getUserByIdentity(userIdentity);
-                if (user == null) {
-                    return new HashSet<>();
-                } else {
-                    return groups.stream()
-                            .filter(g -> g.getUsers().contains(user.getIdentifier()))
-                            .collect(Collectors.toSet());
-                }
-            }
-        };
-    }
-
-    @Override
-    public void auditAccessAttempt(AuthorizationRequest request, AuthorizationResult result) {
-        audited.add(request);
-    }
-
-    public boolean isAudited(AuthorizationRequest request) {
-        return audited.contains(request);
-    }
-
-    @Override
-    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
-
-    }
-
-    @Override
-    public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
-
-    }
-
-    @Override
-    public void preDestruction() throws AuthorizerDestructionException {
-
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
index dbea636..8a359b6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
@@ -302,7 +302,7 @@ public class ControllerServiceDTO extends ComponentDTO {
         this.validationErrors = validationErrors;
     }
 
-    @ApiModelProperty(value = "Indicates whether the Processor is valid, invalid, or still in the process of validating (i.e., it is unknown whether or not the Processor is valid)",
+    @ApiModelProperty(value = "Indicates whether the ControllerService is valid, invalid, or still in the process of validating (i.e., it is unknown whether or not the ControllerService is valid)",
         readOnly = true,
         allowableValues = VALID + ", " + INVALID + ", " + VALIDATING)
     public String getValidationStatus() {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
index 9e48d74..9aa5800 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceReferencingComponentDTO.java
@@ -94,7 +94,7 @@ public class ControllerServiceReferencingComponentDTO {
      * @return type for this component referencing a controller service
      */
     @ApiModelProperty(
-            value = "The type of the component referencing a controller service."
+            value = "The type of the component referencing a controller service in simple Java class name format without package name."
     )
     public String getType() {
         return type;

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ComponentStatusDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ComponentStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ComponentStatusDTO.java
new file mode 100644
index 0000000..f33338c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ComponentStatusDTO.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.web.api.dto.status;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * General DTO for serializing the status of a component.
+ * This DTO is used to expose component status to API clients.
+ * This DTO must NOT contain any sensitive information as it can be returned
+ * to API clients regardless of having READ privilege to the component.
+ */
+@XmlType(name = "componentStatus")
+public class ComponentStatusDTO{
+
+    public static final String VALID = "VALID";
+    public static final String INVALID = "INVALID";
+    public static final String VALIDATING = "VALIDATING";
+
+    private String runStatus;
+    private String validationStatus;
+    private Integer activeThreadCount;
+
+    /**
+     * Sub-classes should override this method to provide API documentation using ApiModelProperty annotation with allowable values.
+     * @return the run status of the component
+     */
+    @ApiModelProperty(value = "The run status of this component",
+            readOnly = true,
+            allowableValues = "ENABLED, ENABLING, DISABLED, DISABLING")
+    public String getRunStatus() {
+        return runStatus;
+    }
+
+    public void setRunStatus(String runStatus) {
+        this.runStatus = runStatus;
+    }
+
+    @ApiModelProperty(value = "Indicates whether the component is valid, invalid, or still in the process of validating" +
+            " (i.e., it is unknown whether or not the component is valid)",
+            readOnly = true,
+            allowableValues = VALID + ", " + INVALID + ", " + VALIDATING)
+    public String getValidationStatus() {
+        return validationStatus;
+    }
+
+    public void setValidationStatus(String validationStatus) {
+        this.validationStatus = validationStatus;
+    }
+
+    /**
+     * @return number of active threads for this component
+     */
+    @ApiModelProperty(
+            value = "The number of active threads for the component."
+    )
+    public Integer getActiveThreadCount() {
+        return activeThreadCount;
+    }
+
+    public void setActiveThreadCount(Integer activeThreadCount) {
+        this.activeThreadCount = activeThreadCount;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerServiceStatusDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerServiceStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerServiceStatusDTO.java
new file mode 100644
index 0000000..891da9f
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ControllerServiceStatusDTO.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.web.api.dto.status;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * DTO for serializing the status of a ControllerService.
+ */
+@XmlType(name = "controllerServiceStatus")
+public class ControllerServiceStatusDTO extends ComponentStatusDTO {
+
+    @ApiModelProperty(value = "The run status of this ControllerService",
+            readOnly = true,
+            allowableValues = "ENABLED, ENABLING, DISABLED, DISABLING")
+    @Override
+    public String getRunStatus() {
+        return super.getRunStatus();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/RemoteProcessGroupStatusDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/RemoteProcessGroupStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/RemoteProcessGroupStatusDTO.java
index b71c42a..33db24b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/RemoteProcessGroupStatusDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/RemoteProcessGroupStatusDTO.java
@@ -33,6 +33,7 @@ public class RemoteProcessGroupStatusDTO {
     private String targetUri;
     private String transmissionStatus;
     private Date statsLastRefreshed;
+    private String validationStatus;
 
     private RemoteProcessGroupStatusSnapshotDTO aggregateSnapshot;
     private List<NodeRemoteProcessGroupStatusSnapshotDTO> nodeSnapshots;
@@ -119,4 +120,17 @@ public class RemoteProcessGroupStatusDTO {
     public void setStatsLastRefreshed(Date statsLastRefreshed) {
         this.statsLastRefreshed = statsLastRefreshed;
     }
+
+    @ApiModelProperty(value = "Indicates whether the component is valid, invalid, or still in the process of validating" +
+            " (i.e., it is unknown whether or not the component is valid)",
+            readOnly = true,
+            allowableValues = "VALID, INVALID, VALIDATING")
+    public String getValidationStatus() {
+        return validationStatus;
+    }
+
+    public void setValidationStatus(String validationStatus) {
+        this.validationStatus = validationStatus;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ReportingTaskStatusDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ReportingTaskStatusDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ReportingTaskStatusDTO.java
new file mode 100644
index 0000000..57ad553
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/status/ReportingTaskStatusDTO.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.nifi.web.api.dto.status;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * DTO for serializing the status of a ReportingTask.
+ */
+@XmlType(name = "reportingTaskStatus")
+public class ReportingTaskStatusDTO extends ComponentStatusDTO {
+
+    @ApiModelProperty(value = "The run status of this ReportingTask",
+            readOnly = true,
+            allowableValues = "RUNNING, STOPPED")
+    @Override
+    public String getRunStatus() {
+        return super.getRunStatus();
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentRunStatusEntity.java
new file mode 100644
index 0000000..2947668
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ComponentRunStatusEntity.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.RevisionDTO;
+
+import javax.xml.bind.annotation.XmlType;
+import java.util.Arrays;
+
+/**
+ * Run status for a given component.
+ */
+@XmlType(name = "componentRunStatus")
+public abstract class ComponentRunStatusEntity extends Entity {
+
+    private RevisionDTO revision;
+    private String state;
+    private Boolean disconnectedNodeAcknowledged;
+
+    /**
+     * @return revision for this request/response
+     */
+    @ApiModelProperty(
+            value = "The revision for this request/response. The revision is required for any mutable flow requests and is included in all responses."
+    )
+    public RevisionDTO getRevision() {
+        return revision;
+    }
+
+    public void setRevision(RevisionDTO revision) {
+        this.revision = revision;
+    }
+    /**
+     * Run status for this component.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the component."
+    )
+    public String getState() {
+        return this.state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    @ApiModelProperty(
+            value = "Acknowledges that this node is disconnected to allow for mutable requests to proceed."
+    )
+    public Boolean isDisconnectedNodeAcknowledged() {
+        return disconnectedNodeAcknowledged;
+    }
+
+    public void setDisconnectedNodeAcknowledged(Boolean disconnectedNodeAcknowledged) {
+        this.disconnectedNodeAcknowledged = disconnectedNodeAcknowledged;
+    }
+
+    protected abstract String[] getSupportedState();
+
+    public void validateState() {
+        if (state == null || state.isEmpty()) {
+            throw new IllegalArgumentException("The desired state is not set.");
+        }
+
+        if (Arrays.stream(getSupportedState()).noneMatch(state::equals)) {
+            throw new IllegalArgumentException(String.format("The desired state '%s' is not supported.", state));
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceEntity.java
index c9c3be3..1c2f4c0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceEntity.java
@@ -17,15 +17,36 @@
 package org.apache.nifi.web.api.entity;
 
 import javax.xml.bind.annotation.XmlRootElement;
+
+import io.swagger.annotations.ApiModelProperty;
 import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+import org.apache.nifi.web.api.dto.status.ControllerServiceStatusDTO;
 
 /**
  * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a controller service.
  */
 @XmlRootElement(name = "controllerServiceEntity")
-public class ControllerServiceEntity extends ComponentEntity implements Permissible<ControllerServiceDTO> {
+public class ControllerServiceEntity extends ComponentEntity implements Permissible<ControllerServiceDTO>, OperationPermissible {
 
+    private String parentGroupId;
     private ControllerServiceDTO component;
+    private PermissionsDTO operatePermissions;
+    private ControllerServiceStatusDTO status;
+
+    /**
+     * @return The id for the parent group of this ControllerService
+     */
+    @ApiModelProperty(
+            value = "The id of parent process group of this ControllerService."
+    )
+    public String getParentGroupId() {
+        return parentGroupId;
+    }
+
+    public void setParentGroupId(String parentGroupId) {
+        this.parentGroupId = parentGroupId;
+    }
 
     /**
      * @return controller service that is being serialized
@@ -38,4 +59,35 @@ public class ControllerServiceEntity extends ComponentEntity implements Permissi
         this.component = component;
     }
 
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO permissions) {
+        this.operatePermissions = permissions;
+    }
+
+    /**
+     * @return The status for this ControllerService
+     */
+    @ApiModelProperty(
+            value = "The status for this ControllerService.",
+            readOnly = true
+    )
+    public ControllerServiceStatusDTO getStatus() {
+        return status;
+    }
+
+    public void setStatus(ControllerServiceStatusDTO status) {
+        this.status = status;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceReferencingComponentEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceReferencingComponentEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceReferencingComponentEntity.java
index 26c9835..17a4c40 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceReferencingComponentEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceReferencingComponentEntity.java
@@ -19,16 +19,19 @@ package org.apache.nifi.web.api.entity;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
+import io.swagger.annotations.ApiModelProperty;
 import org.apache.nifi.web.api.dto.ControllerServiceReferencingComponentDTO;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 
 /**
  * A serialized representation of this class can be placed in the entity body of a response to the API.
  * This particular entity holds a reference to component that references a controller services.
  */
 @XmlRootElement(name = "controllerServiceReferencingComponentEntity")
-public class ControllerServiceReferencingComponentEntity extends ComponentEntity {
+public class ControllerServiceReferencingComponentEntity extends ComponentEntity implements OperationPermissible {
 
     private ControllerServiceReferencingComponentDTO component;
+    private PermissionsDTO operatePermissions;
 
     /**
      * @return controller service referencing components that is being serialized
@@ -41,4 +44,19 @@ public class ControllerServiceReferencingComponentEntity extends ComponentEntity
         this.component = component;
     }
 
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO permissions) {
+        this.operatePermissions = permissions;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceRunStatusEntity.java
new file mode 100644
index 0000000..7970147
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ControllerServiceRunStatusEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Run status for a given ControllerService.
+ */
+@XmlType(name = "controllerServiceRunStatus")
+public class ControllerServiceRunStatusEntity extends ComponentRunStatusEntity {
+
+    private static String[] SUPPORTED_STATE = {"ENABLED", "DISABLED"};
+
+    @Override
+    protected String[] getSupportedState() {
+        return SUPPORTED_STATE;
+    }
+
+    /**
+     * Run status for this ControllerService.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the ControllerService.",
+            allowableValues = "ENABLED, DISABLED"
+    )
+    public String getState() {
+        return super.getState();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/OperationPermissible.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/OperationPermissible.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/OperationPermissible.java
new file mode 100644
index 0000000..c6696d6
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/OperationPermissible.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import org.apache.nifi.web.api.dto.PermissionsDTO;
+
+/**
+ * Provides access to a {@link PermissionsDTO} for its operations.
+ * This is intended to be used by classes that extend {@link Entity} which can be operated by operators
+ * who does not have 'write' or 'read' access to the component, but has 'operate' access.
+ */
+public interface OperationPermissible {
+
+    PermissionsDTO getOperatePermissions();
+
+    void setOperatePermissions(PermissionsDTO permissions);
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortEntity.java
index 2454af1..c72b6e3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortEntity.java
@@ -17,6 +17,7 @@
 package org.apache.nifi.web.api.entity;
 
 import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.apache.nifi.web.api.dto.status.PortStatusDTO;
 
@@ -26,11 +27,12 @@ import javax.xml.bind.annotation.XmlRootElement;
  * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to an input PortDTO.
  */
 @XmlRootElement(name = "portEntity")
-public class PortEntity extends ComponentEntity implements Permissible<PortDTO> {
+public class PortEntity extends ComponentEntity implements Permissible<PortDTO>, OperationPermissible {
 
     private PortDTO component;
     private PortStatusDTO status;
     private String portType;
+    private PermissionsDTO operatePermissions;
 
     /**
      * @return input PortDTO that are being serialized
@@ -66,4 +68,20 @@ public class PortEntity extends ComponentEntity implements Permissible<PortDTO>
     public void setPortType(String portType) {
         this.portType = portType;
     }
+
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO permissions) {
+        this.operatePermissions = permissions;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortRunStatusEntity.java
new file mode 100644
index 0000000..a8b4471
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/PortRunStatusEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Run status for a given Port.
+ */
+@XmlType(name = "portRunStatus")
+public class PortRunStatusEntity extends ComponentRunStatusEntity {
+
+    private static String[] SUPPORTED_STATE = {"RUNNING", "STOPPED", "DISABLED"};
+
+    @Override
+    protected String[] getSupportedState() {
+        return SUPPORTED_STATE;
+    }
+
+    /**
+     * Run status for this Port.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the Port.",
+            allowableValues = "RUNNING, STOPPED, DISABLED"
+    )
+    public String getState() {
+        return super.getState();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorEntity.java
index 3607c0b..18b018d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorEntity.java
@@ -17,6 +17,7 @@
 package org.apache.nifi.web.api.entity;
 
 import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 import org.apache.nifi.web.api.dto.ProcessorDTO;
 import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
 
@@ -26,11 +27,12 @@ import javax.xml.bind.annotation.XmlRootElement;
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a ProcessorDTO.
  */
 @XmlRootElement(name = "processorEntity")
-public class ProcessorEntity extends ComponentEntity implements Permissible<ProcessorDTO> {
+public class ProcessorEntity extends ComponentEntity implements Permissible<ProcessorDTO>, OperationPermissible {
 
     private ProcessorDTO component;
     private String inputRequirement;
     private ProcessorStatusDTO status;
+    private PermissionsDTO operatePermissions;
 
     /**
      * The ProcessorDTO that is being serialized.
@@ -71,4 +73,20 @@ public class ProcessorEntity extends ComponentEntity implements Permissible<Proc
     public void setInputRequirement(String inputRequirement) {
         this.inputRequirement = inputRequirement;
     }
+
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO operatePermissions) {
+        this.operatePermissions = operatePermissions;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorRunStatusEntity.java
new file mode 100644
index 0000000..4f8ef74
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ProcessorRunStatusEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Run status for a given Processor.
+ */
+@XmlType(name = "processorRunStatus")
+public class ProcessorRunStatusEntity extends ComponentRunStatusEntity {
+
+    private static String[] SUPPORTED_STATE = {"RUNNING", "STOPPED", "DISABLED"};
+
+    @Override
+    protected String[] getSupportedState() {
+        return SUPPORTED_STATE;
+    }
+
+    /**
+     * Run status for this Processor.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the Processor.",
+            allowableValues = "RUNNING, STOPPED, DISABLED"
+    )
+    public String getState() {
+        return super.getState();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemotePortRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemotePortRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemotePortRunStatusEntity.java
new file mode 100644
index 0000000..e52bc94
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemotePortRunStatusEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Run status for a given RemotePort.
+ */
+@XmlType(name = "remotePortRunStatus")
+public class RemotePortRunStatusEntity extends ComponentRunStatusEntity {
+
+    private static String[] SUPPORTED_STATE = {"TRANSMITTING", "STOPPED"};
+
+    @Override
+    protected String[] getSupportedState() {
+        return SUPPORTED_STATE;
+    }
+
+    /**
+     * Run status for this RemotePort.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the RemotePort.",
+            allowableValues = "TRANSMITTING, STOPPED"
+    )
+    public String getState() {
+        return super.getState();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupEntity.java
index 8b049a4..28a6418 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupEntity.java
@@ -17,6 +17,7 @@
 package org.apache.nifi.web.api.entity;
 
 import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
 import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
 
@@ -26,7 +27,7 @@ import javax.xml.bind.annotation.XmlRootElement;
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a RemoteProcessGroupDTO.
  */
 @XmlRootElement(name = "remoteProcessGroupEntity")
-public class RemoteProcessGroupEntity extends ComponentEntity implements Permissible<RemoteProcessGroupDTO> {
+public class RemoteProcessGroupEntity extends ComponentEntity implements Permissible<RemoteProcessGroupDTO>, OperationPermissible {
 
     private RemoteProcessGroupDTO component;
     private RemoteProcessGroupStatusDTO status;
@@ -34,6 +35,8 @@ public class RemoteProcessGroupEntity extends ComponentEntity implements Permiss
     private Integer inputPortCount;
     private Integer outputPortCount;
 
+    private PermissionsDTO operatePermissions;
+
     /**
      * The RemoteProcessGroupDTO that is being serialized.
      *
@@ -88,4 +91,20 @@ public class RemoteProcessGroupEntity extends ComponentEntity implements Permiss
     public void setOutputPortCount(Integer outputPortCount) {
         this.outputPortCount = outputPortCount;
     }
+
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO permissions) {
+        this.operatePermissions = permissions;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupPortEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupPortEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupPortEntity.java
index 6216f95..837066f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupPortEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/RemoteProcessGroupPortEntity.java
@@ -18,15 +18,18 @@ package org.apache.nifi.web.api.entity;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
 
 /**
  * A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a RemoteProcessGroupPortDTO.
  */
 @XmlRootElement(name = "remoteProcessGroupPortEntity")
-public class RemoteProcessGroupPortEntity extends ComponentEntity {
+public class RemoteProcessGroupPortEntity extends ComponentEntity implements OperationPermissible {
 
     private RemoteProcessGroupPortDTO remoteProcessGroupPort;
+    private PermissionsDTO operatePermissions;
 
     /**
      * The RemoteProcessGroupPortDTO that is being serialized.
@@ -41,4 +44,19 @@ public class RemoteProcessGroupPortEntity extends ComponentEntity {
         this.remoteProcessGroupPort = remoteProcessGroupPort;
     }
 
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO operatePermissions) {
+        this.operatePermissions = operatePermissions;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskEntity.java
index a7bf6b3..3461bb2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskEntity.java
@@ -18,15 +18,22 @@ package org.apache.nifi.web.api.entity;
 
 import javax.xml.bind.annotation.XmlRootElement;
 
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.PermissionsDTO;
 import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.dto.status.ReportingTaskStatusDTO;
+
+import javax.xml.bind.annotation.XmlRootElement;
 
 /**
  * A serialized representation of this class can be placed in the entity body of a response to the API. This particular entity holds a reference to a reporting task.
  */
 @XmlRootElement(name = "reportingTaskEntity")
-public class ReportingTaskEntity extends ComponentEntity implements Permissible<ReportingTaskDTO> {
+public class ReportingTaskEntity extends ComponentEntity implements Permissible<ReportingTaskDTO>, OperationPermissible {
 
     private ReportingTaskDTO component;
+    private PermissionsDTO operatePermissions;
+    private ReportingTaskStatusDTO status;
 
     /**
      * @return reporting task that is being serialized
@@ -41,4 +48,34 @@ public class ReportingTaskEntity extends ComponentEntity implements Permissible<
         this.component = component;
     }
 
+    /**
+     * @return The permissions for this component operations
+     */
+    @ApiModelProperty(
+            value = "The permissions for this component operations."
+    )
+    @Override
+    public PermissionsDTO getOperatePermissions() {
+        return operatePermissions;
+    }
+
+    @Override
+    public void setOperatePermissions(PermissionsDTO permissions) {
+        this.operatePermissions = permissions;
+    }
+
+    /**
+     * @return The status for this ReportingTask
+     */
+    @ApiModelProperty(
+            value = "The status for this ReportingTask.",
+            readOnly = true
+    )
+    public ReportingTaskStatusDTO getStatus() {
+        return status;
+    }
+
+    public void setStatus(ReportingTaskStatusDTO status) {
+        this.status = status;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskRunStatusEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskRunStatusEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskRunStatusEntity.java
new file mode 100644
index 0000000..6d01e19
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/ReportingTaskRunStatusEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import javax.xml.bind.annotation.XmlType;
+
+/**
+ * Run status for a given ReportingTask.
+ */
+@XmlType(name = "reportingTaskRunStatus")
+public class ReportingTaskRunStatusEntity extends ComponentRunStatusEntity {
+
+    private static String[] SUPPORTED_STATE = {"RUNNING", "STOPPED"};
+
+    @Override
+    protected String[] getSupportedState() {
+        return SUPPORTED_STATE;
+    }
+
+    /**
+     * Run status for this ReportingTask.
+     * @return The run status
+     */
+    @ApiModelProperty(
+            value = "The run status of the ReportingTask.",
+            allowableValues = "RUNNING, STOPPED"
+    )
+    public String getState() {
+        return super.getState();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
index b89e7e6..fafdf26 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/pom.xml
@@ -47,5 +47,11 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.nifi</groupId>
+            <artifactId>nifi-mock-authorizer</artifactId>
+            <version>1.8.0-SNAPSHOT</version>
+        </dependency>
     </dependencies>
 </project>

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/OperationAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/OperationAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/OperationAuthorizable.java
new file mode 100644
index 0000000..ec0d875
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/OperationAuthorizable.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization.resource;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Authorizable for a component that can be scheduled by operators.
+ */
+public class OperationAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource {
+    private static Logger logger = LoggerFactory.getLogger(OperationAuthorizable.class);
+    private final Authorizable baseAuthorizable;
+
+    public OperationAuthorizable(final Authorizable baseAuthorizable) {
+        this.baseAuthorizable = baseAuthorizable;
+    }
+
+    @Override
+    public Authorizable getParentAuthorizable() {
+        // Need to return parent operation authorizable. E.g. /operation/processor/xxxx -> /operation/process-group/yyyy -> /operation/process-group/root
+        if (baseAuthorizable.getParentAuthorizable() == null) {
+            return null;
+        } else {
+            return new OperationAuthorizable(baseAuthorizable.getParentAuthorizable());
+        }
+    }
+
+    @Override
+    public Authorizable getBaseAuthorizable() {
+        return baseAuthorizable;
+    }
+
+    @Override
+    public Resource getResource() {
+        return ResourceFactory.getOperationResource(baseAuthorizable.getResource());
+    }
+
+    /**
+     * <p>Authorize the request operation action with the resource using base authorizable and operation authorizable combination.</p>
+     *
+     * <p>This method authorizes the request with the base authorizable first with WRITE action. If the request is allowed, then finish authorization.
+     * If the base authorizable denies the request, then it checks if the user has WRITE permission for '/operation/{componentType}/{id}'.</p>
+     */
+    public static void authorizeOperation(final Authorizable baseAuthorizable, final Authorizer authorizer, final NiFiUser user) {
+        try {
+            baseAuthorizable.authorize(authorizer, RequestAction.WRITE, user);
+        } catch (AccessDeniedException e) {
+            logger.debug("Authorization failed with {}. Try authorizing with OperationAuthorizable.", baseAuthorizable, e);
+            // Always use WRITE action for operation.
+            new OperationAuthorizable(baseAuthorizable).authorize(authorizer, RequestAction.WRITE, user);
+        }
+
+    }
+
+    /**
+     * Check if the request is authorized.
+     *
+     * @return True if the WRITE request is allowed by the base authorizable, or the user has WRITE permission for '/operation/{componentType}/id'.
+     */
+    public static boolean isOperationAuthorized(final Authorizable baseAuthorizable, final Authorizer authorizer, final NiFiUser user) {
+        return baseAuthorizable.isAuthorized(authorizer, RequestAction.WRITE, user)
+                || new OperationAuthorizable(baseAuthorizable).isAuthorized(authorizer, RequestAction.WRITE, user);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
index f2cec02..9d3b532 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
@@ -442,6 +442,33 @@ public final class ResourceFactory {
     }
 
     /**
+     * Gets a Resource for accessing component operations.
+     *
+     * @param resource      The resource being accessed
+     * @return              The resource
+     */
+    public static Resource getOperationResource(final Resource resource) {
+        Objects.requireNonNull(resource, "The resource type must be specified.");
+
+        return new Resource() {
+            @Override
+            public String getIdentifier() {
+                return ResourceType.Operation.getValue() + resource.getIdentifier();
+            }
+
+            @Override
+            public String getName() {
+                return "Operations for" + resource.getName();
+            }
+
+            @Override
+            public String getSafeDescription() {
+                return "Operations for" + resource.getSafeDescription();
+            }
+        };
+    }
+
+    /**
      * Gets a Resource for accessing a component configuration.
      *
      * @param resourceType  The type of resource being accessed

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
index d2d3126..a24b904 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
@@ -39,6 +39,7 @@ public enum ResourceType {
     DataTransfer("/data-transfer"),
     System("/system"),
     RestrictedComponents("/restricted-components"),
+    Operation("/operation"),
     Template("/templates"),
     Tenant("/tenants");
 
@@ -52,20 +53,20 @@ public enum ResourceType {
         return value;
     }
 
-    public static ResourceType valueOfValue(final String rawValue) {
-        ResourceType type = null;
+    /**
+     * Get ResourceType from a raw resource value.
+     * E.g. From "rovenance-data/processors/7ce897d6-0164-1000-fc87-caee3b08ba47", ProvenanceData will be returned.
+     * @param rawValue the raw resource string representation
+     * @return the type of the specified resource, or null if not found
+     */
+    public static ResourceType fromRawValue(final String rawValue) throws IllegalArgumentException {
 
         for (final ResourceType rt : values()) {
-            if (rt.getValue().equals(rawValue)) {
-                type = rt;
-                break;
+            if (rt.getValue().equals(rawValue) || rawValue.startsWith(rt.getValue() + "/")) {
+                return rt;
             }
         }
 
-        if (type == null) {
-            throw new IllegalArgumentException("Unknown resource type value " + rawValue);
-        }
-
-        return type;
+        return null;
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/OperationAuthorizableTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/OperationAuthorizableTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/OperationAuthorizableTest.java
new file mode 100644
index 0000000..87a4fc4
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/OperationAuthorizableTest.java
@@ -0,0 +1,226 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization.resource;
+
+import org.apache.nifi.authorization.AccessDeniedException;
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.MockPolicyBasedAuthorizer;
+import org.apache.nifi.authorization.Resource;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.junit.Test;
+
+import static org.apache.nifi.authorization.RequestAction.READ;
+import static org.apache.nifi.authorization.RequestAction.WRITE;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+public class OperationAuthorizableTest {
+
+    private static final User AUTH_USER = new User.Builder()
+            .identity("user-a")
+            .identifierGenerateRandom()
+            .build();
+    private static final StandardNiFiUser USER = new StandardNiFiUser.Builder()
+            .identity(AUTH_USER.getIdentity())
+            .build();
+    private final MockProcessGroup ROOT_PG = new MockProcessGroup("root", null);
+    private final MockProcessGroup PG1 = new MockProcessGroup("pg-1", ROOT_PG);
+    private final Authorizable PROCESSOR = new MockProcessor("component-1", PG1);
+
+    private class MockProcessGroup implements Authorizable {
+        private final String identifier;
+        private final MockProcessGroup parent;
+
+        private MockProcessGroup(String identifier, MockProcessGroup parent) {
+            this.identifier = identifier;
+            this.parent = parent;
+        }
+
+        public String getIdentifier() {
+            return identifier;
+        }
+
+        @Override
+        public Authorizable getParentAuthorizable() {
+            return parent;
+        }
+
+        @Override
+        public Resource getResource() {
+            return ResourceFactory.getComponentResource(ResourceType.ProcessGroup, identifier, identifier);
+        }
+    }
+
+    private class MockProcessor implements ComponentAuthorizable {
+        private final String identifier;
+        private final MockProcessGroup processGroup;
+
+        private MockProcessor(String identifier, MockProcessGroup processGroup) {
+            this.identifier = identifier;
+            this.processGroup = processGroup;
+        }
+
+        @Override
+        public String getIdentifier() {
+            return identifier;
+        }
+
+        @Override
+        public String getProcessGroupIdentifier() {
+            return processGroup.getIdentifier();
+        }
+
+        @Override
+        public Authorizable getParentAuthorizable() {
+            return processGroup;
+        }
+
+        @Override
+        public Resource getResource() {
+            return ResourceFactory.getComponentResource(ResourceType.Processor, identifier, identifier);
+        }
+    }
+
+    private void shouldBeDenied(String message, Runnable test) {
+        try {
+            test.run();
+            fail(message);
+        } catch (AccessDeniedException e) {
+            assertNotNull(e);
+        }
+    }
+
+    @Test()
+    public void testUnauthorizedRead() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+
+        // The user should not be able to access the component in any way.
+        shouldBeDenied("Component WRITE should be denied",
+                () -> PROCESSOR.authorize(authorizer, WRITE, USER));
+
+        shouldBeDenied("Component READ should be denied",
+                () -> PROCESSOR.authorize(authorizer, READ, USER));
+
+        shouldBeDenied("Operation should be denied",
+                () -> OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER));
+    }
+
+    @Test()
+    public void testAuthorizedByComponentRead() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+        authorizer.addUser(AUTH_USER);
+        authorizer.addAccessPolicy(new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .addUser(AUTH_USER.getIdentifier())
+                .resource("/processors/component-1")
+                .action(READ)
+                .build());
+
+        PROCESSOR.authorize(authorizer, READ, USER);
+
+        // If the user has only READ access to the base component WRITE and operation should be denied
+        shouldBeDenied("Component WRITE should be denied",
+                () -> PROCESSOR.authorize(authorizer, WRITE, USER));
+
+        shouldBeDenied("Operation WRITE should be denied",
+                () -> OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER));
+    }
+
+    @Test()
+    public void testAuthorizedByComponentWrite() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+        authorizer.addUser(AUTH_USER);
+        authorizer.addAccessPolicy(new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .addUser(AUTH_USER.getIdentifier())
+                .resource("/processors/component-1")
+                .action(WRITE)
+                .build());
+
+        // If the user has WRITE access to the base component, operation access should be allowed, too
+        PROCESSOR.authorize(authorizer, WRITE, USER);
+        OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER);
+
+        // But READ should be denied
+        shouldBeDenied("Component READ should be denied",
+                () -> PROCESSOR.authorize(authorizer, READ, USER));
+    }
+
+    @Test()
+    public void testAuthorizedByComponentParentWrite() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+        authorizer.addUser(AUTH_USER);
+        authorizer.addAccessPolicy(new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .addUser(AUTH_USER.getIdentifier())
+                .resource("/process-groups/root")
+                .action(WRITE)
+                .build());
+
+        // If the user has WRITE access to the base component, operation access should be allowed, too
+        PROCESSOR.authorize(authorizer, WRITE, USER);
+        OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER);
+
+        // But READ should be denied
+        shouldBeDenied("Component READ should be denied",
+                () -> PROCESSOR.authorize(authorizer, READ, USER));
+
+    }
+
+    @Test()
+    public void testAuthorizedByOperationWrite() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+        authorizer.addUser(AUTH_USER);
+        authorizer.addAccessPolicy(new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .addUser(AUTH_USER.getIdentifier())
+                .resource("/operation/processors/component-1")
+                .action(WRITE)
+                .build());
+
+        // Operation should be allowed, too.
+        OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER);
+
+        // If the user only has the operation permissions, then component access should be denied.
+        shouldBeDenied("Component READ should be denied",
+                () -> PROCESSOR.authorize(authorizer, READ, USER));
+        shouldBeDenied("Component WRITE should be denied",
+                () -> PROCESSOR.authorize(authorizer, WRITE, USER));
+    }
+
+    @Test()
+    public void testAuthorizedByOperationParentWrite() {
+        final MockPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
+        authorizer.addUser(AUTH_USER);
+        authorizer.addAccessPolicy(new AccessPolicy.Builder()
+                .identifierGenerateRandom()
+                .addUser(AUTH_USER.getIdentifier())
+                .resource("/operation/process-groups/root")
+                .action(WRITE)
+                .build());
+
+        // Operation should be allowed.
+        OperationAuthorizable.authorizeOperation(PROCESSOR, authorizer, USER);
+
+        // If the user only has the operation permissions, then component access should be denied.
+        shouldBeDenied("Component READ should be denied",
+                () -> PROCESSOR.authorize(authorizer, READ, USER));
+        shouldBeDenied("Component WRITE should be denied",
+                () -> PROCESSOR.authorize(authorizer, WRITE, USER));
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ComponentEntityMerger.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ComponentEntityMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ComponentEntityMerger.java
index f7c28fd..408e99f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ComponentEntityMerger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/manager/ComponentEntityMerger.java
@@ -19,6 +19,7 @@ package org.apache.nifi.cluster.manager;
 import org.apache.nifi.cluster.protocol.NodeIdentifier;
 import org.apache.nifi.web.api.entity.BulletinEntity;
 import org.apache.nifi.web.api.entity.ComponentEntity;
+import org.apache.nifi.web.api.entity.OperationPermissible;
 import org.apache.nifi.web.api.entity.Permissible;
 
 import java.util.ArrayList;
@@ -44,6 +45,11 @@ public interface ComponentEntityMerger<EntityType extends ComponentEntity & Perm
         for (final Map.Entry<NodeIdentifier, EntityType> entry : entityMap.entrySet()) {
             final EntityType entity = entry.getValue();
             PermissionsDtoMerger.mergePermissions(clientEntity.getPermissions(), entity.getPermissions());
+            if (clientEntity instanceof OperationPermissible && entity instanceof  OperationPermissible) {
+                PermissionsDtoMerger.mergePermissions(
+                        ((OperationPermissible) clientEntity).getOperatePermissions(),
+                        ((OperationPermissible) entity).getOperatePermissions());
+            }
         }
 
         if (clientEntity.getPermissions().getCanRead()) {


[2/4] nifi git commit: NIFI-375: Added operation policy

Posted by mc...@apache.org.
http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
index 80a78b0..aaa2027 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/FlowResource.java
@@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.bundle.Bundle;
@@ -378,7 +379,7 @@ public class FlowResource extends ApplicationResource {
     // -------------------
 
     /**
-     * Retrieves all the of controller services in this NiFi.
+     * Retrieves controller services for reporting tasks in this NiFi.
      *
      * @return A controllerServicesEntity.
      */
@@ -387,7 +388,7 @@ public class FlowResource extends ApplicationResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("controller/controller-services")
     @ApiOperation(
-            value = "Gets all controller services",
+            value = "Gets controller services for reporting tasks",
             response = ControllerServicesEntity.class,
             authorizations = {
                     @Authorization(value = "Read - /flow")
@@ -537,7 +538,7 @@ public class FlowResource extends ApplicationResource {
             response = ScheduleComponentsEntity.class,
             authorizations = {
                     @Authorization(value = "Read - /flow"),
-                    @Authorization(value = "Write - /{component-type}/{uuid} - For every component being scheduled/unscheduled")
+                    @Authorization(value = "Write - /{component-type}/{uuid} or /operation/{component-type}/{uuid} - For every component being scheduled/unscheduled")
             }
     )
     @ApiResponses(
@@ -626,7 +627,7 @@ public class FlowResource extends ApplicationResource {
                 // ensure authorized for each processor we will attempt to schedule
                 group.findAllProcessors().stream()
                         .filter(getProcessorFilter.get())
-                        .filter(processor -> processor.isAuthorized(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()))
+                        .filter(processor -> OperationAuthorizable.isOperationAuthorized(processor, authorizer, NiFiUserUtils.getNiFiUser()))
                         .forEach(processor -> {
                             componentIds.add(processor.getIdentifier());
                         });
@@ -634,7 +635,7 @@ public class FlowResource extends ApplicationResource {
                 // ensure authorized for each input port we will attempt to schedule
                 group.findAllInputPorts().stream()
                     .filter(getPortFilter.get())
-                        .filter(inputPort -> inputPort.isAuthorized(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()))
+                        .filter(inputPort -> OperationAuthorizable.isOperationAuthorized(inputPort, authorizer, NiFiUserUtils.getNiFiUser()))
                         .forEach(inputPort -> {
                             componentIds.add(inputPort.getIdentifier());
                         });
@@ -642,7 +643,7 @@ public class FlowResource extends ApplicationResource {
                 // ensure authorized for each output port we will attempt to schedule
                 group.findAllOutputPorts().stream()
                         .filter(getPortFilter.get())
-                        .filter(outputPort -> outputPort.isAuthorized(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()))
+                        .filter(outputPort -> OperationAuthorizable.isOperationAuthorized(outputPort, authorizer, NiFiUserUtils.getNiFiUser()))
                         .forEach(outputPort -> {
                             componentIds.add(outputPort.getIdentifier());
                         });
@@ -685,7 +686,7 @@ public class FlowResource extends ApplicationResource {
                     // ensure access to every component being scheduled
                     requestComponentsToSchedule.keySet().forEach(componentId -> {
                         final Authorizable connectable = lookup.getLocalConnectable(componentId);
-                        connectable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+                        OperationAuthorizable.authorizeOperation(connectable, authorizer, NiFiUserUtils.getNiFiUser());
                     });
                 },
                 () -> {
@@ -730,7 +731,7 @@ public class FlowResource extends ApplicationResource {
         response = ActivateControllerServicesEntity.class,
         authorizations = {
             @Authorization(value = "Read - /flow"),
-            @Authorization(value = "Write - /{component-type}/{uuid} - For every service being enabled/disabled")
+            @Authorization(value = "Write - /{component-type}/{uuid} or /operation/{component-type}/{uuid} - For every service being enabled/disabled")
         })
     @ApiResponses(
             value = {
@@ -787,7 +788,7 @@ public class FlowResource extends ApplicationResource {
 
                 group.findAllControllerServices().stream()
                     .filter(filter)
-                    .filter(service -> service.isAuthorized(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()))
+                    .filter(service -> OperationAuthorizable.isOperationAuthorized(service, authorizer, NiFiUserUtils.getNiFiUser()))
                     .forEach(service -> componentIds.add(service.getIdentifier()));
                 return componentIds;
             });
@@ -827,7 +828,7 @@ public class FlowResource extends ApplicationResource {
                     // ensure access to every component being scheduled
                     requestComponentsToSchedule.keySet().forEach(componentId -> {
                         final Authorizable authorizable = lookup.getControllerService(componentId).getAuthorizable();
-                        authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+                        OperationAuthorizable.authorizeOperation(authorizable, authorizer, NiFiUserUtils.getNiFiUser());
                     });
                 },
             () -> serviceFacade.verifyActivateControllerServices(id, desiredState, requestComponentRevisions.keySet()),

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
index 133da91..d63080a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/InputPortResource.java
@@ -26,12 +26,16 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.web.NiFiServiceFacade;
 import org.apache.nifi.web.Revision;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.apache.nifi.web.api.dto.PositionDTO;
 import org.apache.nifi.web.api.entity.PortEntity;
+import org.apache.nifi.web.api.entity.PortRunStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.LongParameter;
 
@@ -315,6 +319,92 @@ public class InputPortResource extends ApplicationResource {
         );
     }
 
+    /**
+     * Updates the operational status for the specified input port with the specified values.
+     *
+     * @param httpServletRequest request
+     * @param id                 The id of the port to update.
+     * @param requestRunStatus    A portRunStatusEntity.
+     * @return A portEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of an input-port",
+            response = ProcessorEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /input-ports/{uuid} or /operation/input-ports/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRunStatus(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The port id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The port run status.",
+                    required = true
+            ) final PortRunStatusEntity requestRunStatus) {
+
+        if (requestRunStatus == null) {
+            throw new IllegalArgumentException("Port run status must be specified.");
+        }
+
+        if (requestRunStatus.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRunStatus.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRunStatus);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRunStatus,
+                requestRevision,
+                lookup -> {
+                    final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+                    final Authorizable authorizable = lookup.getInputPort(id);
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, user);
+                },
+                () -> serviceFacade.verifyUpdateInputPort(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+                (revision, runStatusEntity) -> {
+                    // update the input port
+                    final PortEntity entity = serviceFacade.updateInputPort(revision, createDTOWithDesiredRunStatus(id, runStatusEntity.getState()));
+                    populateRemainingInputPortEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private PortDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+        final PortDTO dto = new PortDTO();
+        dto.setId(id);
+        dto.setState(runStatus);
+        return dto;
+    }
+
     // setters
 
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
index d3ff063..955d9db 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/OutputPortResource.java
@@ -26,12 +26,16 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.web.NiFiServiceFacade;
 import org.apache.nifi.web.Revision;
 import org.apache.nifi.web.api.dto.PortDTO;
 import org.apache.nifi.web.api.dto.PositionDTO;
 import org.apache.nifi.web.api.entity.PortEntity;
+import org.apache.nifi.web.api.entity.PortRunStatusEntity;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.LongParameter;
 
@@ -315,6 +319,93 @@ public class OutputPortResource extends ApplicationResource {
         );
     }
 
+
+    /**
+     * Updates the operational status for the specified input port with the specified values.
+     *
+     * @param httpServletRequest request
+     * @param id                 The id of the port to update.
+     * @param requestRunStatus    A portRunStatusEntity.
+     * @return A portEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of an output-port",
+            response = ProcessorEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /output-ports/{uuid} or /operation/output-ports/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRunStatus(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The port id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The port run status.",
+                    required = true
+            ) final PortRunStatusEntity requestRunStatus) {
+
+        if (requestRunStatus == null) {
+            throw new IllegalArgumentException("Port run status must be specified.");
+        }
+
+        if (requestRunStatus.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRunStatus.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRunStatus);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRunStatus,
+                requestRevision,
+                lookup -> {
+                    final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+                    final Authorizable authorizable = lookup.getOutputPort(id);
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, user);
+                },
+                () -> serviceFacade.verifyUpdateOutputPort(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+                (revision, runStatusEntity) -> {
+                    // update the input port
+                    final PortEntity entity = serviceFacade.updateOutputPort(revision, createDTOWithDesiredRunStatus(id, runStatusEntity.getState()));
+                    populateRemainingOutputPortEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private PortDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+        final PortDTO dto = new PortDTO();
+        dto.setId(id);
+        dto.setState(runStatus);
+        return dto;
+    }
+
     // setters
 
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
index 5229f69..0d29d60 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
@@ -28,6 +28,7 @@ import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.ComponentAuthorizable;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.ui.extension.UiExtension;
@@ -44,6 +45,7 @@ import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
 import org.apache.nifi.web.api.entity.ComponentStateEntity;
 import org.apache.nifi.web.api.entity.ProcessorDiagnosticsEntity;
 import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.api.entity.ProcessorRunStatusEntity;
 import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.LongParameter;
@@ -214,7 +216,7 @@ public class ProcessorResource extends ApplicationResource {
     @Produces(MediaType.APPLICATION_JSON)
     @Path("/{id}/threads")
     @ApiOperation(value = "Terminates a processor, essentially \"deleting\" its threads and any active tasks", response = ProcessorEntity.class, authorizations = {
-        @Authorization(value = "Write - /processors/{uuid}")
+        @Authorization(value = "Write - /processors/{uuid} or /operation/processors/{uuid}")
     })
     @ApiResponses(value = {
         @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
@@ -238,7 +240,7 @@ public class ProcessorResource extends ApplicationResource {
             requestProcessorEntity,
             lookup -> {
                 final Authorizable authorizable = lookup.getProcessor(id).getAuthorizable();
-                authorizable.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser());
+                OperationAuthorizable.authorizeOperation(authorizable, authorizer, NiFiUserUtils.getNiFiUser());
             },
             () -> serviceFacade.verifyTerminateProcessor(id),
             processorEntity -> {
@@ -668,6 +670,93 @@ public class ProcessorResource extends ApplicationResource {
         );
     }
 
+    /**
+     * Updates the operational status for the specified processor with the specified values.
+     *
+     * @param httpServletRequest request
+     * @param id                 The id of the processor to update.
+     * @param requestRunStatus    A processorEntity.
+     * @return A processorEntity.
+     * @throws InterruptedException if interrupted
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("/{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a processor",
+            response = ProcessorEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /processors/{uuid} or /operation/processors/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRunStatus(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The processor id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The processor run status.",
+                    required = true
+            ) final ProcessorRunStatusEntity requestRunStatus) {
+
+        if (requestRunStatus == null) {
+            throw new IllegalArgumentException("Processor run status must be specified.");
+        }
+
+        if (requestRunStatus.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRunStatus.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRunStatus);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRunStatus,
+                requestRevision,
+                lookup -> {
+                    final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+                    final Authorizable authorizable = lookup.getProcessor(id).getAuthorizable();
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, user);
+                },
+                () -> serviceFacade.verifyUpdateProcessor(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+                (revision, runStatusEntity) -> {
+                    // update the processor
+                    final ProcessorEntity entity = serviceFacade.updateProcessor(revision, createDTOWithDesiredRunStatus(id, runStatusEntity.getState()));
+                    populateRemainingProcessorEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private ProcessorDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+        final ProcessorDTO dto = new ProcessorDTO();
+        dto.setId(id);
+        dto.setState(runStatus);
+        return dto;
+    }
+
     // setters
 
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
index 79c5aaf..f034082 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/RemoteProcessGroupResource.java
@@ -26,6 +26,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.web.NiFiServiceFacade;
 import org.apache.nifi.web.Revision;
@@ -33,6 +34,7 @@ import org.apache.nifi.web.api.dto.PositionDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
 import org.apache.nifi.web.api.dto.RemoteProcessGroupPortDTO;
 import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.entity.RemotePortRunStatusEntity;
 import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
 import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
@@ -435,6 +437,197 @@ public class RemoteProcessGroupResource extends ApplicationResource {
     }
 
     /**
+     * Updates the specified remote process group input port run status.
+     *
+     * @param httpServletRequest           request
+     * @param id                           The id of the remote process group to update.
+     * @param portId                       The id of the input port to update.
+     * @param requestRemotePortRunStatusEntity The remoteProcessGroupPortRunStatusEntity
+     * @return A remoteProcessGroupPortEntity
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/input-ports/{port-id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a remote port",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = RemoteProcessGroupPortEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /remote-process-groups/{uuid} or /operation/remote-process-groups/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRemoteProcessGroupInputPortRunStatus(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The remote process group id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The remote process group port id.",
+                    required = true
+            )
+            @PathParam("port-id") final String portId,
+            @ApiParam(
+                    value = "The remote process group port.",
+                    required = true
+            ) final RemotePortRunStatusEntity requestRemotePortRunStatusEntity) {
+
+        if (requestRemotePortRunStatusEntity == null) {
+            throw new IllegalArgumentException("Remote process group port run status must be specified.");
+        }
+
+        if (requestRemotePortRunStatusEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRemotePortRunStatusEntity.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRemotePortRunStatusEntity);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRemotePortRunStatusEntity.isDisconnectedNodeAcknowledged());
+        }
+
+        final Revision requestRevision = getRevision(requestRemotePortRunStatusEntity.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRemotePortRunStatusEntity,
+                requestRevision,
+                lookup -> {
+                    final Authorizable remoteProcessGroup = lookup.getRemoteProcessGroup(id);
+                    OperationAuthorizable.authorizeOperation(remoteProcessGroup, authorizer, NiFiUserUtils.getNiFiUser());
+                },
+                () -> serviceFacade.verifyUpdateRemoteProcessGroupInputPort(id, createPortDTOWithDesiredRunStatus(portId, id, requestRemotePortRunStatusEntity)),
+                (revision, remotePortRunStatusEntity) -> {
+                    // update the specified remote process group
+                    final RemoteProcessGroupPortEntity controllerResponse = serviceFacade.updateRemoteProcessGroupInputPort(revision, id,
+                            createPortDTOWithDesiredRunStatus(portId, id, remotePortRunStatusEntity));
+
+                    // get the updated revision
+                    final RevisionDTO updatedRevision = controllerResponse.getRevision();
+
+                    // build the response entity
+                    final RemoteProcessGroupPortEntity entity = new RemoteProcessGroupPortEntity();
+                    entity.setRevision(updatedRevision);
+                    entity.setRemoteProcessGroupPort(controllerResponse.getRemoteProcessGroupPort());
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private RemoteProcessGroupPortDTO createPortDTOWithDesiredRunStatus(final String portId, final String groupId, final RemotePortRunStatusEntity entity) {
+        final RemoteProcessGroupPortDTO dto = new RemoteProcessGroupPortDTO();
+        dto.setId(portId);
+        dto.setGroupId(groupId);
+        dto.setTransmitting(shouldTransmit(entity));
+        return dto;
+    }
+
+    /**
+     * Updates the specified remote process group output port run status.
+     *
+     * @param httpServletRequest           request
+     * @param id                           The id of the remote process group to update.
+     * @param portId                       The id of the output port to update.
+     * @param requestRemotePortRunStatusEntity The remoteProcessGroupPortEntity
+     * @return A remoteProcessGroupPortEntity
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/output-ports/{port-id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a remote port",
+            notes = NON_GUARANTEED_ENDPOINT,
+            response = RemoteProcessGroupPortEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /remote-process-groups/{uuid} or /operation/remote-process-groups/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRemoteProcessGroupOutputPortRunStatus(
+            @Context HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The remote process group id.",
+                    required = true
+            )
+            @PathParam("id") String id,
+            @ApiParam(
+                    value = "The remote process group port id.",
+                    required = true
+            )
+            @PathParam("port-id") String portId,
+            @ApiParam(
+                    value = "The remote process group port.",
+                    required = true
+            ) RemotePortRunStatusEntity requestRemotePortRunStatusEntity) {
+
+        if (requestRemotePortRunStatusEntity == null) {
+            throw new IllegalArgumentException("Remote process group port run status must be specified.");
+        }
+
+        if (requestRemotePortRunStatusEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRemotePortRunStatusEntity.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRemotePortRunStatusEntity);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRemotePortRunStatusEntity.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRemotePortRunStatusEntity.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRemotePortRunStatusEntity,
+                requestRevision,
+                lookup -> {
+                    final Authorizable remoteProcessGroup = lookup.getRemoteProcessGroup(id);
+                    OperationAuthorizable.authorizeOperation(remoteProcessGroup, authorizer, NiFiUserUtils.getNiFiUser());
+                },
+                () -> serviceFacade.verifyUpdateRemoteProcessGroupOutputPort(id, createPortDTOWithDesiredRunStatus(portId, id, requestRemotePortRunStatusEntity)),
+                (revision, remotePortRunStatusEntity) -> {
+                    // update the specified remote process group
+                    final RemoteProcessGroupPortEntity controllerResponse = serviceFacade.updateRemoteProcessGroupOutputPort(revision, id,
+                            createPortDTOWithDesiredRunStatus(portId, id, remotePortRunStatusEntity));
+
+                    // get the updated revision
+                    final RevisionDTO updatedRevision = controllerResponse.getRevision();
+
+                    // build the response entity
+                    RemoteProcessGroupPortEntity entity = new RemoteProcessGroupPortEntity();
+                    entity.setRevision(updatedRevision);
+                    entity.setRemoteProcessGroupPort(controllerResponse.getRemoteProcessGroupPort());
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    /**
      * Updates the specified remote process group.
      *
      * @param httpServletRequest       request
@@ -557,6 +750,95 @@ public class RemoteProcessGroupResource extends ApplicationResource {
         );
     }
 
+    /**
+     * Updates the operational status for the specified remote process group with the specified value.
+     *
+     * @param httpServletRequest       request
+     * @param id                       The id of the remote process group to update.
+     * @param requestRemotePortRunStatusEntity A remotePortRunStatusEntity.
+     * @return A remoteProcessGroupEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a remote process group",
+            response = RemoteProcessGroupEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /remote-process-groups/{uuid} or /operation/remote-process-groups/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRemoteProcessGroupRunStatus(
+            @Context HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The remote process group id.",
+                    required = true
+            )
+            @PathParam("id") String id,
+            @ApiParam(
+                    value = "The remote process group run status.",
+                    required = true
+            ) final RemotePortRunStatusEntity requestRemotePortRunStatusEntity) {
+
+        if (requestRemotePortRunStatusEntity == null) {
+            throw new IllegalArgumentException("Remote process group run status must be specified.");
+        }
+
+        if (requestRemotePortRunStatusEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRemotePortRunStatusEntity.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRemotePortRunStatusEntity);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRemotePortRunStatusEntity.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRemotePortRunStatusEntity.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRemotePortRunStatusEntity,
+                requestRevision,
+                lookup -> {
+                    Authorizable authorizable = lookup.getRemoteProcessGroup(id);
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, NiFiUserUtils.getNiFiUser());
+                },
+                () -> serviceFacade.verifyUpdateRemoteProcessGroup(createDTOWithDesiredRunStatus(id, requestRemotePortRunStatusEntity)),
+                (revision, remotePortRunStatusEntity) -> {
+                    // update the specified remote process group
+                    final RemoteProcessGroupEntity entity = serviceFacade.updateRemoteProcessGroup(revision, createDTOWithDesiredRunStatus(id, remotePortRunStatusEntity));
+                    populateRemainingRemoteProcessGroupEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private RemoteProcessGroupDTO createDTOWithDesiredRunStatus(final String id, final RemotePortRunStatusEntity entity) {
+        final RemoteProcessGroupDTO dto = new RemoteProcessGroupDTO();
+        dto.setId(id);
+        dto.setTransmitting(shouldTransmit(entity));
+        return dto;
+    }
+
+
+    private boolean shouldTransmit(RemotePortRunStatusEntity requestRemotePortRunStatusEntity) {
+        return "TRANSMITTING".equals(requestRemotePortRunStatusEntity.getState());
+    }
+
     // setters
 
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
index 278c372..2c516c4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
@@ -28,6 +28,7 @@ import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.ComponentAuthorizable;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.ui.extension.UiExtension;
 import org.apache.nifi.ui.extension.UiExtensionMapping;
@@ -41,6 +42,7 @@ import org.apache.nifi.web.api.dto.ReportingTaskDTO;
 import org.apache.nifi.web.api.entity.ComponentStateEntity;
 import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
 import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskRunStatusEntity;
 import org.apache.nifi.web.api.request.ClientIdParameter;
 import org.apache.nifi.web.api.request.LongParameter;
 
@@ -113,6 +115,9 @@ public class ReportingTaskResource extends ApplicationResource {
      */
     public ReportingTaskDTO populateRemainingReportingTaskContent(final ReportingTaskDTO reportingTask) {
         final BundleDTO bundle = reportingTask.getBundle();
+        if (bundle == null) {
+            return reportingTask;
+        }
 
         // see if this processor has any ui extensions
         final UiExtensionMapping uiExtensionMapping = (UiExtensionMapping) servletContext.getAttribute("nifi-ui-extensions");
@@ -542,6 +547,91 @@ public class ReportingTaskResource extends ApplicationResource {
         );
     }
 
+    /**
+     * Updates the operational status for the specified ReportingTask with the specified values.
+     *
+     * @param httpServletRequest  request
+     * @param id                  The id of the reporting task to update.
+     * @param requestRunStatus A runStatusEntity.
+     * @return A reportingTaskEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("{id}/run-status")
+    @ApiOperation(
+            value = "Updates run status of a reporting task",
+            response = ReportingTaskEntity.class,
+            authorizations = {
+                    @Authorization(value = "Write - /reporting-tasks/{uuid} or  or /operation/reporting-tasks/{uuid}")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+            }
+    )
+    public Response updateRunStatus(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The reporting task id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The reporting task run status.",
+                    required = true
+            ) final ReportingTaskRunStatusEntity requestRunStatus) {
+
+        if (requestRunStatus == null) {
+            throw new IllegalArgumentException("Reporting task run status must be specified.");
+        }
+
+        if (requestRunStatus.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        requestRunStatus.validateState();
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, requestRunStatus);
+        } else if (isDisconnectedFromCluster()) {
+            verifyDisconnectedNodeModification(requestRunStatus.isDisconnectedNodeAcknowledged());
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision requestRevision = getRevision(requestRunStatus.getRevision(), id);
+        return withWriteLock(
+                serviceFacade,
+                requestRunStatus,
+                requestRevision,
+                lookup -> {
+                    // authorize reporting task
+                    final Authorizable authorizable = lookup.getReportingTask(id).getAuthorizable();
+                    OperationAuthorizable.authorizeOperation(authorizable, authorizer, NiFiUserUtils.getNiFiUser());
+                },
+                () -> serviceFacade.verifyUpdateReportingTask(createDTOWithDesiredRunStatus(id, requestRunStatus.getState())),
+                (revision, reportingTaskRunStatusEntity) -> {
+                    // update the reporting task
+                    final ReportingTaskEntity entity = serviceFacade.updateReportingTask(revision, createDTOWithDesiredRunStatus(id, reportingTaskRunStatusEntity.getState()));
+                    populateRemainingReportingTaskEntityContent(entity);
+
+                    return generateOkResponse(entity).build();
+                }
+        );
+    }
+
+    private ReportingTaskDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
+        final ReportingTaskDTO dto = new ReportingTaskDTO();
+        dto.setId(id);
+        dto.setState(runStatus);
+        return dto;
+    }
+
     // setters
 
     public void setServiceFacade(NiFiServiceFacade serviceFacade) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 4016d66..6c2c3bc 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -49,6 +49,7 @@ import org.apache.nifi.authorization.Resource;
 import org.apache.nifi.authorization.User;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ComponentAuthorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
 import org.apache.nifi.authorization.user.NiFiUser;
 import org.apache.nifi.authorization.user.NiFiUserUtils;
 import org.apache.nifi.bundle.Bundle;
@@ -945,7 +946,7 @@ public final class DtoFactory {
     }
 
 
-    public RemoteProcessGroupStatusDTO createRemoteProcessGroupStatusDto(final RemoteProcessGroupStatus remoteProcessGroupStatus) {
+    public RemoteProcessGroupStatusDTO createRemoteProcessGroupStatusDto(final RemoteProcessGroup remoteProcessGroup, final RemoteProcessGroupStatus remoteProcessGroupStatus) {
         final RemoteProcessGroupStatusDTO dto = new RemoteProcessGroupStatusDTO();
         dto.setId(remoteProcessGroupStatus.getId());
         dto.setGroupId(remoteProcessGroupStatus.getGroupId());
@@ -953,6 +954,7 @@ public final class DtoFactory {
         dto.setName(remoteProcessGroupStatus.getName());
         dto.setTransmissionStatus(remoteProcessGroupStatus.getTransmissionStatus().toString());
         dto.setStatsLastRefreshed(new Date());
+        dto.setValidationStatus(getRemoteProcessGroupValidationStatus(remoteProcessGroup).name());
 
         final RemoteProcessGroupStatusSnapshotDTO snapshot = new RemoteProcessGroupStatusSnapshotDTO();
         dto.setAggregateSnapshot(snapshot);
@@ -973,6 +975,13 @@ public final class DtoFactory {
         return dto;
     }
 
+    private ValidationStatus getRemoteProcessGroupValidationStatus(RemoteProcessGroup remoteProcessGroup) {
+        final boolean hasAuthIssue = remoteProcessGroup.getAuthorizationIssue() != null && !remoteProcessGroup.getAuthorizationIssue().isEmpty();
+        final Collection<ValidationResult> validationResults = remoteProcessGroup.validate();
+        final boolean hasValidationIssue = validationResults != null && !validationResults.isEmpty();
+        return hasAuthIssue || hasValidationIssue ? ValidationStatus.INVALID : ValidationStatus.VALID;
+    }
+
     public ProcessGroupStatusDTO createConciseProcessGroupStatusDto(final ProcessGroupStatus processGroupStatus) {
         final ProcessGroupStatusDTO processGroupStatusDto = new ProcessGroupStatusDTO();
         processGroupStatusDto.setId(processGroupStatus.getId());
@@ -1060,8 +1069,8 @@ public final class DtoFactory {
         final Collection<RemoteProcessGroupStatus> childRemoteProcessGroupStatusCollection = processGroupStatus.getRemoteProcessGroupStatus();
         if (childRemoteProcessGroupStatusCollection != null) {
             for (final RemoteProcessGroupStatus childRemoteProcessGroupStatus : childRemoteProcessGroupStatusCollection) {
-                final RemoteProcessGroupStatusDTO childRemoteProcessGroupStatusDto = createRemoteProcessGroupStatusDto(childRemoteProcessGroupStatus);
-                final RemoteProcessGroup remoteProcessGroup = processGroup.findRemoteProcessGroup(childRemoteProcessGroupStatusDto.getId());
+                final RemoteProcessGroup remoteProcessGroup = processGroup.findRemoteProcessGroup(childRemoteProcessGroupStatus.getId());
+                final RemoteProcessGroupStatusDTO childRemoteProcessGroupStatusDto = createRemoteProcessGroupStatusDto(remoteProcessGroup, childRemoteProcessGroupStatus);
                 final PermissionsDTO remoteProcessGroupPermissions = createPermissionsDto(remoteProcessGroup);
                 childRemoteProcessGroupStatusDtoCollection.add(entityFactory.createRemoteProcessGroupStatusSnapshotEntity(childRemoteProcessGroupStatusDto.getAggregateSnapshot(),
                         remoteProcessGroupPermissions));
@@ -2015,13 +2024,14 @@ public final class DtoFactory {
             final PortDTO dto = createPortDto(inputPort);
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(inputPort.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(inputPort);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(inputPort));
             final PortStatusDTO status = getComponentStatus(
                 () -> groupStatus.getInputPortStatus().stream().filter(inputPortStatus -> inputPort.getIdentifier().equals(inputPortStatus.getId())).findFirst().orElse(null),
                 inputPortStatus -> createPortStatusDto(inputPortStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPort.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            flow.getInputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, status, bulletinEntities));
+            flow.getInputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final PortDTO snippetOutputPort : snippet.getOutputPorts()) {
@@ -2031,13 +2041,14 @@ public final class DtoFactory {
             final PortDTO dto = createPortDto(outputPort);
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(outputPort.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(outputPort);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(outputPort));
             final PortStatusDTO status = getComponentStatus(
                 () -> groupStatus.getOutputPortStatus().stream().filter(outputPortStatus -> outputPort.getIdentifier().equals(outputPortStatus.getId())).findFirst().orElse(null),
                 outputPortStatus -> createPortStatusDto(outputPortStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPort.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            flow.getOutputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, status, bulletinEntities));
+            flow.getOutputPorts().add(entityFactory.createPortEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final LabelDTO snippetLabel : snippet.getLabels()) {
@@ -2072,13 +2083,14 @@ public final class DtoFactory {
             final ProcessorDTO dto = createProcessorDto(processor);
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(processor.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(processor);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(processor));
             final ProcessorStatusDTO status = getComponentStatus(
                 () -> groupStatus.getProcessorStatus().stream().filter(processorStatus -> processor.getIdentifier().equals(processorStatus.getId())).findFirst().orElse(null),
                 processorStatus -> createProcessorStatusDto(processorStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(processor.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            flow.getProcessors().add(entityFactory.createProcessorEntity(dto, revision, permissions, status, bulletinEntities));
+            flow.getProcessors().add(entityFactory.createProcessorEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final RemoteProcessGroupDTO snippetRemoteProcessGroup : snippet.getRemoteProcessGroups()) {
@@ -2088,13 +2100,14 @@ public final class DtoFactory {
             final RemoteProcessGroupDTO dto = createRemoteProcessGroupDto(remoteProcessGroup);
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(remoteProcessGroup.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(remoteProcessGroup);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(remoteProcessGroup));
             final RemoteProcessGroupStatusDTO status = getComponentStatus(
                 () -> groupStatus.getRemoteProcessGroupStatus().stream().filter(rpgStatus -> remoteProcessGroup.getIdentifier().equals(rpgStatus.getId())).findFirst().orElse(null),
-                remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(remoteProcessGroupStatus)
+                remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(remoteProcessGroup, remoteProcessGroupStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(remoteProcessGroup.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            flow.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(dto, revision, permissions, status, bulletinEntities));
+            flow.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(dto, revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         return flow;
@@ -2118,13 +2131,14 @@ public final class DtoFactory {
         for (final ProcessorNode procNode : group.getProcessors()) {
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(procNode.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(procNode);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(procNode));
             final ProcessorStatusDTO status = getComponentStatus(
                 () -> groupStatus.getProcessorStatus().stream().filter(processorStatus -> procNode.getIdentifier().equals(processorStatus.getId())).findFirst().orElse(null),
                 processorStatus -> createProcessorStatusDto(processorStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(procNode.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            dto.getProcessors().add(entityFactory.createProcessorEntity(createProcessorDto(procNode), revision, permissions, status, bulletinEntities));
+            dto.getProcessors().add(entityFactory.createProcessorEntity(createProcessorDto(procNode), revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final Connection connNode : group.getConnections()) {
@@ -2163,37 +2177,40 @@ public final class DtoFactory {
         for (final RemoteProcessGroup rpg : group.getRemoteProcessGroups()) {
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(rpg.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(rpg);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(rpg));
             final RemoteProcessGroupStatusDTO status = getComponentStatus(
                 () -> groupStatus.getRemoteProcessGroupStatus().stream().filter(remoteProcessGroupStatus -> rpg.getIdentifier().equals(remoteProcessGroupStatus.getId())).findFirst().orElse(null),
-                remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(remoteProcessGroupStatus)
+                remoteProcessGroupStatus -> createRemoteProcessGroupStatusDto(rpg, remoteProcessGroupStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(rpg.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            dto.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(createRemoteProcessGroupDto(rpg), revision, permissions, status, bulletinEntities));
+            dto.getRemoteProcessGroups().add(entityFactory.createRemoteProcessGroupEntity(createRemoteProcessGroupDto(rpg), revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final Port inputPort : group.getInputPorts()) {
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(inputPort.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(inputPort);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(inputPort));
             final PortStatusDTO status = getComponentStatus(
                 () -> groupStatus.getInputPortStatus().stream().filter(inputPortStatus -> inputPort.getIdentifier().equals(inputPortStatus.getId())).findFirst().orElse(null),
                 inputPortStatus -> createPortStatusDto(inputPortStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(inputPort.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            dto.getInputPorts().add(entityFactory.createPortEntity(createPortDto(inputPort), revision, permissions, status, bulletinEntities));
+            dto.getInputPorts().add(entityFactory.createPortEntity(createPortDto(inputPort), revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         for (final Port outputPort : group.getOutputPorts()) {
             final RevisionDTO revision = createRevisionDTO(revisionManager.getRevision(outputPort.getIdentifier()));
             final PermissionsDTO permissions = createPermissionsDto(outputPort);
+            final PermissionsDTO operatePermissions = createPermissionsDto(new OperationAuthorizable(outputPort));
             final PortStatusDTO status = getComponentStatus(
                 () -> groupStatus.getOutputPortStatus().stream().filter(outputPortStatus -> outputPort.getIdentifier().equals(outputPortStatus.getId())).findFirst().orElse(null),
                 outputPortStatus -> createPortStatusDto(outputPortStatus)
             );
             final List<BulletinDTO> bulletins = createBulletinDtos(bulletinRepository.findBulletinsForSource(outputPort.getIdentifier()));
             final List<BulletinEntity> bulletinEntities = bulletins.stream().map(bulletin -> entityFactory.createBulletinEntity(bulletin, permissions.getCanRead())).collect(Collectors.toList());
-            dto.getOutputPorts().add(entityFactory.createPortEntity(createPortDto(outputPort), revision, permissions, status, bulletinEntities));
+            dto.getOutputPorts().add(entityFactory.createPortEntity(createPortDto(outputPort), revision, permissions, operatePermissions, status, bulletinEntities));
         }
 
         return dto;

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
index 014616d..1bf0c0a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java
@@ -22,6 +22,7 @@ import org.apache.nifi.web.api.dto.flow.FlowBreadcrumbDTO;
 import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
 import org.apache.nifi.web.api.dto.status.ConnectionStatusDTO;
 import org.apache.nifi.web.api.dto.status.ConnectionStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ControllerServiceStatusDTO;
 import org.apache.nifi.web.api.dto.status.PortStatusDTO;
 import org.apache.nifi.web.api.dto.status.PortStatusSnapshotDTO;
 import org.apache.nifi.web.api.dto.status.ProcessGroupStatusDTO;
@@ -30,6 +31,7 @@ import org.apache.nifi.web.api.dto.status.ProcessorStatusDTO;
 import org.apache.nifi.web.api.dto.status.ProcessorStatusSnapshotDTO;
 import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusDTO;
 import org.apache.nifi.web.api.dto.status.RemoteProcessGroupStatusSnapshotDTO;
+import org.apache.nifi.web.api.dto.status.ReportingTaskStatusDTO;
 import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
 import org.apache.nifi.web.api.entity.AccessPolicyEntity;
 import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
@@ -197,13 +199,14 @@ public final class EntityFactory {
         return entity;
     }
 
-    public ProcessorEntity createProcessorEntity(final ProcessorDTO dto, final RevisionDTO revision, final PermissionsDTO permissions,
+    public ProcessorEntity createProcessorEntity(final ProcessorDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final PermissionsDTO operatePermissions,
         final ProcessorStatusDTO status, final List<BulletinEntity> bulletins) {
 
         final ProcessorEntity entity = new ProcessorEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setStatus(status);
             entity.setId(dto.getId());
             entity.setInputRequirement(dto.getInputRequirement());
@@ -216,11 +219,13 @@ public final class EntityFactory {
         return entity;
     }
 
-    public PortEntity createPortEntity(final PortDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final PortStatusDTO status, final List<BulletinEntity> bulletins) {
+    public PortEntity createPortEntity(final PortDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final PermissionsDTO operatePermissions,
+                                       final PortStatusDTO status, final List<BulletinEntity> bulletins) {
         final PortEntity entity = new PortEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setStatus(status);
             entity.setId(dto.getId());
             entity.setPosition(dto.getPosition());
@@ -429,12 +434,14 @@ public final class EntityFactory {
         return entity;
     }
 
-    public RemoteProcessGroupEntity createRemoteProcessGroupEntity(final RemoteProcessGroupDTO dto, final RevisionDTO revision, final PermissionsDTO permissions,
+    public RemoteProcessGroupEntity createRemoteProcessGroupEntity(final RemoteProcessGroupDTO dto, final RevisionDTO revision,
+                                                                   final PermissionsDTO permissions, final PermissionsDTO operatePermissions,
                                                                    final RemoteProcessGroupStatusDTO status, final List<BulletinEntity> bulletins) {
         final RemoteProcessGroupEntity entity = new RemoteProcessGroupEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setStatus(status);
             entity.setId(dto.getId());
             entity.setPosition(dto.getPosition());
@@ -448,11 +455,13 @@ public final class EntityFactory {
         return entity;
     }
 
-    public RemoteProcessGroupPortEntity createRemoteProcessGroupPortEntity(final RemoteProcessGroupPortDTO dto, final RevisionDTO revision, final PermissionsDTO permissions) {
+    public RemoteProcessGroupPortEntity createRemoteProcessGroupPortEntity(final RemoteProcessGroupPortDTO dto, final RevisionDTO revision,
+                                                                           final PermissionsDTO permissions, final PermissionsDTO operatePermissions) {
         final RemoteProcessGroupPortEntity entity = new RemoteProcessGroupPortEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setId(dto.getId());
             if (permissions != null && permissions.getCanRead()) {
                 entity.setRemoteProcessGroupPort(dto);
@@ -468,12 +477,21 @@ public final class EntityFactory {
         return entity;
     }
 
-    public ReportingTaskEntity createReportingTaskEntity(final ReportingTaskDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final List<BulletinEntity> bulletins) {
+    public ReportingTaskEntity createReportingTaskEntity(final ReportingTaskDTO dto, final RevisionDTO revision,
+                                                         final PermissionsDTO permissions, final PermissionsDTO operatePermissions, final List<BulletinEntity> bulletins) {
         final ReportingTaskEntity entity = new ReportingTaskEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setId(dto.getId());
+
+            final ReportingTaskStatusDTO status = new ReportingTaskStatusDTO();
+            status.setRunStatus(dto.getState());
+            status.setValidationStatus(dto.getValidationStatus());
+            status.setActiveThreadCount(dto.getActiveThreadCount());
+            entity.setStatus(status);
+
             if (permissions != null && permissions.getCanRead()) {
                 entity.setComponent(dto);
                 entity.setBulletins(bulletins);
@@ -495,13 +513,22 @@ public final class EntityFactory {
         return entity;
     }
 
-    public ControllerServiceEntity createControllerServiceEntity(final ControllerServiceDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final List<BulletinEntity> bulletins) {
+    public ControllerServiceEntity createControllerServiceEntity(final ControllerServiceDTO dto, final RevisionDTO revision,
+                                                                 final PermissionsDTO permissions, final PermissionsDTO operatePermissions, final List<BulletinEntity> bulletins) {
         final ControllerServiceEntity entity = new ControllerServiceEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setId(dto.getId());
+            entity.setParentGroupId(dto.getParentGroupId());
             entity.setPosition(dto.getPosition());
+
+            final ControllerServiceStatusDTO status = new ControllerServiceStatusDTO();
+            status.setRunStatus(dto.getState());
+            status.setValidationStatus(dto.getValidationStatus());
+            entity.setStatus(status);
+
             if (permissions != null && permissions.getCanRead()) {
                 entity.setComponent(dto);
                 entity.setBulletins(bulletins);
@@ -510,12 +537,13 @@ public final class EntityFactory {
         return entity;
     }
 
-    public ControllerServiceReferencingComponentEntity createControllerServiceReferencingComponentEntity(
-        final ControllerServiceReferencingComponentDTO dto, final RevisionDTO revision, final PermissionsDTO permissions) {
+    public ControllerServiceReferencingComponentEntity createControllerServiceReferencingComponentEntity(final String id,
+        final ControllerServiceReferencingComponentDTO dto, final RevisionDTO revision, final PermissionsDTO permissions, final PermissionsDTO operatePermissions) {
         final ControllerServiceReferencingComponentEntity entity = new ControllerServiceReferencingComponentEntity();
         entity.setRevision(revision);
         if (dto != null) {
             entity.setPermissions(permissions);
+            entity.setOperatePermissions(operatePermissions);
             entity.setId(dto.getId());
             if (permissions != null && permissions.getCanRead()) {
                 entity.setComponent(dto);

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
index 1d08081..18a47d2 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java
@@ -859,6 +859,7 @@ public class ControllerFacade implements Authorizable {
             resources.add(ResourceFactory.getDataResource(processorResource));
             resources.add(ResourceFactory.getProvenanceDataResource(processorResource));
             resources.add(ResourceFactory.getPolicyResource(processorResource));
+            resources.add(ResourceFactory.getOperationResource(processorResource));
         }
 
         // add each label
@@ -875,6 +876,7 @@ public class ControllerFacade implements Authorizable {
             resources.add(ResourceFactory.getDataResource(processGroupResource));
             resources.add(ResourceFactory.getProvenanceDataResource(processGroupResource));
             resources.add(ResourceFactory.getPolicyResource(processGroupResource));
+            resources.add(ResourceFactory.getOperationResource(processGroupResource));
         }
 
         // add each remote process group
@@ -884,6 +886,7 @@ public class ControllerFacade implements Authorizable {
             resources.add(ResourceFactory.getDataResource(remoteProcessGroupResource));
             resources.add(ResourceFactory.getProvenanceDataResource(remoteProcessGroupResource));
             resources.add(ResourceFactory.getPolicyResource(remoteProcessGroupResource));
+            resources.add(ResourceFactory.getOperationResource(remoteProcessGroupResource));
         }
 
         // add each input port
@@ -893,6 +896,7 @@ public class ControllerFacade implements Authorizable {
             resources.add(ResourceFactory.getDataResource(inputPortResource));
             resources.add(ResourceFactory.getProvenanceDataResource(inputPortResource));
             resources.add(ResourceFactory.getPolicyResource(inputPortResource));
+            resources.add(ResourceFactory.getOperationResource(inputPortResource));
             if (inputPort instanceof RootGroupPort) {
                 resources.add(ResourceFactory.getDataTransferResource(inputPortResource));
             }
@@ -905,6 +909,7 @@ public class ControllerFacade implements Authorizable {
             resources.add(ResourceFactory.getDataResource(outputPortResource));
             resources.add(ResourceFactory.getProvenanceDataResource(outputPortResource));
             resources.add(ResourceFactory.getPolicyResource(outputPortResource));
+            resources.add(ResourceFactory.getOperationResource(outputPortResource));
             if (outputPort instanceof RootGroupPort) {
                 resources.add(ResourceFactory.getDataTransferResource(outputPortResource));
             }
@@ -915,6 +920,7 @@ public class ControllerFacade implements Authorizable {
             final Resource controllerServiceResource = controllerService.getResource();
             resources.add(controllerServiceResource);
             resources.add(ResourceFactory.getPolicyResource(controllerServiceResource));
+            resources.add(ResourceFactory.getOperationResource(controllerServiceResource));
         };
 
         flowController.getAllControllerServices().forEach(csConsumer);
@@ -926,6 +932,7 @@ public class ControllerFacade implements Authorizable {
             final Resource reportingTaskResource = reportingTask.getResource();
             resources.add(reportingTaskResource);
             resources.add(ResourceFactory.getPolicyResource(reportingTaskResource));
+            resources.add(ResourceFactory.getOperationResource(reportingTaskResource));
         }
 
         // add each template

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/StandardAuthorizableLookupTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/StandardAuthorizableLookupTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/StandardAuthorizableLookupTest.java
new file mode 100644
index 0000000..5935aa2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/authorization/StandardAuthorizableLookupTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.authorization.resource.AccessPolicyAuthorizable;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.authorization.resource.DataAuthorizable;
+import org.apache.nifi.authorization.resource.DataTransferAuthorizable;
+import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.resource.ProvenanceDataAuthorizable;
+import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.web.dao.ProcessorDAO;
+import org.junit.Test;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class StandardAuthorizableLookupTest {
+
+    @Test
+    public void testGetAuthorizableFromResource() {
+        final ProcessorDAO processorDAO = mock(ProcessorDAO.class);
+        final ProcessorNode processorNode = mock(ProcessorNode.class);
+
+        when(processorDAO.getProcessor(eq("id"))).thenReturn(processorNode);
+
+        final StandardAuthorizableLookup lookup = new StandardAuthorizableLookup();
+        lookup.setProcessorDAO(processorDAO);
+
+        Authorizable authorizable = lookup.getAuthorizableFromResource("/processors/id");
+        assertTrue(authorizable instanceof ProcessorNode);
+
+        authorizable = lookup.getAuthorizableFromResource("/policies/processors/id");
+        assertTrue(authorizable instanceof AccessPolicyAuthorizable);
+        assertTrue(((AccessPolicyAuthorizable) authorizable).getBaseAuthorizable() instanceof ProcessorNode);
+
+        authorizable = lookup.getAuthorizableFromResource("/data/processors/id");
+        assertTrue(authorizable instanceof DataAuthorizable);
+        assertTrue(((DataAuthorizable) authorizable).getBaseAuthorizable() instanceof ProcessorNode);
+
+        authorizable = lookup.getAuthorizableFromResource("/data-transfer/processors/id");
+        assertTrue(authorizable instanceof DataTransferAuthorizable);
+        assertTrue(((DataTransferAuthorizable) authorizable).getBaseAuthorizable() instanceof ProcessorNode);
+
+        authorizable = lookup.getAuthorizableFromResource("/provenance-data/processors/id");
+        assertTrue(authorizable instanceof ProvenanceDataAuthorizable);
+        assertTrue(((ProvenanceDataAuthorizable) authorizable).getBaseAuthorizable() instanceof ProcessorNode);
+
+        authorizable = lookup.getAuthorizableFromResource("/operation/processors/id");
+        assertTrue(authorizable instanceof OperationAuthorizable);
+        assertTrue(((OperationAuthorizable) authorizable).getBaseAuthorizable() instanceof ProcessorNode);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/f570cb98/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
index 33d108f..bf32d5b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-actions.js
@@ -534,13 +534,10 @@
                                 'state': 'ENABLED'
                             }
                         } else {
-                            uri = d.uri;
+                            uri = d.uri + '/run-status';
                             entity = {
                                 'revision': nfClient.getRevision(d),
-                                'component': {
-                                    'id': d.id,
-                                    'state': 'STOPPED'
-                                }
+                                'state': 'STOPPED'
                             };
                         }
 
@@ -596,13 +593,10 @@
                                 'state': 'DISABLED'
                             }
                         } else {
-                            uri = d.uri;
+                            uri = d.uri + "/run-status";
                             entity = {
                                 'revision': nfClient.getRevision(d),
-                                'component': {
-                                     'id': d.id,
-                                    'state': 'DISABLED'
-                                }
+                                'state': 'DISABLED'
                             };
                         }
 
@@ -677,13 +671,10 @@
                                 'state': 'RUNNING'
                             }
                         } else {
-                            uri = d.uri;
+                            uri = d.uri + '/run-status';
                             entity = {
                                 'revision': nfClient.getRevision(d),
-                                'component': {
-                                    'id': d.id,
-                                    'state': 'RUNNING'
-                                }
+                                'state': 'RUNNING'
                             };
                         }
 
@@ -742,13 +733,10 @@
                                 'state': 'STOPPED'
                             };
                         } else {
-                            uri = d.uri;
+                            uri = d.uri + '/run-status';
                             entity = {
                                 'revision': nfClient.getRevision(d),
-                                'component': {
-                                    'id': d.id,
-                                    'state': 'STOPPED'
-                                }
+                                'state': 'STOPPED'
                             };
                         }
 
@@ -805,14 +793,11 @@
                 // build the entity
                 var entity = {
                     'revision': nfClient.getRevision(d),
-                    'component': {
-                        'id': d.id,
-                        'transmitting': true
-                    }
+                    'state': 'TRANSMITTING'
                 };
 
                 // start transmitting
-                updateResource(d.uri, entity).done(function (response) {
+                updateResource(d.uri + '/run-status', entity).done(function (response) {
                     nfRemoteProcessGroup.set(response);
                 });
             });
@@ -833,13 +818,10 @@
                 // build the entity
                 var entity = {
                     'revision': nfClient.getRevision(d),
-                    'component': {
-                        'id': d.id,
-                        'transmitting': false
-                    }
+                    'state': 'STOPPED'
                 };
 
-                updateResource(d.uri, entity).done(function (response) {
+                updateResource(d.uri + '/run-status', entity).done(function (response) {
                     nfRemoteProcessGroup.set(response);
                 });
             });