You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ma...@apache.org on 2016/07/12 21:00:05 UTC

[2/9] nifi git commit: NIFI-2095: - Adding a page for managing users and groups. - Adding a page for managing access policies. - Renaming accessPolicy in entity to permissions to avoid confusion with the accessPolicy model. - Adding an Authorizable for a

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/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
new file mode 100644
index 0000000..652290a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-policy-management.js
@@ -0,0 +1,1027 @@
+/*
+ * 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.
+ */
+
+/* global nf, d3 */
+
+nf.PolicyManagement = (function () {
+    
+    var config = {
+        urls: {
+            api: '../nifi-api',
+            searchTenants: '../nifi-api/tenants/search-results'
+        }
+    };
+
+    var initialized = false;
+
+    var initAddTenantToPolicyDialog = function () {
+        $('#new-policy-user-button').on('click', function () {
+            $('#search-users-dialog').modal('show');
+        });
+
+        $('#delete-policy-button').on('click', function () {
+            promptToDeletePolicy();
+        });
+
+        $('#search-users-dialog').modal({
+            scrollableContentStyle: 'scrollable',
+            headerText: 'Search users',
+            buttons: [{
+                buttonText: 'Ok',
+                color: {
+                    base: '#728E9B',
+                    hover: '#004849',
+                    text: '#ffffff'
+                },
+                handler: {
+                    click: function () {
+                        var tenantSearchTerm = $('#search-users-field').val();
+
+                        // create the search request
+                        $.ajax({
+                            type: 'GET',
+                            data: {
+                                q: tenantSearchTerm
+                            },
+                            dataType: 'json',
+                            url: config.urls.searchTenants
+                        }).done(function (response) {
+                            var selectedUsers = $.map(response.users, function (user) {
+                                return $.extend({
+                                    type: 'user'
+                                }, user);
+                            });
+                            var selectedGroups = $.map(response.userGroups, function (userGroup) {
+                                return $.extend({
+                                    type: 'group'
+                                }, userGroup);
+                            });
+
+                            var selectedUser = [];
+                            selectedUser = selectedUser.concat(selectedUsers, selectedGroups);
+
+                            // ensure the search found some results
+                            if (!$.isArray(selectedUser) || selectedUser.length === 0) {
+                                nf.Dialog.showOkDialog({
+                                    headerText: 'User Search',
+                                    dialogContent: 'No users match \'' + nf.Common.escapeHtml(tenantSearchTerm) + '\'.'
+                                });
+                            } else if (selectedUser.length > 1) {
+                                nf.Dialog.showOkDialog({
+                                    headerText: 'User Search',
+                                    dialogContent: 'More than one user matches \'' + nf.Common.escapeHtml(tenantSearchTerm) + '\'.'
+                                });
+                            } else if (selectedUser.length === 1) {
+                                var user = selectedUser[0];
+
+                                // add to table and update policy
+                                var policyGrid = $('#policy-table').data('gridInstance');
+                                var policyData = policyGrid.getData();
+
+                                // begin the update
+                                policyData.beginUpdate();
+
+                                // remove the user
+                                policyData.addItem(user);
+
+                                // end the update
+                                policyData.endUpdate();
+
+                                // update the policy
+                                updatePolicy();
+
+                                // close the dialog
+                                $('#search-users-dialog').modal('hide');
+                            }
+                        });
+                    }
+                }
+            },
+                {
+                    buttonText: 'Cancel',
+                    color: {
+                        base: '#E3E8EB',
+                        hover: '#C7D2D7',
+                        text: '#004849'
+                    },
+                    handler: {
+                        click: function () {
+                            // close the dialog
+                            $('#search-users-dialog').modal('hide');
+                        }
+                    }
+                }],
+            handler: {
+                close: function () {
+                    // reset the search fields
+                    $('#search-users-field').val('');
+                    $('#selected-user-id').text('');
+                }
+            }
+        });
+
+        // configure the user auto complete
+        $.widget('nf.userSearchAutocomplete', $.ui.autocomplete, {
+            _normalize: function (searchResults) {
+                var items = [];
+                items.push(searchResults);
+                return items;
+            },
+            _renderMenu: function (ul, items) {
+                // results are normalized into a single element array
+                var searchResults = items[0];
+
+                var self = this;
+                $.each(searchResults.userGroups, function (_, tenant) {
+                    self._renderGroup(ul, {
+                        label: tenant.component.identity,
+                        value: tenant.component.identity
+                    });
+                });
+                $.each(searchResults.users, function (_, tenant) {
+                    self._renderUser(ul, {
+                        label: tenant.component.identity,
+                        value: tenant.component.identity
+                    });
+                });
+
+                // ensure there were some results
+                if (ul.children().length === 0) {
+                    ul.append('<li class="unset search-no-matches">No users matched the search terms</li>');
+                }
+            },
+            _resizeMenu: function () {
+                var ul = this.menu.element;
+                ul.width($('#search-users-field').width() + 6);
+            },
+            _renderUser: function (ul, match) {
+                var userContent = $('<a></a>').text(match.label);
+                return $('<li></li>').data('ui-autocomplete-item', match).append(userContent).appendTo(ul);
+            },
+            _renderGroup: function (ul, match) {
+                var groupLabel = $('<span></span>').text(match.label);
+                var groupContent = $('<a></a>').append('<div class="fa fa-users" style="margin-right: 5px;"></div>').append(groupLabel);
+                return $('<li></li>').data('ui-autocomplete-item', match).append(groupContent).appendTo(ul);
+            }
+        });
+
+        // configure the autocomplete field
+        $('#search-users-field').userSearchAutocomplete({
+            minLength: 0,
+            appendTo: '#search-users-results',
+            position: {
+                my: 'left top',
+                at: 'left bottom',
+                offset: '0 1'
+            },
+            source: function (request, response) {
+                // create the search request
+                $.ajax({
+                    type: 'GET',
+                    data: {
+                        q: request.term
+                    },
+                    dataType: 'json',
+                    url: config.urls.searchTenants
+                }).done(function (searchResponse) {
+                    response(searchResponse);
+                });
+            }
+        });
+    };
+    
+    var initPolicyTable = function () {
+        // create/override a policy
+        $('#create-policy-link, #override-policy-link').on('click', function () {
+            createPolicy();
+        });
+
+        // policy type listing
+        $('#policy-type-list').combo({
+            options: [{
+                text: 'access the user interface',
+                value: 'flow'
+            }, {
+                text: 'access the controller',
+                value: 'controller'
+            }, {
+                text: 'query provenance',
+                value: 'provenance'
+            }, {
+                text: 'access all policies',
+                value: 'policies'
+            }, {
+                text: 'retrieve site-to-site details',
+                value: 'site-to-site'
+            }, {
+                text: 'view system diagnostics',
+                value: 'system'
+            }, {
+                text: 'proxy user requests',
+                value: 'proxy'
+            }, {
+                text: 'access counters',
+                value: 'counters'
+            }],
+            select: function (option) {
+                if (initialized) {
+                    // record the policy type
+                    $('#selected-policy-type').text(option.value);
+
+                    // if the option is for a specific component
+                    if (option.value === 'controller' || option.value === 'counters' || option.value === 'policies') {
+                        // update the policy target and let it relaod the policy
+                        $('#controller-policy-target').combo('setSelectedOption', {
+                            'value': 'read'
+                        }).show();
+                    } else {
+                        $('#controller-policy-target').hide();
+
+                        // record the action
+                        if (option.value === 'proxy') {
+                            $('#selected-policy-action').text('write');
+                        } else {
+                            $('#selected-policy-action').text('read');
+                        }
+
+                        // reload the policy
+                        loadPolicy();
+                    }
+                }
+            }
+        });
+
+        // controller policy target
+        $('#controller-policy-target').combo({
+            options: [{
+                text: 'view',
+                value: 'read'
+            }, {
+                text: 'modify',
+                value: 'write'
+            }],
+            select: function (option) {
+                if (initialized) {
+                    // record the policy action
+                    $('#selected-policy-action').text(option.value);
+
+                    // reload the policy
+                    loadPolicy();
+                }
+            }
+        });
+        
+        // component policy target
+        $('#component-policy-target').combo({
+            options: [{
+                text: 'view the component',
+                value: 'read-component'
+            }, {
+                text: 'modify the component',
+                value: 'write-component'
+            }, {
+                text: 'view the provenance events',
+                value: 'read-provenance-events'
+            }, {
+                text: 'view the policies',
+                value: 'read-policies'
+            }, {
+                text: 'modify the policies',
+                value: 'write-policies'
+            }, {
+                text: 'receive data via site-to-site',
+                value: 'write-receive-data',
+                disabled: true
+            },  {
+                text: 'send data via site-to-site',
+                value: 'write-send-data',
+                disabled: true
+            }],
+            select: function (option) {
+                if (initialized) {
+                    var resource = $('#selected-policy-component-type').text();
+
+                    if (option.value === 'read-component') {
+                        $('#selected-policy-action').text('read');
+                    } else if (option.value === 'write-component') {
+                        $('#selected-policy-action').text('write');
+                    } else if (option.value === 'read-provenance-events') {
+                        $('#selected-policy-action').text('read');
+                        resource = ('provenance-events/' + resource);
+                    } else if (option.value === 'read-policies') {
+                        $('#selected-policy-action').text('read');
+                        resource = ('policies/' + resource);
+                    } else if (option.value === 'write-policies') {
+                        $('#selected-policy-action').text('write');
+                        resource = ('policies/' + resource);
+                    } else if (option.value === 'write-receive-data') {
+                        $('#selected-policy-action').text('write');
+                        resource = 'data-transfer/input-ports';
+                    } else if (option.value === 'write-send-data') {
+                        $('#selected-policy-action').text('write');
+                        resource = 'data-transfer/output-ports';
+                    }
+
+                    // set the resource
+                    $('#selected-policy-type').text(resource);
+
+                    // reload the policy
+                    loadPolicy();
+                }
+            }
+        });
+
+        // function for formatting the user identity
+        var identityFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+            if (dataContext.type === 'group') {
+                markup += '<div class="fa fa-users" style="margin-right: 5px;"></div>';
+            }
+
+            markup += dataContext.component.identity;
+
+            return markup;
+        };
+
+        // function for formatting the actions column
+        var actionFormatter = function (row, cell, value, columnDef, dataContext) {
+            var markup = '';
+
+            markup += '<div title="Remove" class="pointer delete-user fa fa-trash"></div>';
+
+            return markup;
+        };
+
+        // initialize the templates table
+        var usersColumns = [
+            {id: 'identity', name: 'User', sortable: true, resizable: true, formatter: identityFormatter},
+            {id: 'actions', name: '&nbsp;', sortable: false, resizable: false, formatter: actionFormatter, width: 100, maxWidth: 100}
+        ];
+        var usersOptions = {
+            forceFitColumns: true,
+            enableTextSelectionOnCells: true,
+            enableCellNavigation: true,
+            enableColumnReorder: false,
+            autoEdit: false
+        };
+
+        // initialize the dataview
+        var policyData = new Slick.Data.DataView({
+            inlineFilters: false
+        });
+        policyData.setItems([]);
+
+        // initialize the sort
+        sort({
+            columnId: 'identity',
+            sortAsc: true
+        }, policyData);
+
+        // initialize the grid
+        var policyGrid = new Slick.Grid('#policy-table', policyData, usersColumns, usersOptions);
+        policyGrid.setSelectionModel(new Slick.RowSelectionModel());
+        policyGrid.registerPlugin(new Slick.AutoTooltips());
+        policyGrid.setSortColumn('identity', true);
+        policyGrid.onSort.subscribe(function (e, args) {
+            sort({
+                columnId: args.sortCol.id,
+                sortAsc: args.sortAsc
+            }, policyData);
+        });
+
+        // configure a click listener
+        policyGrid.onClick.subscribe(function (e, args) {
+            var target = $(e.target);
+
+            // get the node at this row
+            var item = policyData.getItem(args.row);
+
+            // determine the desired action
+            if (policyGrid.getColumns()[args.cell].id === 'actions') {
+                if (target.hasClass('delete-user')) {
+                    promptToRemoveUserFromPolicy(item);
+                }
+            }
+        });
+
+        // wire up the dataview to the grid
+        policyData.onRowCountChanged.subscribe(function (e, args) {
+            policyGrid.updateRowCount();
+            policyGrid.render();
+
+            // update the total number of displayed policy users
+            $('#displayed-policy-users').text(args.current);
+        });
+        policyData.onRowsChanged.subscribe(function (e, args) {
+            policyGrid.invalidateRows(args.rows);
+            policyGrid.render();
+        });
+
+        // hold onto an instance of the grid
+        $('#policy-table').data('gridInstance', policyGrid);
+
+        // initialize the number of displayed items
+        $('#displayed-policy-users').text('0');
+    };
+
+    /**
+     * Sorts the specified data using the specified sort details.
+     *
+     * @param {object} sortDetails
+     * @param {object} data
+     */
+    var sort = function (sortDetails, data) {
+        // defines a function for sorting
+        var comparer = function (a, b) {
+            var aString = nf.Common.isDefinedAndNotNull(a[sortDetails.columnId]) ? a[sortDetails.columnId] : '';
+            var bString = nf.Common.isDefinedAndNotNull(b[sortDetails.columnId]) ? b[sortDetails.columnId] : '';
+            return aString === bString ? 0 : aString > bString ? 1 : -1;
+        };
+
+        // perform the sort
+        data.sort(comparer, sortDetails.sortAsc);
+    };
+
+    /**
+     * Prompts for the removal of the specified user.
+     *
+     * @param item
+     */
+    var promptToRemoveUserFromPolicy = function (item) {
+        nf.Dialog.showYesNoDialog({
+            headerText: 'Update Policy',
+            dialogContent: 'Remove \'' + nf.Common.escapeHtml(item.component.identity) + '\' from this policy?',
+            yesHandler: function () {
+                removeUserFromPolicy(item);
+            }
+        });
+    };
+
+    /**
+     * Removes the specified item from the current policy.
+     *
+     * @param item
+     */
+    var removeUserFromPolicy = function (item) {
+        var policyGrid = $('#policy-table').data('gridInstance');
+        var policyData = policyGrid.getData();
+
+        // begin the update
+        policyData.beginUpdate();
+
+        // remove the user
+        policyData.deleteItem(item.id);
+
+        // end the update
+        policyData.endUpdate();
+
+        // save the configuration
+        updatePolicy();
+    };
+
+    /**
+     * Prompts for the deletion of the selected policy.
+     */
+    var promptToDeletePolicy = function () {
+        nf.Dialog.showYesNoDialog({
+            headerText: 'Update Policy',
+            dialogContent: 'Are you sure you want to delete this policy?',
+            yesHandler: function () {
+                deletePolicy();
+            }
+        });
+    };
+
+    /**
+     * Deletes the current policy.
+     */
+    var deletePolicy = function () {
+        var currentEntity = $('#policy-table').data('policy');
+        
+        if (nf.Common.isDefinedAndNotNull(currentEntity)) {
+            $.ajax({
+                type: 'DELETE',
+                url: currentEntity.component.uri + '?' + $.param(nf.Client.getRevision(currentEntity)),
+                dataType: 'json'
+            }).done(function () {
+                loadPolicy();
+            }).fail(nf.Common.handleAjaxError);
+        } else {
+            nf.Dialog.showOkDialog({
+                headerText: 'Update Policy',
+                dialogContent: 'No policy selected'
+            });
+        }
+    };
+
+    /**
+     * Gets the currently selected resource.
+     */
+    var getSelectedResourceAndAction = function () {
+        var componentId = $('#selected-policy-component-id').text();
+        var resource = $('#selected-policy-type').text();
+        if (componentId !== '') {
+            resource += ('/' + componentId);
+        }
+
+        return {
+            'action': $('#selected-policy-action').text(),
+            'resource': '/' + resource
+        };
+    };
+
+    /**
+     * Populates the table with the specified users and groups.
+     *
+     * @param users
+     * @param userGroups
+     */
+    var populateTable = function (users, userGroups) {
+        var policyGrid = $('#policy-table').data('gridInstance');
+        var policyData = policyGrid.getData();
+
+        // begin the update
+        policyData.beginUpdate();
+
+        var policyUsers = [];
+
+        // add each user
+        $.each(users, function (_, user) {
+            policyUsers.push($.extend({
+                type: 'user'
+            }, user));
+        });
+
+        // add each group
+        $.each(userGroups, function (_, group) {
+            policyUsers.push($.extend({
+                type: 'group'
+            }, group));
+        });
+
+        // set the rows
+        policyData.setItems(policyUsers);
+
+        // end the update
+        policyData.endUpdate();
+
+        // re-sort and clear selection after updating
+        policyData.reSort();
+        policyGrid.getSelectionModel().setSelectedRows([]);
+    };
+
+    /**
+     * Converts the specified resource into human readable form.
+     *
+     * @param resource
+     */
+    var convertToHumanReadableResource = function (resource) {
+        if (resource === '/policies') {
+            return 'all policies';
+        } else if (resource === '/controller') {
+            return 'the controller';
+        } else {
+            return 'Process Group ' + nf.Common.substringAfterLast(resource, '/');
+        }
+    };
+
+    /**
+     * Populates the specified policy.
+     *
+     * @param policyEntity
+     */
+    var populatePolicy = function (policyEntity) {
+        var policy = policyEntity.component;
+
+        // get the currently selected policy
+        var resourceAndAction = getSelectedResourceAndAction();
+
+        // reset of the policy message
+        resetPolicyMessage();
+
+        // store the current policy version
+        $('#policy-table').data('policy', policyEntity);
+        
+        // allow removal and modification as the policy is not inherited
+        $('#new-policy-user-button').prop('disabled', false);
+        
+        // update the refresh timestamp
+        $('#policy-last-refreshed').text(policyEntity.generated);
+
+        // see if the policy is for this resource
+        if (resourceAndAction.resource === policy.resource) {
+            // allow remove when policy is not inherited
+            $('#delete-policy-button').prop('disabled', false);
+        } else {
+            $('#policy-message').text('Showing effective policy inherited from ' + convertToHumanReadableResource(policy.resource) + '. ');
+            $('#new-policy-message').hide();
+            $('#override-policy-message').show();
+
+            // require non inherited policy for removal
+            $('#delete-policy-button').prop('disabled', true);
+        }
+
+        // populate the table
+        populateTable(policy.users, policy.userGroups);
+    };
+
+    /**
+     * Loads the configuration for the specified process group.
+     */
+    var loadPolicy = function () {
+        var resourceAndAction = getSelectedResourceAndAction();
+
+        return $.Deferred(function (deferred) {
+            $.ajax({
+                type: 'GET',
+                url: '../nifi-api/policies/' + resourceAndAction.action + resourceAndAction.resource,
+                dataType: 'json'
+            }).done(function (policyEntity) {
+                var policy = policyEntity.component;
+
+                $('#policy-message').text(policy.resource);
+
+                // populate the policy details
+                populatePolicy(policyEntity);
+
+                deferred.resolve();
+            }).fail(function (xhr, status, error) {
+                if (xhr.status === 404) {
+                    // show an appropriate messate
+                    $('#policy-message').text('No policy for the specified resource. ');
+                    $('#new-policy-message').show();
+                    $('#override-policy-message').hide();
+
+                    // reset the current policy
+                    $('#policy-table').removeData('policy');
+
+                    // require non inherited policy for removal and modification
+                    $('#new-policy-user-button').prop('disabled', true);
+                    $('#delete-policy-button').prop('disabled', true);
+
+                    // populate the table with no users
+                    populateTable([], []);
+
+                    deferred.resolve();
+                } else {
+                    deferred.reject();
+                    nf.Common.handleAjaxError(xhr, status, error);
+                }
+            });
+        }).promise();
+    };
+
+    /**
+     * Creates a new policy for the current selection.
+     */
+    var createPolicy = function () {
+        var resourceAndAction = getSelectedResourceAndAction();
+
+        var entity = {
+            'revision': nf.Client.getRevision({
+                'revision': {
+                    'version': 0
+                }
+            }),
+            'component': {
+                'action': resourceAndAction.action,
+                'resource': resourceAndAction.resource
+            }
+        };
+
+        $.ajax({
+            type: 'POST',
+            url: '../nifi-api/policies',
+            data: JSON.stringify(entity),
+            dataType: 'json',
+            contentType: 'application/json'
+        }).done(function (policyEntity) {
+            populatePolicy(policyEntity);
+        }).fail(nf.Common.handleAjaxError);
+    };
+
+    /**
+     * Updates the policy for the current selection.
+     */
+    var updatePolicy = function () {
+        var policyGrid = $('#policy-table').data('gridInstance');
+        var policyData = policyGrid.getData();
+
+        var users = [];
+        var userGroups = [];
+
+        var items = policyData.getItems();
+        $.each(items, function (_, item) {
+            if (item.type === 'user') {
+                users.push(item);
+            } else {
+                userGroups.push(item);
+            }
+
+            // remove the type as it was added client side to render differently
+            delete item.type;
+        });
+
+        var currentEntity = $('#policy-table').data('policy');
+        if (nf.Common.isDefinedAndNotNull(currentEntity)) {
+            var entity = {
+                'revision': nf.Client.getRevision(currentEntity),
+                'component': {
+                    'id': currentEntity.id,
+                    'users': users,
+                    'userGroups': userGroups
+                }
+            };
+    
+            $.ajax({
+                type: 'PUT',
+                url: currentEntity.component.uri,
+                data: JSON.stringify(entity),
+                dataType: 'json',
+                contentType: 'application/json'
+            }).done(function (policyEntity) {
+                populatePolicy(policyEntity);
+            }).fail(nf.Common.handleAjaxError);
+        } else {
+            nf.Dialog.showOkDialog({
+                headerText: 'Update Policy',
+                dialogContent: 'No policy selected'
+            });
+        }
+    };
+
+    /**
+     * Shows the process group configuration.
+     */
+    var showPolicy = function () {
+        // show the configuration dialog
+        nf.Shell.showContent('#policy-management').done(function () {
+            reset();
+        });
+
+        // adjust the table size
+        nf.PolicyManagement.resetTableSize();
+    };
+
+    /**
+     * Reset the policy message.
+     */
+    var resetPolicyMessage = function () {
+        $('#policy-message').text('');
+        $('#new-policy-message').hide();
+        $('#override-policy-message').hide();
+    };
+
+    /**
+     * Resets the policy management dialog.
+     */
+    var reset = function () {
+        resetPolicyMessage();
+
+        // clear the selected policy details
+        $('#selected-policy-type').text('');
+        $('#selected-policy-action').text('');
+        $('#selected-policy-component-id').text('');
+        $('#selected-policy-component-type').text('');
+        
+        // clear the selected component details
+        $('div.policy-selected-component-container').hide();
+
+        // reset button state
+        $('#delete-policy-button').prop('disabled', false);
+        $('#new-policy-user-button').prop('disabled', false);
+    };
+
+    /**
+     * Populates the initial policy resource.
+     *
+     * @param resource
+     */
+    var populateComponentResource = function (resource) {
+        // record the initial resource type
+        $('#selected-policy-component-type').text(resource);
+
+        var policyTarget = $('#component-policy-target').combo('getSelectedOption').value;
+        if (policyTarget === 'read-component') {
+            $('#selected-policy-action').text('read');
+        } else if (policyTarget === 'write-component') {
+            $('#selected-policy-action').text('write');
+        } else if (policyTarget === 'read-provenance-events') {
+            $('#selected-policy-action').text('read');
+            resource = ('provenance-events/' + resource);
+        } else if (policyTarget === 'read-policies') {
+            $('#selected-policy-action').text('read');
+            resource = ('policies/' + resource);
+        } else if (policyTarget === 'write-policies') {
+            $('#selected-policy-action').text('write');
+            resource = ('policies/' + resource);
+        }
+
+        // update the policy type
+        $('#selected-policy-type').text(resource);
+    };
+    
+    return {
+        /**
+         * Initializes the settings page.
+         */
+        init: function () {
+            initAddTenantToPolicyDialog();
+            initPolicyTable();
+
+            // mark as initialized
+            initialized = true;
+        },
+
+        /**
+         * Update the size of the grid based on its container's current size.
+         */
+        resetTableSize: function () {
+            var policyTable = $('#policy-table');
+            if (policyTable.is(':visible')) {
+                var policyGrid = policyTable.data('gridInstance');
+                if (nf.Common.isDefinedAndNotNull(policyGrid)) {
+                    policyGrid.resizeCanvas();
+                }
+            }
+        },
+
+        /**
+         * Shows the controller service policy.
+         *
+         * @param d
+         */
+        showControllerServicePolicy: function (d) {
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').show();
+            $('#global-policy-controls').hide();
+
+            // update the visibility
+            if (d.permissions.canRead) {
+                $('#policy-selected-controller-service-container div.policy-selected-component-name').text(d.component.name);
+            } else {
+                $('#policy-selected-controller-service-container div.policy-selected-component-name').text(d.id);
+            }
+            $('#policy-selected-controller-service-container').show();
+
+            // populate the initial resource
+            $('#selected-policy-component-id').text(d.id);
+            populateComponentResource('controller-services');
+
+            return loadPolicy().done(showPolicy);
+        },
+
+        /**
+         * Shows the reporting task policy.
+         *
+         * @param d
+         */
+        showReportingTaskPolicy: function (d) {
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').show();
+            $('#global-policy-controls').hide();
+
+            // update the visibility
+            if (d.permissions.canRead) {
+                $('#policy-selected-reporting-task-container div.policy-selected-component-name').text(d.component.name);
+            } else {
+                $('#policy-selected-reporting-task-container div.policy-selected-component-name').text(d.id);
+            }
+            $('#policy-selected-reporting-task-container').show();
+            
+            // populate the initial resource
+            $('#selected-policy-component-id').text(d.id);
+            populateComponentResource('reporting-tasks');
+
+            return loadPolicy().done(showPolicy);
+        },
+
+        /**
+         * Shows the template policy.
+         *
+         * @param d
+         */
+        showTemplatePolicy: function (d) {
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').show();
+            $('#global-policy-controls').hide();
+
+            // update the visibility
+            if (d.permissions.canRead) {
+                $('#policy-selected-template-container div.policy-selected-component-name').text(d.template.name);
+            } else {
+                $('#policy-selected-template-container div.policy-selected-component-name').text(d.id);
+            }
+            $('#policy-selected-template-container').show();
+
+            // populate the initial resource
+            $('#selected-policy-component-id').text(d.id);
+            populateComponentResource('templates');
+
+            return loadPolicy().done(showPolicy);
+        },
+
+        /**
+         * Shows the component policy dialog.
+         */
+        showComponentPolicy: function (selection) {
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').show();
+            $('#global-policy-controls').hide();
+
+            // update the visibility
+            $('#policy-selected-component-container').show();
+            
+            var resource;
+            if (selection.empty()) {
+                $('#selected-policy-component-id').text(nf.Canvas.getGroupId());
+                resource = 'process-groups';
+            } else {
+                var d = selection.datum();
+                $('#selected-policy-component-id').text(d.id);
+
+                if (nf.CanvasUtils.isProcessor(selection)) {
+                    resource = 'processors';
+                } else if (nf.CanvasUtils.isConnection(selection)) {
+                    resource = 'connections';
+                } else if (nf.CanvasUtils.isProcessGroup(selection)) {
+                    resource = 'process-groups';
+                } else if (nf.CanvasUtils.isInputPort(selection)) {
+                    resource = 'input-ports';
+                } else if (nf.CanvasUtils.isOutputPort(selection)) {
+                    resource = 'output-ports';
+                } else if (nf.CanvasUtils.isRemoteProcessGroup(selection)) {
+                    resource = 'remote-process-groups';
+                } else if (nf.CanvasUtils.isLabel(selection)) {
+                    resource = 'labels';
+                }
+
+                // enable site to site option
+                $('#component-policy-target')
+                    .combo('setOptionEnabled', {
+                        value: 'write-receive-data'
+                    }, nf.CanvasUtils.isInputPort(selection) && nf.Canvas.getParentGroupId() === null)
+                    .combo('setOptionEnabled', {
+                        value: 'write-send-data'
+                    }, nf.CanvasUtils.isOutputPort(selection) && nf.Canvas.getParentGroupId() === null);
+            }
+
+            // populate the initial resource
+            populateComponentResource(resource);
+
+            return loadPolicy().done(showPolicy);
+        },
+
+        /**
+         * Shows the global policies dialog.
+         */
+        showGlobalPolicies: function () {
+            // reset the policy message
+            resetPolicyMessage();
+
+            // update the policy controls visibility
+            $('#component-policy-controls').hide();
+            $('#global-policy-controls').show();
+
+            // reload the current policies
+            var policyType = $('#policy-type-list').combo('getSelectedOption').value;
+            $('#selected-policy-type').text(policyType);
+
+            if (policyType === 'controller') {
+                $('#selected-policy-action').text($('#controller-policy-target').combo('getSelectedOption').value);
+            } else if (policyType === 'proxy') {
+                $('#selected-policy-action').text('write');
+            } else {
+                $('#selected-policy-action').text('read');
+            }
+
+            return loadPolicy().done(showPolicy);
+        },
+    };
+}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
index 38ccbca..ec08e50 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-port.js
@@ -159,7 +159,7 @@ nf.Port = (function () {
 
         // only activate dragging and connecting if appropriate
         port.filter(function (d) {
-            return d.accessPolicy.canWrite && d.accessPolicy.canRead;
+            return d.permissions.canWrite && d.permissions.canRead;
         }).call(nf.Draggable.activate).call(nf.Connectable.activate);
     };
 
@@ -176,13 +176,13 @@ nf.Port = (function () {
         // port border authorization
         updated.select('rect.border')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         // port body authorization
         updated.select('rect.body')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         updated.each(function (portData) {
@@ -262,7 +262,7 @@ nf.Port = (function () {
                         });
                 }
 
-                if (portData.accessPolicy.canRead) {
+                if (portData.permissions.canRead) {
                     // update the port name
                     port.select('text.port-name')
                         .each(function (d) {
@@ -291,7 +291,7 @@ nf.Port = (function () {
                 // populate the stats
                 port.call(updatePortStatus);
             } else {
-                if (portData.accessPolicy.canRead) {
+                if (portData.permissions.canRead) {
                     // update the port name
                     port.select('text.port-name')
                         .text(function (d) {
@@ -364,7 +364,7 @@ nf.Port = (function () {
                 }
 
                 // if there are validation errors generate a tooltip
-                if (d.accessPolicy.canRead && !nf.Common.isEmpty(d.component.validationErrors)) {
+                if (d.permissions.canRead && !nf.Common.isEmpty(d.component.validationErrors)) {
                     tip = d3.select('#port-tooltips').append('div')
                         .attr('id', function () {
                             return 'run-status-tip-' + d.id;

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
index 26d253d..d27a46a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-configuration.js
@@ -74,7 +74,7 @@ nf.ProcessGroupConfiguration = (function () {
             contentType: 'application/json'
         }).done(function (response) {
             // refresh the process group if necessary
-            if (response.accessPolicy.canRead && response.component.parentGroupId === nf.Canvas.getGroupId()) {
+            if (response.permissions.canRead && response.component.parentGroupId === nf.Canvas.getGroupId()) {
                 nf.ProcessGroup.set(response);
             }
 
@@ -122,7 +122,7 @@ nf.ProcessGroupConfiguration = (function () {
                 url: config.urls.api + '/process-groups/' + encodeURIComponent(groupId),
                 dataType: 'json'
             }).done(function (response) {
-                if (response.accessPolicy.canWrite) {
+                if (response.permissions.canWrite) {
                     var processGroup = response.component;
 
                     // populate the process group settings
@@ -137,7 +137,7 @@ nf.ProcessGroupConfiguration = (function () {
                         saveConfiguration(response.revision.version, response.id);
                     });
                 } else {
-                    if (response.accessPolicy.canRead) {
+                    if (response.permissions.canRead) {
                         // populate the process group settings
                         $('#read-only-process-group-name').removeClass('unset').text(response.component.name);
                         $('#read-only-process-group-comments').removeClass('unset').text(response.component.comments);

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-details.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-details.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-details.js
deleted file mode 100644
index 019d3aa..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group-details.js
+++ /dev/null
@@ -1,68 +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.
- */
-
-/* global nf */
-
-nf.ProcessGroupDetails = (function () {
-
-    return {
-        init: function () {
-            // configure the processor details dialog
-            $('#process-group-details').modal({
-                scrollableContentStyle: 'scrollable',
-                headerText: 'Process Group Details',
-                buttons: [{
-                    buttonText: 'Ok',
-                    color: {
-                        base: '#728E9B',
-                        hover: '#004849',
-                        text: '#ffffff'
-                    },
-                    handler: {
-                        click: function () {
-                            // hide the dialog
-                            $('#process-group-details').modal('hide');
-                        }
-                    }
-                }],
-                handler: {
-                    close: function () {
-                        // clear the processor details
-                        nf.Common.clearField('read-only-process-group-id');
-                        nf.Common.clearField('read-only-process-group-name');
-                        nf.Common.clearField('read-only-process-group-comments');
-                    }
-                }
-            });
-        },
-
-        showDetails: function (selection) {
-            // if the specified selection is a process group
-            if (nf.CanvasUtils.isProcessGroup(selection)) {
-                var selectionData = selection.datum();
-
-                // populate the port settings
-                nf.Common.populateField('read-only-process-group-id', selectionData.id);
-                nf.Common.populateField('read-only-process-group-name', selectionData.component.name);
-                nf.Common.populateField('read-only-process-group-comments', selectionData.component.comments);
-
-                // show the details
-                $('#process-group-details').modal('show');
-            }
-        }
-    };
-}());
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-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-process-group.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
index d4dbe37..fc96c01 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-process-group.js
@@ -157,7 +157,7 @@ nf.ProcessGroup = (function () {
 
         // only support dragging, connection, and drag and drop if appropriate
         processGroup.filter(function (d) {
-                return d.accessPolicy.canWrite && d.accessPolicy.canRead;
+                return d.permissions.canWrite && d.permissions.canRead;
             })
             .on('mouseover.drop', function (d) {
                 // Using mouseover/out to workaround chrome issue #122746
@@ -210,13 +210,13 @@ nf.ProcessGroup = (function () {
         // process group border authorization
         updated.select('rect.border')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         // process group body authorization
         updated.select('rect.body')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         updated.each(function (processGroupData) {
@@ -742,7 +742,7 @@ nf.ProcessGroup = (function () {
                         return stoppedX + stoppedCount.node().getComputedTextLength() + CONTENTS_SPACER;
                     })
                     .classed('has-validation-errors', function (d) {
-                        return d.accessPolicy.canRead && d.component.invalidCount > 0;
+                        return d.permissions.canRead && d.component.invalidCount > 0;
                     });
                 var invalidCount = details.select('text.process-group-invalid-count')
                     .attr('x', function () {
@@ -768,7 +768,7 @@ nf.ProcessGroup = (function () {
                         return d.disabledCount;
                     });
 
-                if (processGroupData.accessPolicy.canRead) {
+                if (processGroupData.permissions.canRead) {
                     // update the process group comments
                     details.select('text.process-group-comments')
                         .each(function (d) {
@@ -812,7 +812,7 @@ nf.ProcessGroup = (function () {
                 // populate the stats
                 processGroup.call(updateProcessGroupStatus);
             } else {
-                if (processGroupData.accessPolicy.canRead) {
+                if (processGroupData.permissions.canRead) {
                     // update the process group name
                     processGroup.select('text.process-group-name')
                         .text(function (d) {

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
index a1602df..86c5228 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor-configuration.js
@@ -374,7 +374,7 @@ nf.ProcessorConfiguration = (function () {
     var reloadProcessorConnections = function (processor) {
         var connections = nf.Connection.getComponentConnections(processor.id);
         $.each(connections, function (_, connection) {
-            if (connection.accessPolicy.canRead) {
+            if (connection.permissions.canRead) {
                 if (connection.sourceId === processor.id) {
                     nf.Connection.reload(connection.component);
                 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
index d553b57..1f133ce 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/canvas/nf-processor.js
@@ -143,13 +143,13 @@ nf.Processor = (function () {
         // processor border authorization
         updated.select('rect.border')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         // processor body authorization
         updated.select('rect.body')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         updated.each(function (processorData) {
@@ -474,7 +474,7 @@ nf.Processor = (function () {
                         .text('\uf24a');
                 }
 
-                if (processorData.accessPolicy.canRead) {
+                if (processorData.permissions.canRead) {
                     // update the processor name
                     processor.select('text.processor-name')
                         .each(function (d) {
@@ -516,7 +516,7 @@ nf.Processor = (function () {
                 // populate the stats
                 processor.call(updateProcessorStatus);
             } else {
-                if (processorData.accessPolicy.canRead) {
+                if (processorData.permissions.canRead) {
                     // update the processor name
                     processor.select('text.processor-name')
                         .text(function (d) {
@@ -553,7 +553,7 @@ nf.Processor = (function () {
                 // get the default color
                 var color = nf.Processor.defaultColor();
                 
-                if (!d.accessPolicy.canRead) {
+                if (!d.permissions.canRead) {
                     return color;
                 }
 
@@ -615,7 +615,7 @@ nf.Processor = (function () {
                 }
 
                 // if there are validation errors generate a tooltip
-                if (d.accessPolicy.canRead && !nf.Common.isEmpty(d.component.validationErrors)) {
+                if (d.permissions.canRead && !nf.Common.isEmpty(d.component.validationErrors)) {
                     tip = d3.select('#processor-tooltips').append('div')
                         .attr('id', function () {
                             return 'run-status-tip-' + d.id;

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/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 ceaddc2..b053761 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
@@ -168,13 +168,13 @@ nf.RemoteProcessGroup = (function () {
         // remote process group border authorization
         updated.select('rect.border')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         // remote process group body authorization
         updated.select('rect.body')
             .classed('unauthorized', function (d) {
-                return d.accessPolicy.canRead === false;
+                return d.permissions.canRead === false;
             });
 
         updated.each(function (remoteProcessGroupData) {
@@ -493,7 +493,7 @@ nf.RemoteProcessGroup = (function () {
                         .text('\uf24a');
                 }
 
-                if (remoteProcessGroupData.accessPolicy.canRead) {
+                if (remoteProcessGroupData.permissions.canRead) {
                     // remote process group uri
                     details.select('text.remote-process-group-uri')
                         .each(function (d) {
@@ -612,7 +612,7 @@ nf.RemoteProcessGroup = (function () {
                 // populate the stats
                 remoteProcessGroup.call(updateProcessGroupStatus);
             } else {
-                if (remoteProcessGroupData.accessPolicy.canRead) {
+                if (remoteProcessGroupData.permissions.canRead) {
                     // update the process group name
                     remoteProcessGroup.select('text.remote-process-group-name')
                         .text(function (d) {
@@ -694,7 +694,7 @@ nf.RemoteProcessGroup = (function () {
         updated.select('text.remote-process-group-transmission-status')
             .text(function (d) {
                 var icon = '';
-                if (d.accessPolicy.canRead) {
+                if (d.permissions.canRead) {
                     if (!nf.Common.isEmpty(d.component.authorizationIssues)) {
                         icon = '\uf071';
                     } else if (d.component.transmitting === true) {
@@ -707,7 +707,7 @@ nf.RemoteProcessGroup = (function () {
             })
             .attr('font-family', function (d) {
                 var family = '';
-                if (d.accessPolicy.canRead) {
+                if (d.permissions.canRead) {
                     if (!nf.Common.isEmpty(d.component.authorizationIssues) || d.component.transmitting) {
                         family = 'FontAwesome';
                     } else {
@@ -717,7 +717,7 @@ nf.RemoteProcessGroup = (function () {
                 return family;
             })
             .classed('has-authorization-errors', function (d) {
-                return d.accessPolicy.canRead && !nf.Common.isEmpty(d.component.authorizationIssues);
+                return d.permissions.canRead && !nf.Common.isEmpty(d.component.authorizationIssues);
             })
             .each(function (d) {
                 // remove the existing tip if necessary
@@ -727,7 +727,7 @@ nf.RemoteProcessGroup = (function () {
                 }
 
                 // if there are validation errors generate a tooltip
-                if (d.accessPolicy.canRead && !nf.Common.isEmpty(d.component.authorizationIssues)) {
+                if (d.permissions.canRead && !nf.Common.isEmpty(d.component.authorizationIssues)) {
                     tip = d3.select('#remote-process-group-tooltips').append('div')
                         .attr('id', function () {
                             return 'authorization-issues-' + d.id;
@@ -944,7 +944,7 @@ nf.RemoteProcessGroup = (function () {
                     // reload the group's connections
                     var connections = nf.Connection.getComponentConnections(remoteProcessGroup.id);
                     $.each(connections, function (_, connection) {
-                        if (connection.accessPolicy.canRead) {
+                        if (connection.permissions.canRead) {
                             nf.Connection.reload(connection.component);
                         }
                     });

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/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 5c0b303..7ba7213 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
@@ -171,7 +171,7 @@ nf.Settings = (function () {
      * @returns {String}
      */
     var nameFormatter = function (row, cell, value, columnDef, dataContext) {
-        if (!dataContext.accessPolicy.canRead) {
+        if (!dataContext.permissions.canRead) {
             return '<span class="blank">' + dataContext.id + '</span>';
         }
 
@@ -189,7 +189,7 @@ nf.Settings = (function () {
      * @returns {String}
      */
     var typeFormatter = function (row, cell, value, columnDef, dataContext) {
-        if (!dataContext.accessPolicy.canRead) {
+        if (!dataContext.permissions.canRead) {
             return '';
         }
 
@@ -589,7 +589,7 @@ nf.Settings = (function () {
         initNewReportingTaskDialog();
 
         var moreReportingTaskDetails = function (row, cell, value, columnDef, dataContext) {
-            if (!dataContext.accessPolicy.canRead) {
+            if (!dataContext.permissions.canRead) {
                 return '';
             }
 
@@ -617,7 +617,7 @@ nf.Settings = (function () {
         };
 
         var reportingTaskRunStatusFormatter = function (row, cell, value, columnDef, dataContext) {
-            if (!dataContext.accessPolicy.canRead) {
+            if (!dataContext.permissions.canRead) {
                 return '';
             }
 
@@ -649,25 +649,28 @@ nf.Settings = (function () {
         var reportingTaskActionFormatter = function (row, cell, value, columnDef, dataContext) {
             var markup = '';
 
-            if (dataContext.accessPolicy.canRead && dataContext.accessPolicy.canWrite) {
+            if (dataContext.permissions.canRead && dataContext.permissions.canWrite) {
                 if (dataContext.component.state === 'RUNNING') {
-                    markup += '<div title="Stop" class="pointer stop-reporting-task fa fa-stop" style="margin-top: 2px;" ></div>';
+                    markup += '<div title="Stop" class="pointer stop-reporting-task fa fa-stop" style="margin-top: 2px; margin-right: 3px;" ></div>';
                 } else if (dataContext.component.state === 'STOPPED' || dataContext.component.state === 'DISABLED') {
-                    markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil" style="margin-top: 2px;" ></div>';
+                    markup += '<div title="Edit" class="pointer edit-reporting-task fa fa-pencil" style="margin-top: 2px; margin-right: 3px;" ></div>';
 
                     // support starting when stopped and no validation errors
                     if (dataContext.component.state === 'STOPPED' && nf.Common.isEmpty(dataContext.component.validationErrors)) {
-                        markup += '<div title="Start" class="pointer start-reporting-task fa fa-play" style="margin-top: 2px; margin-left: 3px;"></div>';
+                        markup += '<div title="Start" class="pointer start-reporting-task fa fa-play" style="margin-top: 2px; margin-right: 3px;"></div>';
                     }
 
-                    markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash" style="margin-top: 2px; margin-left: 3px;" ></div>';
+                    markup += '<div title="Remove" class="pointer delete-reporting-task fa fa-trash" style="margin-top: 2px; margin-right: 3px;" ></div>';
                 }
 
                 if (dataContext.component.persistsState === true) {
-                    markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks" style="margin-top: 2px; margin-left: 3px;" ></div>';
+                    markup += '<div title="View State" class="pointer view-state-reporting-task fa fa-tasks" style="margin-top: 2px; margin-right: 3px;" ></div>';
                 }
             }
 
+            // TODO - only if we can adminster policies
+            markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
+
             return markup;
         };
 
@@ -726,6 +729,12 @@ nf.Settings = (function () {
                 } else if (target.hasClass('view-state-reporting-task')) {
                     var canClear = reportingTaskEntity.component.state === 'STOPPED' && reportingTaskEntity.component.activeThreadCount === 0;
                     nf.ComponentState.showState(reportingTaskEntity.component, canClear);
+                } else if (target.hasClass('edit-access-policies')) {
+                    // show the policies for this service
+                    nf.PolicyManagement.showReportingTaskPolicy(reportingTaskEntity);
+
+                    // close the settings dialog
+                    $('#shell-close-button').click();
                 }
             } else if (reportingTasksGrid.getColumns()[args.cell].id === 'moreDetails') {
                 if (target.hasClass('view-reporting-task')) {
@@ -848,7 +857,7 @@ nf.Settings = (function () {
                 // update the current time
                 $('#settings-last-refreshed').text(response.currentTime);
 
-                if (response.accessPolicy.canWrite) {
+                if (response.permissions.canWrite) {
                     // populate the settings
                     $('#maximum-timer-driven-thread-count-field').removeClass('unset').val(response.controllerConfiguration.maxTimerDrivenThreadCount);
                     $('#maximum-event-driven-thread-count-field').removeClass('unset').val(response.controllerConfiguration.maxEventDrivenThreadCount);
@@ -860,7 +869,7 @@ nf.Settings = (function () {
                         saveSettings(response.revision.version);
                     });
                 } else {
-                    if (response.accessPolicy.canRead) {
+                    if (response.permissions.canRead) {
                         // populate the settings
                         $('#read-only-maximum-timer-driven-thread-count-field').removeClass('unset').text(response.controllerConfiguration.maxTimerDrivenThreadCount);
                         $('#read-only-maximum-event-driven-thread-count-field').removeClass('unset').text(response.controllerConfiguration.maxEventDrivenThreadCount);

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/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 7c9951e..d3d848a 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
@@ -286,6 +286,32 @@ nf.Common = (function () {
         },
 
         /**
+         * Determines whether the current user can access counters.
+         *
+         * @returns {boolean}
+         */
+        canAccessPolicies: function () {
+            if (nf.Common.isDefinedAndNotNull(nf.Common.currentUser)) {
+                return nf.Common.currentUser.policiesPermissions.canRead === true;
+            } else {
+                return false;
+            }
+        },
+
+        /**
+         * Determines whether the current user can modify counters.
+         *
+         * @returns {boolean}
+         */
+        canModifyPolicies: function () {
+            if (nf.Common.isDefinedAndNotNull(nf.Common.currentUser)) {
+                return nf.Common.currentUser.policiesPermissions.canRead === true && nf.Common.currentUser.policiesPermissions.canWrite === true;
+            } else {
+                return false;
+            }
+        },
+
+        /**
          * Determines whether the current user can access the controller.
          *
          * @returns {boolean}
@@ -402,7 +428,7 @@ nf.Common = (function () {
             // status code 400, 403, 404, and 409 are expected response codes for common errors.
             if (xhr.status === 400 || xhr.status === 403 || xhr.status === 404 || xhr.status === 409) {
                 nf.Dialog.showOkDialog({
-                    headerText: 'Malformed Request',
+                    headerText: 'Error',
                     dialogContent: nf.Common.escapeHtml(xhr.responseText)
                 });
             } else {

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
index f76adf6..8a7f86e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/nf-processor-details.js
@@ -261,7 +261,7 @@ nf.ProcessorDetails = (function () {
             }).fail(function (xhr, status, error) {
                 if (xhr.status === 400 || xhr.status === 404 || xhr.status === 409) {
                     nf.Dialog.showOkDialog({
-                        headerText: 'Malformed Request',
+                        headerText: 'Error',
                         dialogContent: nf.Common.escapeHtml(xhr.responseText)
                     });
                 } else {

http://git-wip-us.apache.org/repos/asf/nifi/blob/e0c96794/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
index 5477a31..0b58865 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/js/nf/templates/nf-templates-table.js
@@ -74,6 +74,22 @@ nf.TemplatesTable = (function () {
     };
 
     /**
+     * Opens the access policies for the specified template.
+     * 
+     * @param templateEntity
+     */
+    var openAccessPolicies = function (templateEntity) {
+        // only attempt this if we're within a frame
+        if (top !== window) {
+            // and our parent has canvas utils and shell defined
+            if (nf.Common.isDefinedAndNotNull(parent.nf) && nf.Common.isDefinedAndNotNull(parent.nf.PolicyManagement) && nf.Common.isDefinedAndNotNull(parent.nf.Shell)) {
+                parent.nf.PolicyManagement.showTemplatePolicy(templateEntity);
+                parent.$('#shell-close-button').click();
+            }
+        }
+    };
+
+    /**
      * Deletes the template with the specified id.
      * 
      * @argument {string} templateEntity     The template
@@ -212,7 +228,7 @@ nf.TemplatesTable = (function () {
             });
 
             var timestampFormatter = function (row, cell, value, columnDef, dataContext) {
-                if (!dataContext.accessPolicy.canRead) {
+                if (!dataContext.permissions.canRead) {
                     return '';
                 }
 
@@ -220,7 +236,7 @@ nf.TemplatesTable = (function () {
             };
 
             var nameFormatter = function (row, cell, value, columnDef, dataContext) {
-                if (!dataContext.accessPolicy.canRead) {
+                if (!dataContext.permissions.canRead) {
                     return '<span class="blank">' + dataContext.id + '</span>';
                 }
 
@@ -228,7 +244,7 @@ nf.TemplatesTable = (function () {
             };
 
             var descriptionFormatter = function (row, cell, value, columnDef, dataContext) {
-                if (!dataContext.accessPolicy.canRead) {
+                if (!dataContext.permissions.canRead) {
                     return '';
                 }
 
@@ -236,7 +252,7 @@ nf.TemplatesTable = (function () {
             };
 
             var groupIdFormatter = function (row, cell, value, columnDef, dataContext) {
-                if (!dataContext.accessPolicy.canRead) {
+                if (!dataContext.permissions.canRead) {
                     return '';
                 }
 
@@ -247,14 +263,21 @@ nf.TemplatesTable = (function () {
             var actionFormatter = function (row, cell, value, columnDef, dataContext) {
                 var markup = '';
 
-                if (dataContext.accessPolicy.canRead === true) {
-                    markup += '<div title="Download" class="pointer export-template icon icon-template-save" style="margin-top: 2px;"></div>';
+                if (dataContext.permissions.canRead === true) {
+                    markup += '<div title="Download" class="pointer export-template icon icon-template-save" style="margin-top: 2px; margin-right: 3px;"></div>';
                 }
 
                 // all DFMs to remove templates
-                if (dataContext.accessPolicy.canWrite === true) {
-                    markup += '<div title="Remove Template" class="pointer prompt-to-delete-template fa fa-trash" style="margin-top: 2px; margin-left: 3px;"></div>';
+                if (dataContext.permissions.canWrite === true) {
+                    markup += '<div title="Remove Template" class="pointer prompt-to-delete-template fa fa-trash" style="margin-top: 2px; margin-right: 3px;"></div>';
                 }
+
+                // if we in the shell
+                // TODO - only if we can adminster policies
+                if (top !== window) {
+                    markup += '<div title="Access Policies" class="pointer edit-access-policies fa fa-key" style="margin-top: 2px;"></div>';
+                }
+
                 return markup;
             };
 
@@ -317,6 +340,8 @@ nf.TemplatesTable = (function () {
                         downloadTemplate(item);
                     } else if (target.hasClass('prompt-to-delete-template')) {
                         promptToDeleteTemplate(item);
+                    } else if (target.hasClass('edit-access-policies')) {
+                        openAccessPolicies(item);
                     }
                 }
             });