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/18 19:06:14 UTC

nifi git commit: NIFI-2272: - Ensuring the appropriate visibilty of the action in the policy management page. NIFI-2273: - Ensuring we load the policy or inform the user of the appropriate permissions of the effective policy. NIFI-2239: - Providing help

Repository: nifi
Updated Branches:
  refs/heads/master 5c8636edf -> aa91032cd


NIFI-2272:
- Ensuring the appropriate visibilty of the action in the policy management page.
NIFI-2273:
- Ensuring we load the policy or inform the user of the appropriate permissions of the effective policy.
NIFI-2239:
- Providing help tooltips for the policies in the management page.
NIFI-2283:
- Adding auditing for access policies, users, and groups.
NIFI-2263:
- Not replicating history requests throughout the cluster.
NIFI-2096:
- Fixing upload template file input in Firefox.
NIFI-2301:
- Removing relevant policies after component deletion.


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

Branch: refs/heads/master
Commit: aa91032cde8ad1cf2caf18fc0f02b5f678e1f3a7
Parents: 5c8636e
Author: Matt Gilman <ma...@gmail.com>
Authored: Mon Jul 18 14:05:04 2016 -0400
Committer: Mark Payne <ma...@hotmail.com>
Committed: Mon Jul 18 15:05:54 2016 -0400

----------------------------------------------------------------------
 .../java/org/apache/nifi/action/Component.java  |   5 +-
 .../apache/nifi/audit/AccessPolicyAuditor.java  | 274 +++++++++++++++++++
 .../java/org/apache/nifi/audit/UserAuditor.java | 245 +++++++++++++++++
 .../org/apache/nifi/audit/UserGroupAuditor.java | 259 ++++++++++++++++++
 .../nifi/web/StandardNiFiServiceFacade.java     |  65 +++--
 .../apache/nifi/web/api/ControllerResource.java |   4 +-
 .../org/apache/nifi/web/api/FlowResource.java   |  39 +--
 .../src/main/resources/nifi-web-api-context.xml |  15 +
 .../WEB-INF/partials/canvas/navigation.jsp      |   2 +-
 .../partials/canvas/policy-management.jsp       |   2 +-
 .../partials/canvas/upload-template-dialog.jsp  |   5 +-
 .../webapp/js/nf/canvas/nf-policy-management.js | 158 +++++++----
 12 files changed, 961 insertions(+), 112 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-api/src/main/java/org/apache/nifi/action/Component.java
----------------------------------------------------------------------
diff --git a/nifi-api/src/main/java/org/apache/nifi/action/Component.java b/nifi-api/src/main/java/org/apache/nifi/action/Component.java
index dcd23ee..a25525f 100644
--- a/nifi-api/src/main/java/org/apache/nifi/action/Component.java
+++ b/nifi-api/src/main/java/org/apache/nifi/action/Component.java
@@ -30,5 +30,8 @@ public enum Component {
     Funnel,
     Connection,
     ControllerService,
-    ReportingTask;
+    ReportingTask,
+    AccessPolicy,
+    User,
+    UserGroup;
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/AccessPolicyAuditor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/AccessPolicyAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/AccessPolicyAuditor.java
new file mode 100644
index 0000000..9636e5a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/AccessPolicyAuditor.java
@@ -0,0 +1,274 @@
+/*
+ * 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.audit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.authorization.AccessPolicy;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.web.api.dto.AccessPolicyDTO;
+import org.apache.nifi.web.dao.AccessPolicyDAO;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Audits policy creation/removal and configuration changes.
+ */
+@Aspect
+public class AccessPolicyAuditor extends NiFiAuditor {
+
+    private static final Logger logger = LoggerFactory.getLogger(AccessPolicyAuditor.class);
+
+    private static final String USERS = "Users";
+    private static final String USER_GROUPS = "User Groups";
+
+    /**
+     * Audits the creation of policies via createAccessPolicy().
+     *
+     * This method only needs to be run 'after returning'. However, in Java 7 the order in which these methods are returned from Class.getDeclaredMethods (even though there is no order guaranteed)
+     * seems to differ from Java 6. SpringAOP depends on this ordering to determine advice precedence. By normalizing all advice into Around advice we can alleviate this issue.
+     *
+     * @param proceedingJoinPoint join point
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+            + "execution(org.apache.nifi.authorization.AccessPolicy createAccessPolicy(org.apache.nifi.web.api.dto.AccessPolicyDTO))")
+    public AccessPolicy createAccessPolicyAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+        // create the access policy
+        AccessPolicy policy = (AccessPolicy) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the policy action...
+        final Action action = generateAuditRecord(policy, Operation.Add);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return policy;
+    }
+
+    /**
+     * Audits the configuration of a single policy.
+     *
+     * @param proceedingJoinPoint join point
+     * @param accessPolicyDTO dto
+     * @param accessPolicyDAO dao
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+            + "execution(org.apache.nifi.authorization.AccessPolicy updateAccessPolicy(org.apache.nifi.web.api.dto.AccessPolicyDTO)) && "
+            + "args(accessPolicyDTO) && "
+            + "target(accessPolicyDAO)")
+    public AccessPolicy updateAccessPolicyAdvice(ProceedingJoinPoint proceedingJoinPoint, AccessPolicyDTO accessPolicyDTO, AccessPolicyDAO accessPolicyDAO) throws Throwable {
+        // determine the initial values for each property/setting thats changing
+        AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(accessPolicyDTO.getId());
+        final Map<String, String> values = extractConfiguredPropertyValues(accessPolicy, accessPolicyDTO);
+
+        // update the policy state
+        final AccessPolicy updatedAccessPolicy = (AccessPolicy) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the policy action...
+        // get the updated verbose state
+        accessPolicy = accessPolicyDAO.getAccessPolicy(updatedAccessPolicy.getIdentifier());
+
+        // get the current user
+        NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (user != null) {
+            // determine the updated values
+            Map<String, String> updatedValues = extractConfiguredPropertyValues(accessPolicy, accessPolicyDTO);
+
+            // create a policy action
+            Date actionTimestamp = new Date();
+            Collection<Action> actions = new ArrayList<>();
+
+            // go through each updated value
+            for (String property : updatedValues.keySet()) {
+                String newValue = updatedValues.get(property);
+                String oldValue = values.get(property);
+                Operation operation = null;
+
+                // determine the type of operation
+                if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
+                    operation = Operation.Configure;
+                }
+
+                // create a configuration action accordingly
+                if (operation != null) {
+                    final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+                    actionDetails.setName(property);
+                    actionDetails.setValue(newValue);
+                    actionDetails.setPreviousValue(oldValue);
+
+                    // create a configuration action
+                    FlowChangeAction configurationAction = new FlowChangeAction();
+                    configurationAction.setUserIdentity(user.getIdentity());
+                    configurationAction.setOperation(operation);
+                    configurationAction.setTimestamp(actionTimestamp);
+                    configurationAction.setSourceId(accessPolicy.getIdentifier());
+                    configurationAction.setSourceName(formatPolicyName(accessPolicy));
+                    configurationAction.setSourceType(Component.AccessPolicy);
+                    configurationAction.setActionDetails(actionDetails);
+                    actions.add(configurationAction);
+                }
+            }
+
+            // ensure there are actions to record
+            if (!actions.isEmpty()) {
+                // save the actions
+                saveActions(actions, logger);
+            }
+        }
+
+        return updatedAccessPolicy;
+    }
+
+    /**
+     * Audits the removal of a policy via deleteAccessPolicy().
+     *
+     * @param proceedingJoinPoint join point
+     * @param policyId policy id
+     * @param accessPolicyDAO dao
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.AccessPolicyDAO+) && "
+            + "execution(org.apache.nifi.authorization.AccessPolicy deleteAccessPolicy(java.lang.String)) && "
+            + "args(policyId) && "
+            + "target(accessPolicyDAO)")
+    public AccessPolicy removePolicyAdvice(ProceedingJoinPoint proceedingJoinPoint, String policyId, AccessPolicyDAO accessPolicyDAO) throws Throwable {
+        // get the policy before removing it
+        AccessPolicy accessPolicy = accessPolicyDAO.getAccessPolicy(policyId);
+
+        // remove the policy
+        final AccessPolicy removedAccessPolicy = (AccessPolicy)proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add removal actions...
+        // audit the policy removal
+        final Action action = generateAuditRecord(accessPolicy, Operation.Remove);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return removedAccessPolicy;
+    }
+
+    /**
+     * Generates an audit record for the creation of a policy.
+     *
+     * @param policy policy
+     * @param operation operation
+     * @return action
+     */
+    public Action generateAuditRecord(AccessPolicy policy, Operation operation) {
+        return generateAuditRecord(policy, operation, null);
+    }
+
+    /**
+     * Generates an audit record for the creation of a policy.
+     *
+     * @param policy policy
+     * @param operation operation
+     * @param actionDetails details
+     * @return action
+     */
+    public Action generateAuditRecord(AccessPolicy policy, Operation operation, ActionDetails actionDetails) {
+        FlowChangeAction action = null;
+
+        // get the current user
+        NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (user != null) {
+            // create the policy action for adding this policy
+            action = new FlowChangeAction();
+            action.setUserIdentity(user.getIdentity());
+            action.setOperation(operation);
+            action.setTimestamp(new Date());
+            action.setSourceId(policy.getIdentifier());
+            action.setSourceName(formatPolicyName(policy));
+            action.setSourceType(Component.AccessPolicy);
+
+            if (actionDetails != null) {
+                action.setActionDetails(actionDetails);
+            }
+        }
+
+        return action;
+    }
+
+    /**
+     * Formats the name of the specified policy.
+     *
+     * @param policy policy
+     * @return formatted name
+     */
+    private String formatPolicyName(final AccessPolicy policy) {
+        return policy.getAction().toString() + " " + policy.getResource();
+    }
+
+    /**
+     * Extracts the values for the configured properties from the specified policy.
+     */
+    private Map<String, String> extractConfiguredPropertyValues(AccessPolicy policy, AccessPolicyDTO policyDTO) {
+        Map<String, String> values = new HashMap<>();
+
+        if (policyDTO.getUsers() != null) {
+            // get each of the auto terminated relationship names
+            final List<String> currentUsers = new ArrayList<>(policy.getUsers());
+
+            // sort them and include in the configuration
+            Collections.sort(currentUsers, Collator.getInstance(Locale.US));
+            values.put(USERS, StringUtils.join(currentUsers, ", "));
+        }
+        if (policyDTO.getUserGroups() != null) {
+            // get each of the auto terminated relationship names
+            final List<String> currentUserGroups = new ArrayList<>(policy.getGroups());
+
+            // sort them and include in the configuration
+            Collections.sort(currentUserGroups, Collator.getInstance(Locale.US));
+            values.put(USER_GROUPS, StringUtils.join(currentUserGroups, ", "));
+        }
+
+        return values;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserAuditor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserAuditor.java
new file mode 100644
index 0000000..843184d
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserAuditor.java
@@ -0,0 +1,245 @@
+/*
+ * 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.audit;
+
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.authorization.User;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.web.api.dto.UserDTO;
+import org.apache.nifi.web.dao.UserDAO;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Audits user creation/removal and configuration changes.
+ */
+@Aspect
+public class UserAuditor extends NiFiAuditor {
+
+    private static final Logger logger = LoggerFactory.getLogger(UserAuditor.class);
+
+    private static final String IDENTITY = "Identity";
+
+    /**
+     * Audits the creation of policies via createUser().
+     *
+     * This method only needs to be run 'after returning'. However, in Java 7 the order in which these methods are returned from Class.getDeclaredMethods (even though there is no order guaranteed)
+     * seems to differ from Java 6. SpringAOP depends on this ordering to determine advice precedence. By normalizing all advice into Around advice we can alleviate this issue.
+     *
+     * @param proceedingJoinPoint join point
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+            + "execution(org.apache.nifi.authorization.User createUser(org.apache.nifi.web.api.dto.UserDTO))")
+    public User createUserAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+        // create the access user
+        User user = (User) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the user action...
+        final Action action = generateAuditRecord(user, Operation.Add);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return user;
+    }
+
+    /**
+     * Audits the configuration of a single user.
+     *
+     * @param proceedingJoinPoint join point
+     * @param userDTO dto
+     * @param userDAO dao
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+            + "execution(org.apache.nifi.authorization.User updateUser(org.apache.nifi.web.api.dto.UserDTO)) && "
+            + "args(userDTO) && "
+            + "target(userDAO)")
+    public User updateUserAdvice(ProceedingJoinPoint proceedingJoinPoint, UserDTO userDTO, UserDAO userDAO) throws Throwable {
+        // determine the initial values for each property/setting thats changing
+        User user = userDAO.getUser(userDTO.getId());
+        final Map<String, String> values = extractConfiguredPropertyValues(user, userDTO);
+
+        // update the user state
+        final User updatedUser = (User) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the user action...
+        // get the updated verbose state
+        user = userDAO.getUser(updatedUser.getIdentifier());
+
+        // get the current user
+        NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (niFiUser != null) {
+            // determine the updated values
+            Map<String, String> updatedValues = extractConfiguredPropertyValues(user, userDTO);
+
+            // create a user action
+            Date actionTimestamp = new Date();
+            Collection<Action> actions = new ArrayList<>();
+
+            // go through each updated value
+            for (String property : updatedValues.keySet()) {
+                String newValue = updatedValues.get(property);
+                String oldValue = values.get(property);
+                Operation operation = null;
+
+                // determine the type of operation
+                if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
+                    operation = Operation.Configure;
+                }
+
+                // create a configuration action accordingly
+                if (operation != null) {
+                    final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+                    actionDetails.setName(property);
+                    actionDetails.setValue(newValue);
+                    actionDetails.setPreviousValue(oldValue);
+
+                    // create a configuration action
+                    FlowChangeAction configurationAction = new FlowChangeAction();
+                    configurationAction.setUserIdentity(niFiUser.getIdentity());
+                    configurationAction.setOperation(operation);
+                    configurationAction.setTimestamp(actionTimestamp);
+                    configurationAction.setSourceId(user.getIdentifier());
+                    configurationAction.setSourceName(user.getIdentity());
+                    configurationAction.setSourceType(Component.User);
+                    configurationAction.setActionDetails(actionDetails);
+                    actions.add(configurationAction);
+                }
+            }
+
+            // ensure there are actions to record
+            if (!actions.isEmpty()) {
+                // save the actions
+                saveActions(actions, logger);
+            }
+        }
+
+        return updatedUser;
+    }
+
+    /**
+     * Audits the removal of a user via deleteUser().
+     *
+     * @param proceedingJoinPoint join point
+     * @param userId user id
+     * @param userDAO dao
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserDAO+) && "
+            + "execution(org.apache.nifi.authorization.User deleteUser(java.lang.String)) && "
+            + "args(userId) && "
+            + "target(userDAO)")
+    public User removeUserAdvice(ProceedingJoinPoint proceedingJoinPoint, String userId, UserDAO userDAO) throws Throwable {
+        // get the user before removing it
+        User user = userDAO.getUser(userId);
+
+        // remove the user
+        final User removedUser = (User) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add removal actions...
+        // audit the user removal
+        final Action action = generateAuditRecord(user, Operation.Remove);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return removedUser;
+    }
+
+    /**
+     * Generates an audit record for the creation of a user.
+     *
+     * @param user user
+     * @param operation operation
+     * @return action
+     */
+    public Action generateAuditRecord(User user, Operation operation) {
+        return generateAuditRecord(user, operation, null);
+    }
+
+    /**
+     * Generates an audit record for the creation of a user.
+     *
+     * @param user user
+     * @param operation operation
+     * @param actionDetails details
+     * @return action
+     */
+    public Action generateAuditRecord(User user, Operation operation, ActionDetails actionDetails) {
+        FlowChangeAction action = null;
+
+        // get the current user
+        NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (niFiUser != null) {
+            // create the user action for adding this user
+            action = new FlowChangeAction();
+            action.setUserIdentity(niFiUser.getIdentity());
+            action.setOperation(operation);
+            action.setTimestamp(new Date());
+            action.setSourceId(user.getIdentifier());
+            action.setSourceName(user.getIdentity());
+            action.setSourceType(Component.User);
+
+            if (actionDetails != null) {
+                action.setActionDetails(actionDetails);
+            }
+        }
+
+        return action;
+    }
+
+    /**
+     * Extracts the values for the configured properties from the specified user.
+     */
+    private Map<String, String> extractConfiguredPropertyValues(User user, UserDTO userDTO) {
+        Map<String, String> values = new HashMap<>();
+
+        if (userDTO.getIdentity() != null) {
+            values.put(IDENTITY, user.getIdentity());
+        }
+
+        return values;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserGroupAuditor.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserGroupAuditor.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserGroupAuditor.java
new file mode 100644
index 0000000..f039d88
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/audit/UserGroupAuditor.java
@@ -0,0 +1,259 @@
+/*
+ * 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.audit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.action.Action;
+import org.apache.nifi.action.Component;
+import org.apache.nifi.action.FlowChangeAction;
+import org.apache.nifi.action.Operation;
+import org.apache.nifi.action.details.ActionDetails;
+import org.apache.nifi.action.details.FlowChangeConfigureDetails;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.user.NiFiUser;
+import org.apache.nifi.authorization.user.NiFiUserUtils;
+import org.apache.nifi.web.api.dto.UserGroupDTO;
+import org.apache.nifi.web.dao.UserGroupDAO;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Audits user creation/removal and configuration changes.
+ */
+@Aspect
+public class UserGroupAuditor extends NiFiAuditor {
+
+    private static final Logger logger = LoggerFactory.getLogger(UserGroupAuditor.class);
+
+    private static final String NAME = "Name";
+    private static final String USERS = "Users";
+
+    /**
+     * Audits the creation of policies via createUser().
+     *
+     * This method only needs to be run 'after returning'. However, in Java 7 the order in which these methods are returned from Class.getDeclaredMethods (even though there is no order guaranteed)
+     * seems to differ from Java 6. SpringAOP depends on this ordering to determine advice precedence. By normalizing all advice into Around advice we can alleviate this issue.
+     *
+     * @param proceedingJoinPoint join point
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+            + "execution(org.apache.nifi.authorization.Group createUserGroup(org.apache.nifi.web.api.dto.UserGroupDTO))")
+    public Group createUserGroupAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
+        // create the access user group
+        Group userGroup = (Group) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the user action...
+        final Action action = generateAuditRecord(userGroup, Operation.Add);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return userGroup;
+    }
+
+    /**
+     * Audits the configuration of a single user.
+     *
+     * @param proceedingJoinPoint join point
+     * @param userGroupDTO dto
+     * @param userGroupDAO dao
+     * @return node
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+            + "execution(org.apache.nifi.authorization.Group updateUserGroup(org.apache.nifi.web.api.dto.UserGroupDTO)) && "
+            + "args(userGroupDTO) && "
+            + "target(userGroupDAO)")
+    public Group updateUserAdvice(ProceedingJoinPoint proceedingJoinPoint, UserGroupDTO userGroupDTO, UserGroupDAO userGroupDAO) throws Throwable {
+        // determine the initial values for each property/setting thats changing
+        Group user = userGroupDAO.getUserGroup(userGroupDTO.getId());
+        final Map<String, String> values = extractConfiguredPropertyValues(user, userGroupDTO);
+
+        // update the user state
+        final Group updatedUserGroup = (Group) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add the user group action...
+        // get the updated verbose state
+        user = userGroupDAO.getUserGroup(updatedUserGroup.getIdentifier());
+
+        // get the current user
+        NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (niFiUser != null) {
+            // determine the updated values
+            Map<String, String> updatedValues = extractConfiguredPropertyValues(user, userGroupDTO);
+
+            // create a user action
+            Date actionTimestamp = new Date();
+            Collection<Action> actions = new ArrayList<>();
+
+            // go through each updated value
+            for (String property : updatedValues.keySet()) {
+                String newValue = updatedValues.get(property);
+                String oldValue = values.get(property);
+                Operation operation = null;
+
+                // determine the type of operation
+                if (oldValue == null || newValue == null || !newValue.equals(oldValue)) {
+                    operation = Operation.Configure;
+                }
+
+                // create a configuration action accordingly
+                if (operation != null) {
+                    final FlowChangeConfigureDetails actionDetails = new FlowChangeConfigureDetails();
+                    actionDetails.setName(property);
+                    actionDetails.setValue(newValue);
+                    actionDetails.setPreviousValue(oldValue);
+
+                    // create a configuration action
+                    FlowChangeAction configurationAction = new FlowChangeAction();
+                    configurationAction.setUserIdentity(niFiUser.getIdentity());
+                    configurationAction.setOperation(operation);
+                    configurationAction.setTimestamp(actionTimestamp);
+                    configurationAction.setSourceId(user.getIdentifier());
+                    configurationAction.setSourceName(user.getName());
+                    configurationAction.setSourceType(Component.UserGroup);
+                    configurationAction.setActionDetails(actionDetails);
+                    actions.add(configurationAction);
+                }
+            }
+
+            // ensure there are actions to record
+            if (!actions.isEmpty()) {
+                // save the actions
+                saveActions(actions, logger);
+            }
+        }
+
+        return updatedUserGroup;
+    }
+
+    /**
+     * Audits the removal of a user via deleteUser().
+     *
+     * @param proceedingJoinPoint join point
+     * @param userGroupId user id
+     * @param userGroupDAO dao
+     * @throws Throwable ex
+     */
+    @Around("within(org.apache.nifi.web.dao.UserGroupDAO+) && "
+            + "execution(org.apache.nifi.authorization.Group deleteUserGroup(java.lang.String)) && "
+            + "args(userGroupId) && "
+            + "target(userGroupDAO)")
+    public Group removeUserAdvice(ProceedingJoinPoint proceedingJoinPoint, String userGroupId, UserGroupDAO userGroupDAO) throws Throwable {
+        // get the user group before removing it
+        Group userGroup = userGroupDAO.getUserGroup(userGroupId);
+
+        // remove the user group
+        final Group removedUserGroup = (Group) proceedingJoinPoint.proceed();
+
+        // if no exceptions were thrown, add removal actions...
+        // audit the user removal
+        final Action action = generateAuditRecord(userGroup, Operation.Remove);
+
+        // save the actions
+        if (action != null) {
+            saveAction(action, logger);
+        }
+
+        return removedUserGroup;
+    }
+
+    /**
+     * Generates an audit record for the creation of a user group.
+     *
+     * @param userGroup userGroup
+     * @param operation operation
+     * @return action
+     */
+    public Action generateAuditRecord(Group userGroup, Operation operation) {
+        return generateAuditRecord(userGroup, operation, null);
+    }
+
+    /**
+     * Generates an audit record for the creation of a user group.
+     *
+     * @param userGroup userGroup
+     * @param operation operation
+     * @param actionDetails details
+     * @return action
+     */
+    public Action generateAuditRecord(Group userGroup, Operation operation, ActionDetails actionDetails) {
+        FlowChangeAction action = null;
+
+        // get the current user
+        NiFiUser niFiUser = NiFiUserUtils.getNiFiUser();
+
+        // ensure the user was found
+        if (niFiUser != null) {
+            // create the user action for adding this user
+            action = new FlowChangeAction();
+            action.setUserIdentity(niFiUser.getIdentity());
+            action.setOperation(operation);
+            action.setTimestamp(new Date());
+            action.setSourceId(userGroup.getIdentifier());
+            action.setSourceName(userGroup.getName());
+            action.setSourceType(Component.UserGroup);
+
+            if (actionDetails != null) {
+                action.setActionDetails(actionDetails);
+            }
+        }
+
+        return action;
+    }
+
+    /**
+     * Extracts the values for the configured properties from the specified user group.
+     */
+    private Map<String, String> extractConfiguredPropertyValues(Group group, UserGroupDTO userGroupDTO) {
+        Map<String, String> values = new HashMap<>();
+
+        if (userGroupDTO.getIdentity() != null) {
+            values.put(NAME, group.getName());
+        }
+        if (userGroupDTO.getUsers() != null) {
+            // get each of the auto terminated relationship names
+            final List<String> currentUsers = new ArrayList<>(group.getUsers());
+
+            // sort them and include in the configuration
+            Collections.sort(currentUsers, Collator.getInstance(Locale.US));
+            values.put(USERS, StringUtils.join(currentUsers, ", "));
+        }
+
+        return values;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/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 892718e..2988e99 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
@@ -16,28 +16,7 @@
  */
 package org.apache.nifi.web;
 
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.ListIterator;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.UUID;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-
-import javax.ws.rs.WebApplicationException;
-import javax.ws.rs.core.Response;
-
+import com.google.common.collect.Sets;
 import org.apache.nifi.action.Action;
 import org.apache.nifi.action.Component;
 import org.apache.nifi.action.FlowChangeAction;
@@ -215,7 +194,26 @@ import org.apache.nifi.web.util.SnippetUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.collect.Sets;
+import javax.ws.rs.WebApplicationException;
+import javax.ws.rs.core.Response;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
 
 /**
  * Implementation of NiFiServiceFacade that performs revision checking.
@@ -978,6 +976,20 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 controllerFacade.save();
                 logger.debug("Deletion of component {} was successful", authorizable);
 
+                try {
+                    // since the component is being deleted, also delete any relevant access policies
+                    final AccessPolicy readPolicy = accessPolicyDAO.getAccessPolicy(RequestAction.READ, authorizable);
+                    if (authorizable.getResource().getIdentifier().equals(readPolicy.getResource())) {
+                        accessPolicyDAO.deleteAccessPolicy(readPolicy.getIdentifier());
+                    }
+                    final AccessPolicy writePolicy = accessPolicyDAO.getAccessPolicy(RequestAction.WRITE, authorizable);
+                    if (authorizable.getResource().getIdentifier().equals(writePolicy.getResource())) {
+                        accessPolicyDAO.deleteAccessPolicy(writePolicy.getIdentifier());
+                    }
+                } catch (final Exception e) {
+                    logger.warn(String.format("Unable to remove access policy for %s after component removal.", authorizable.getResource().getIdentifier()), e);
+                }
+
                 return dto;
             }
         });
@@ -2650,6 +2662,13 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 case Connection:
                     authorizable = authorizableLookup.getConnection(sourceId);
                     break;
+                case AccessPolicy:
+                    authorizable = authorizableLookup.getAccessPolicyById(sourceId);
+                    break;
+                case User:
+                case UserGroup:
+                    authorizable = authorizableLookup.getTenant();
+                    break;
                 default:
                     throw new WebApplicationException(Response.serverError().entity("An unexpected type of component is the source of this action.").build());
             }

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.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/ControllerResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
index 346f5a4..7a87e6e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerResource.java
@@ -646,9 +646,7 @@ public class ControllerResource extends ApplicationResource {
             throw new IllegalArgumentException("The end date must be specified.");
         }
 
-        if (isReplicateRequest()) {
-            return replicate(HttpMethod.DELETE);
-        }
+        // Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
 
         // handle expects request (usually from the cluster manager)
         final boolean validationPhase = isValidationPhase(httpServletRequest);

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/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 8df6c17..8a17e65 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
@@ -30,7 +30,6 @@ import org.apache.nifi.authorization.AuthorizationResult;
 import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
-import org.apache.nifi.authorization.Resource;
 import org.apache.nifi.authorization.UserContextKeys;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.authorization.resource.ResourceFactory;
@@ -50,8 +49,8 @@ import org.apache.nifi.web.api.dto.AboutDTO;
 import org.apache.nifi.web.api.dto.BannerDTO;
 import org.apache.nifi.web.api.dto.BulletinBoardDTO;
 import org.apache.nifi.web.api.dto.BulletinQueryDTO;
-import org.apache.nifi.web.api.dto.ClusterSummaryDTO;
 import org.apache.nifi.web.api.dto.ClusterDTO;
+import org.apache.nifi.web.api.dto.ClusterSummaryDTO;
 import org.apache.nifi.web.api.dto.NodeDTO;
 import org.apache.nifi.web.api.dto.ProcessGroupDTO;
 import org.apache.nifi.web.api.dto.RevisionDTO;
@@ -237,30 +236,6 @@ public class FlowResource extends ApplicationResource {
         }
     }
 
-    private boolean isAuthorized(final RequestAction action, final Resource resource) {
-        final NiFiUser user = NiFiUserUtils.getNiFiUser();
-
-        final Map<String,String> userContext;
-        if (!StringUtils.isBlank(user.getClientAddress())) {
-            userContext = new HashMap<>();
-            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
-        } else {
-            userContext = null;
-        }
-
-        final AuthorizationRequest request = new AuthorizationRequest.Builder()
-                .resource(resource)
-                .identity(user.getIdentity())
-                .anonymous(user.isAnonymous())
-                .accessAttempt(false)
-                .action(action)
-                .userContext(userContext)
-                .build();
-
-        final AuthorizationResult result = authorizer.authorize(request);
-        return Result.Approved.equals(result.getResult());
-    }
-
     // ----
     // flow
     // ----
@@ -2146,9 +2121,7 @@ public class FlowResource extends ApplicationResource {
             }
         }
 
-        if (isReplicateRequest()) {
-            return replicate(HttpMethod.GET);
-        }
+        // Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
 
         // create a history query
         final HistoryQueryDTO query = new HistoryQueryDTO();
@@ -2231,9 +2204,7 @@ public class FlowResource extends ApplicationResource {
             throw new IllegalArgumentException("The action id must be specified.");
         }
 
-        if (isReplicateRequest()) {
-            return replicate(HttpMethod.GET);
-        }
+        // Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
 
         // get the specified action
         final ActionDTO action = serviceFacade.getAction(id.getInteger());
@@ -2284,9 +2255,7 @@ public class FlowResource extends ApplicationResource {
 
         authorizeFlow();
 
-        if (isReplicateRequest()) {
-            return replicate(HttpMethod.GET);
-        }
+        // Note: History requests are not replicated throughout the cluster and are instead handled by the nodes independently
 
         // create the response entity
         final ComponentHistoryEntity entity = new ComponentHistoryEntity();

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 85983ca..6e8de79 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -491,6 +491,21 @@
         <property name="auditService" ref="auditService"/>
         <property name="processGroupDAO" ref="processGroupDAO"/>
     </bean>
+    <bean id="policyAuditor" class="org.apache.nifi.audit.AccessPolicyAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
+    <bean id="userAuditor" class="org.apache.nifi.audit.UserAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
+    <bean id="userGroupAuditor" class="org.apache.nifi.audit.UserGroupAuditor">
+        <property name="serviceFacade" ref="serviceFacade"/>
+        <property name="auditService" ref="auditService"/>
+        <property name="processGroupDAO" ref="processGroupDAO"/>
+    </bean>
     
     <!-- NiFi locking -->
     <bean id="serviceFacadeLock" class="org.apache.nifi.web.NiFiServiceFacadeLock"/>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
index a15dee1..01ab413 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/navigation.jsp
@@ -102,7 +102,7 @@
                     <div class="button-spacer-small">&nbsp;</div>
                     <div id="operate-policy" class="action-button" title="Access Policies">
                         <button ng-click="appCtrl.nf.Actions['managePolicies'](appCtrl.nf.CanvasUtils.getSelection());"
-                                ng-disabled="appCtrl.nf.CanvasUtils.getSelection().size() > 1">
+                                ng-disabled="!(appCtrl.nf.CanvasUtils.getSelection().size() <= 1 && appCtrl.nf.Common.canAccessTenants())">
                             <div class="graph-control-action-icon fa fa-key"></div></button>
                     </div>
                     <div class="button-spacer-large">&nbsp;</div>

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
index 926b6f1..b0e10e9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/policy-management.jsp
@@ -28,7 +28,7 @@
         </div>
         <div id="global-policy-controls" class="hidden policy-controls">
             <div id="policy-type-list"></div>
-            <div id="controller-policy-target"></div>
+            <div id="controller-policy-target" class="hidden"></div>
             <div class="clear"></div>
         </div>
         <div id="component-policy-controls" class="hidden policy-controls">

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/upload-template-dialog.jsp
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/upload-template-dialog.jsp b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/upload-template-dialog.jsp
index 3c63209..0469885 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/upload-template-dialog.jsp
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-ui/src/main/webapp/WEB-INF/partials/canvas/upload-template-dialog.jsp
@@ -20,11 +20,12 @@
         <div id="select-template-container">
             <div id="template-browse-container">
                 <span id="select-template-label">Select Template</span>
-                <button id="select-template-button" class="fa fa-search">
+                <div id="select-template-button">
+                    <button class="fa fa-search"></button>
                     <form id="template-upload-form" enctype="multipart/form-data" method="post">
                         <input type="file" name="template" id="template-file-field"/>
                     </form>
-                </button>
+                </div>
             </div>
         </div>
         <div id="submit-template-container">

http://git-wip-us.apache.org/repos/asf/nifi/blob/aa91032c/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 dbadf00..e88e93c 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
@@ -213,29 +213,41 @@ nf.PolicyManagement = (function () {
         // policy type listing
         $('#policy-type-list').combo({
             options: [{
-                text: 'access the user interface',
-                value: 'flow'
+                text: 'view the user interface',
+                value: 'flow',
+                description: 'Allows users to view the user interface'
             }, {
                 text: 'access the controller',
-                value: 'controller'
+                value: 'controller',
+                description: 'Allows users to view/modify the controller including Reporting Tasks, Controller Services, and Nodes in the Cluster'
             }, {
                 text: 'query provenance',
-                value: 'provenance'
+                value: 'provenance',
+                description: 'Allows users to submit a Provenance Search and request Event Lineage'
             }, {
                 text: 'access all policies',
-                value: 'policies'
+                value: 'policies',
+                description: 'Allows users to view/modify the policies for all components'
+            }, {
+                text: 'access users/user groups',
+                value: 'tenants',
+                description: 'Allows users to view/modify the users and user groups'
             }, {
                 text: 'retrieve site-to-site details',
-                value: 'site-to-site'
+                value: 'site-to-site',
+                description: 'Allows other NiFi instances to retrieve Site-To-Site details of this NiFi'
             }, {
                 text: 'view system diagnostics',
-                value: 'system'
+                value: 'system',
+                description: 'Allows users to view System Diagnostics'
             }, {
                 text: 'proxy user requests',
-                value: 'proxy'
+                value: 'proxy',
+                description: 'Allows proxy machines to send requests on the behalf of others'
             }, {
                 text: 'access counters',
-                value: 'counters'
+                value: 'counters',
+                description: 'Allows users to view/modify Counters'
             }],
             select: function (option) {
                 if (initialized) {
@@ -243,7 +255,7 @@ nf.PolicyManagement = (function () {
                     $('#selected-policy-type').text(option.value);
 
                     // if the option is for a specific component
-                    if (option.value === 'controller' || option.value === 'counters' || option.value === 'policies') {
+                    if (option.value === 'controller' || option.value === 'counters' || option.value === 'policies' || option.value === 'tenants') {
                         // update the policy target and let it relaod the policy
                         $('#controller-policy-target').combo('setSelectedOption', {
                             'value': 'read'
@@ -289,26 +301,33 @@ nf.PolicyManagement = (function () {
         $('#component-policy-target').combo({
             options: [{
                 text: 'view the component',
-                value: 'read-component'
+                value: 'read-component',
+                description: 'Allows users to view component configuration details'
             }, {
                 text: 'modify the component',
-                value: 'write-component'
+                value: 'write-component',
+                description: 'Allows users to modify component configuration details'
             }, {
                 text: 'view the provenance events',
-                value: 'read-provenance-events'
+                value: 'read-provenance-events',
+                description: 'Allows users to access provenance events and content for this component'
             }, {
                 text: 'view the policies',
-                value: 'read-policies'
+                value: 'read-policies',
+                description: 'Allows users to view the list of users who can view/modify this component'
             }, {
                 text: 'modify the policies',
-                value: 'write-policies'
+                value: 'write-policies',
+                description: 'Allows users to modify the list of users who can view/modify this component'
             }, {
                 text: 'receive data via site-to-site',
                 value: 'write-receive-data',
+                description: 'Allows this port to receive data from these NiFi instances',
                 disabled: true
-            },  {
+            }, {
                 text: 'send data via site-to-site',
                 value: 'write-send-data',
+                description: 'Allows this port to send data to these NiFi instances',
                 disabled: true
             }],
             select: function (option) {
@@ -618,9 +637,6 @@ nf.PolicyManagement = (function () {
         
         // 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) {
@@ -651,33 +667,54 @@ nf.PolicyManagement = (function () {
                 url: '../nifi-api/policies/' + resourceAndAction.action + resourceAndAction.resource,
                 dataType: 'json'
             }).done(function (policyEntity) {
-                var policy = policyEntity.component;
+                // return OK so we either have access to the policy or we don't have access to an inherited policy
+
+                // update the refresh timestamp
+                $('#policy-last-refreshed').text(policyEntity.generated);
 
-                $('#policy-message').text(policy.resource);
+                // ensure appropriate actions for the loaded policy
+                if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) {
+                    var policy = policyEntity.component;
 
-                // populate the policy details
-                populatePolicy(policyEntity);
+                    $('#policy-message').text(policy.resource);
+
+                    // populate the policy details
+                    populatePolicy(policyEntity);
+                } else {
+                    // reset the policy
+                    resetPolicy();
+
+                    // show an appropriate message
+                    $('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. ');
+                    $('#new-policy-message').hide();
+                    $('#override-policy-message').show();
+                }
 
                 deferred.resolve();
             }).fail(function (xhr, status, error) {
                 if (xhr.status === 404) {
-                    // show an appropriate messate
-                    $('#policy-message').text('No policy for the specified resource. ');
+                    // reset the policy
+                    resetPolicy();
+
+                    // show an appropriate message
+                    $('#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);
+                    deferred.resolve();
+                } else if (xhr.status === 403) {
+                    // reset the policy
+                    resetPolicy();
 
-                    // populate the table with no users
-                    populateTable([], []);
+                    // show an appropriate message
+                    $('#policy-message').text('Not authorized to access the policy for the specified resource.');
+                    $('#new-policy-message').hide();
+                    $('#override-policy-message').hide();
 
                     deferred.resolve();
                 } else {
+                    resetPolicy();
+
                     deferred.reject();
                     nf.Common.handleAjaxError(xhr, status, error);
                 }
@@ -754,7 +791,23 @@ nf.PolicyManagement = (function () {
                 dataType: 'json',
                 contentType: 'application/json'
             }).done(function (policyEntity) {
-                populatePolicy(policyEntity);
+                // ensure appropriate actions for the loaded policy
+                if (policyEntity.permissions.canRead === true && policyEntity.permissions.canWrite === true) {
+                    var policy = policyEntity.component;
+
+                    $('#policy-message').text(policy.resource);
+
+                    // populate the policy details
+                    populatePolicy(policyEntity);
+                } else {
+                    // reset the policy
+                    resetPolicy();
+
+                    // show an appropriate message
+                    $('#policy-message').text('No policy for the specified resource and not authorized to access the inherited policy. ');
+                    $('#new-policy-message').hide();
+                    $('#override-policy-message').show();
+                }
             }).fail(nf.Common.handleAjaxError);
         } else {
             nf.Dialog.showOkDialog({
@@ -769,7 +822,7 @@ nf.PolicyManagement = (function () {
      */
     var showPolicy = function () {
         // show the configuration dialog
-        nf.Shell.showContent('#policy-management').done(function () {
+        nf.Shell.showContent('#policy-management').always(function () {
             reset();
         });
 
@@ -787,10 +840,27 @@ nf.PolicyManagement = (function () {
     };
 
     /**
+     * Reset the policy.
+     */
+    var resetPolicy = function () {
+        resetPolicyMessage();
+
+        // reset button state
+        $('#delete-policy-button').prop('disabled', true);
+        $('#new-policy-user-button').prop('disabled', true);
+
+        // reset the current policy
+        $('#policy-table').removeData('policy');
+
+        // populate the table with no users
+        populateTable([], []);
+    }
+
+    /**
      * Resets the policy management dialog.
      */
     var reset = function () {
-        resetPolicyMessage();
+        resetPolicy();
 
         // clear the selected policy details
         $('#selected-policy-type').text('');
@@ -800,10 +870,6 @@ nf.PolicyManagement = (function () {
         
         // 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);
     };
 
     /**
@@ -885,7 +951,7 @@ nf.PolicyManagement = (function () {
             $('#selected-policy-component-id').text(d.id);
             populateComponentResource('controller-services');
 
-            return loadPolicy().done(showPolicy);
+            return loadPolicy().always(showPolicy);
         },
 
         /**
@@ -913,7 +979,7 @@ nf.PolicyManagement = (function () {
             $('#selected-policy-component-id').text(d.id);
             populateComponentResource('reporting-tasks');
 
-            return loadPolicy().done(showPolicy);
+            return loadPolicy().always(showPolicy);
         },
 
         /**
@@ -941,7 +1007,7 @@ nf.PolicyManagement = (function () {
             $('#selected-policy-component-id').text(d.id);
             populateComponentResource('templates');
 
-            return loadPolicy().done(showPolicy);
+            return loadPolicy().always(showPolicy);
         },
 
         /**
@@ -995,7 +1061,7 @@ nf.PolicyManagement = (function () {
             // populate the initial resource
             populateComponentResource(resource);
 
-            return loadPolicy().done(showPolicy);
+            return loadPolicy().always(showPolicy);
         },
 
         /**
@@ -1021,7 +1087,7 @@ nf.PolicyManagement = (function () {
                 $('#selected-policy-action').text('read');
             }
 
-            return loadPolicy().done(showPolicy);
-        },
+            return loadPolicy().always(showPolicy);
+        }
     };
 }());
\ No newline at end of file