You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/06/09 17:55:11 UTC

[04/11] nifi git commit: NIFI-3653: - Introducing UserGroup and Policy provider interfaces. - Introducing FileUserGroupProvider and FileAccessPolicyProvider. - Refactoring FileAuthorizer to utilize the file based implementations. - Introducing the Standa

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
new file mode 100644
index 0000000..8b91f78
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileUserGroupProviderTest.java
@@ -0,0 +1,698 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.util.file.FileUtils;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class FileUserGroupProviderTest {
+
+    private static final String EMPTY_TENANTS_CONCISE =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants/>";
+
+    private static final String EMPTY_TENANTS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenants>"
+        + "</tenants>";
+
+    private static final String BAD_SCHEMA_TENANTS =
+        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+        + "<tenant>"
+        + "</tenant>";
+
+    private static final String SIMPLE_TENANTS_BY_USER =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<tenants>" +
+            "  <users>" +
+            "    <user identifier=\"user-1\" identity=\"user-1\"/>" +
+            "    <user identifier=\"user-2\" identity=\"user-2\"/>" +
+            "  </users>" +
+            "</tenants>";
+
+    private static final String TENANTS =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
+            "<tenants>" +
+            "  <groups>" +
+            "    <group identifier=\"group-1\" name=\"group-1\">" +
+            "       <user identifier=\"user-1\" />" +
+            "    </group>" +
+            "    <group identifier=\"group-2\" name=\"group-2\">" +
+            "       <user identifier=\"user-2\" />" +
+            "    </group>" +
+            "  </groups>" +
+            "  <users>" +
+            "    <user identifier=\"user-1\" identity=\"user-1\" />" +
+            "    <user identifier=\"user-2\" identity=\"user-2\" />" +
+            "  </users>" +
+            "</tenants>";
+
+    private NiFiProperties properties;
+    private FileUserGroupProvider userGroupProvider;
+    private File primaryTenants;
+    private File restoreTenants;
+
+    private AuthorizerConfigurationContext configurationContext;
+
+    @Before
+    public void setup() throws IOException {
+        // primary tenants
+        primaryTenants = new File("target/authorizations/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile());
+
+        // restore authorizations
+        restoreTenants = new File("target/restore/users.xml");
+        FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile());
+
+        properties = mock(NiFiProperties.class);
+        when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
+
+        configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
+        when(configurationContext.getProperties()).then((invocation) -> {
+            final Map<String, String> properties = new HashMap<>();
+
+            final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
+            if (tenantFile != null) {
+                properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
+            }
+
+            final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
+            if (legacyAuthFile != null) {
+                properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
+            }
+
+            int i = 1;
+            while (true) {
+                final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                final PropertyValue value = configurationContext.getProperty(key);
+                if (value == null) {
+                    break;
+                } else {
+                    properties.put(key, value.getValue());
+                }
+            }
+
+            return properties;
+        });
+
+        userGroupProvider = new FileUserGroupProvider();
+        userGroupProvider.setNiFiProperties(properties);
+        userGroupProvider.initialize(null);
+    }
+
+    @After
+    public void cleanup() throws Exception {
+        deleteFile(primaryTenants);
+        deleteFile(restoreTenants);
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        // verify all users got created correctly
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(6, users.size());
+
+        final User user1 = userGroupProvider.getUserByIdentity("user1");
+        assertNotNull(user1);
+
+        final User user2 = userGroupProvider.getUserByIdentity("user2");
+        assertNotNull(user2);
+
+        final User user3 = userGroupProvider.getUserByIdentity("user3");
+        assertNotNull(user3);
+
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        assertNotNull(user4);
+
+        final User user5 = userGroupProvider.getUserByIdentity("user5");
+        assertNotNull(user5);
+
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+        assertNotNull(user6);
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+        final Group group1 = groups.iterator().next();
+        assertEquals("group1", group1.getName());
+    }
+
+    @Test
+    public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception {
+        final Properties props = new Properties();
+        props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
+        props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
+
+        properties = getNiFiProperties(props);
+        when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
+        userGroupProvider.setNiFiProperties(properties);
+
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final User user1 = userGroupProvider.getUserByIdentity("user1");
+        assertNotNull(user1);
+
+        final User user2 = userGroupProvider.getUserByIdentity("user2");
+        assertNotNull(user2);
+
+        final User user3 = userGroupProvider.getUserByIdentity("user3");
+        assertNotNull(user3);
+
+        final User user4 = userGroupProvider.getUserByIdentity("user4");
+        assertNotNull(user4);
+
+        final User user5 = userGroupProvider.getUserByIdentity("user5");
+        assertNotNull(user5);
+
+        final User user6 = userGroupProvider.getUserByIdentity("user6");
+        assertNotNull(user6);
+
+        // verify one group got created
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+        final Group group1 = groups.iterator().next();
+        assertEquals("group1", group1.getName());
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception {
+        when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
+                .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialUsersNotProvided() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(0, users.size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenInitialUsersProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node-identity-1";
+        final String nodeIdentity2 = "node-identity-2";
+
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(3, users.size());
+
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(adminIdentity).identity(adminIdentity).build()));
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity1).identity(nodeIdentity1).build()));
+        assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity2).identity(nodeIdentity2).build()));
+    }
+
+    @Test
+    public void testOnConfiguredWhenTenantsExistAndInitialUsersProvided() throws Exception {
+        final String adminIdentity = "admin-user";
+        final String nodeIdentity1 = "node-identity-1";
+        final String nodeIdentity2 = "node-identity-2";
+
+        // despite setting initial users, they will not be loaded as the tenants file is non-empty
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
+                .thenReturn(new StandardPropertyValue(adminIdentity, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity1, null));
+        when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
+                .thenReturn(new StandardPropertyValue(nodeIdentity2, null));
+
+        writeFile(primaryTenants, SIMPLE_TENANTS_BY_USER);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+
+        assertTrue(users.contains(new User.Builder().identifier("user-1").identity("user-1").build()));
+        assertTrue(users.contains(new User.Builder().identifier("user-2").identity("user-2").build()));
+    }
+
+    @Test
+    public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getUsers().size());
+        assertEquals(0, userGroupProvider.getGroups().size());
+    }
+
+    @Test
+    public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+
+        assertEquals(primaryTenants.length(), restoreTenants.length());
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception {
+        writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWhenPrimaryTenantsDifferentThanRestore() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testOnConfiguredWithBadTenantsSchema() throws Exception {
+        writeFile(primaryTenants, BAD_SCHEMA_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+    }
+
+    @Test
+    public void testGetAllUsersGroupsPolicies() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(2, groups.size());
+
+        boolean foundGroup1 = false;
+        boolean foundGroup2 = false;
+
+        for (Group group : groups) {
+            if (group.getIdentifier().equals("group-1") && group.getName().equals("group-1")
+                    && group.getUsers().size() == 1 && group.getUsers().contains("user-1")) {
+                foundGroup1 = true;
+            } else if (group.getIdentifier().equals("group-2") && group.getName().equals("group-2")
+                    && group.getUsers().size() == 1 && group.getUsers().contains("user-2")) {
+                foundGroup2 = true;
+            }
+        }
+
+        assertTrue(foundGroup1);
+        assertTrue(foundGroup2);
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(2, users.size());
+
+        boolean foundUser1 = false;
+        boolean foundUser2 = false;
+
+        for (User user : users) {
+            if (user.getIdentifier().equals("user-1") && user.getIdentity().equals("user-1")) {
+                foundUser1 = true;
+            } else if (user.getIdentifier().equals("user-2") && user.getIdentity().equals("user-2")) {
+                foundUser2 = true;
+            }
+        }
+
+        assertTrue(foundUser1);
+        assertTrue(foundUser2);
+    }
+
+    // --------------- User Tests ------------------------
+
+    @Test
+    public void testAddUser() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-1")
+                .identity("user-identity-1")
+                .build();
+
+        final User addedUser = userGroupProvider.addUser(user);
+        assertNotNull(addedUser);
+        assertEquals(user.getIdentifier(), addedUser.getIdentifier());
+        assertEquals(user.getIdentity(), addedUser.getIdentity());
+
+        final Set<User> users = userGroupProvider.getUsers();
+        assertEquals(1, users.size());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identifier = "user-1";
+        final User user = userGroupProvider.getUser(identifier);
+        assertNotNull(user);
+        assertEquals(identifier, user.getIdentifier());
+    }
+
+    @Test
+    public void testGetUserByIdentifierWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identifier = "user-X";
+        final User user = userGroupProvider.getUser(identifier);
+        assertNull(user);
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identity = "user-1";
+        final User user = userGroupProvider.getUserByIdentity(identity);
+        assertNotNull(user);
+        assertEquals(identity, user.getIdentifier());
+    }
+
+    @Test
+    public void testGetUserByIdentityWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final String identity = "user-X";
+        final User user = userGroupProvider.getUserByIdentity(identity);
+        assertNull(user);
+    }
+
+    @Test
+    public void testDeleteUser() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        // retrieve user-1 and verify it exists
+        final User user = userGroupProvider.getUser("user-1");
+        assertEquals("user-1", user.getIdentifier());
+
+        // delete user-1
+        final User deletedUser = userGroupProvider.deleteUser(user);
+        assertNotNull(deletedUser);
+        assertEquals("user-1", deletedUser.getIdentifier());
+
+        // should be one less user
+        assertEquals(1, userGroupProvider.getUsers().size());
+        assertNull(userGroupProvider.getUser(user.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteUserWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        //user that doesn't exist
+        final User user = new User.Builder().identifier("user-X").identity("user-identity-X").build();
+
+        // should return null and still have 2 users because nothing was deleted
+        final User deletedUser = userGroupProvider.deleteUser(user);
+        assertNull(deletedUser);
+        assertEquals(2, userGroupProvider.getUsers().size());
+    }
+
+    @Test
+    public void testUpdateUserWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-1")
+                .identity("new-identity")
+                .build();
+
+        final User updatedUser = userGroupProvider.updateUser(user);
+        assertNotNull(updatedUser);
+        assertEquals(user.getIdentifier(), updatedUser.getIdentifier());
+        assertEquals(user.getIdentity(), updatedUser.getIdentity());
+    }
+
+    @Test
+    public void testUpdateUserWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getUsers().size());
+
+        final User user = new User.Builder()
+                .identifier("user-X")
+                .identity("new-identity")
+                .build();
+
+        final User updatedUser = userGroupProvider.updateUser(user);
+        assertNull(updatedUser);
+    }
+
+    // --------------- Group Tests ------------------------
+
+    @Test
+    public void testAddGroup() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-1")
+                .name("group-name-1")
+                .build();
+
+        final Group addedGroup = userGroupProvider.addGroup(group);
+        assertNotNull(addedGroup);
+        assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
+        assertEquals(group.getName(), addedGroup.getName());
+        assertEquals(0, addedGroup.getUsers().size());
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(1, groups.size());
+    }
+
+    @Test
+    public void testAddGroupWithUser() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-XXX")
+                .name("group-name-XXX")
+                .addUser("user-1")
+                .build();
+
+        final Group addedGroup = userGroupProvider.addGroup(group);
+        assertNotNull(addedGroup);
+        assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
+        assertEquals(group.getName(), addedGroup.getName());
+        assertEquals(1, addedGroup.getUsers().size());
+
+        final Set<Group> groups = userGroupProvider.getGroups();
+        assertEquals(3, groups.size());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testAddGroupWhenUserDoesNotExist() throws Exception {
+        writeFile(primaryTenants, EMPTY_TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(0, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-1")
+                .name("group-name-1")
+                .addUser("user1")
+                .build();
+
+        userGroupProvider.addGroup(group);
+    }
+
+    @Test
+    public void testGetGroupByIdentifierWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final String identifier = "group-1";
+        final Group group = userGroupProvider.getGroup(identifier);
+        assertNotNull(group);
+        assertEquals(identifier, group.getIdentifier());
+    }
+
+    @Test
+    public void testGetGroupByIdentifierWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final String identifier = "group-X";
+        final Group group = userGroupProvider.getGroup(identifier);
+        assertNull(group);
+    }
+
+    @Test
+    public void testDeleteGroupWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // retrieve group-1
+        final Group group = userGroupProvider.getGroup("group-1");
+        assertEquals("group-1", group.getIdentifier());
+
+        // delete group-1
+        final Group deletedGroup = userGroupProvider.deleteGroup(group);
+        assertNotNull(deletedGroup);
+        assertEquals("group-1", deletedGroup.getIdentifier());
+
+        // verify there is one less overall group
+        assertEquals(1, userGroupProvider.getGroups().size());
+
+        // verify we can no longer retrieve group-1 by identifier
+        assertNull(userGroupProvider.getGroup(group.getIdentifier()));
+    }
+
+    @Test
+    public void testDeleteGroupWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-id-X")
+                .name("group-name-X")
+                .build();
+
+        final Group deletedGroup = userGroupProvider.deleteGroup(group);
+        assertNull(deletedGroup);
+        assertEquals(2, userGroupProvider.getGroups().size());
+    }
+
+    @Test
+    public void testUpdateGroupWhenFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        // verify user-1 is in group-1 before the update
+        final Group groupBefore = userGroupProvider.getGroup("group-1");
+        assertEquals(1, groupBefore.getUsers().size());
+        assertTrue(groupBefore.getUsers().contains("user-1"));
+
+        final Group group = new Group.Builder()
+                .identifier("group-1")
+                .name("new-name")
+                .addUser("user-2")
+                .build();
+
+        final Group updatedGroup = userGroupProvider.updateGroup(group);
+        assertEquals(group.getIdentifier(), updatedGroup.getIdentifier());
+        assertEquals(group.getName(), updatedGroup.getName());
+
+        assertEquals(1, updatedGroup.getUsers().size());
+        assertTrue(updatedGroup.getUsers().contains("user-2"));
+    }
+
+    @Test
+    public void testUpdateGroupWhenNotFound() throws Exception {
+        writeFile(primaryTenants, TENANTS);
+        userGroupProvider.onConfigured(configurationContext);
+        assertEquals(2, userGroupProvider.getGroups().size());
+
+        final Group group = new Group.Builder()
+                .identifier("group-X")
+                .name("group-X")
+                .build();
+
+        final Group updatedGroup = userGroupProvider.updateGroup(group);
+        assertNull(updatedGroup);
+        assertEquals(2, userGroupProvider.getGroups().size());
+    }
+
+    private static void writeFile(final File file, final String content) throws Exception {
+        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
+        try (final FileOutputStream fos = new FileOutputStream(file)) {
+            fos.write(bytes);
+        }
+    }
+
+    private static boolean deleteFile(final File file) {
+        if (file.isDirectory()) {
+            FileUtils.deleteFilesInDir(file, null, null, true, true);
+        }
+        return FileUtils.deleteFile(file, null, 10);
+    }
+
+    private NiFiProperties getNiFiProperties(final Properties properties) {
+        final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
+        when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames());
+
+        when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
+            @Override
+            public String answer(InvocationOnMock invocationOnMock) throws Throwable {
+                return properties.getProperty((String)invocationOnMock.getArguments()[0]);
+            }
+        });
+        return nifiProperties;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
index 344f49c..58343e8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardAuthorizerInitializationContext.java
@@ -22,10 +22,15 @@ package org.apache.nifi.authorization;
 public class StandardAuthorizerInitializationContext implements AuthorizerInitializationContext {
 
     private final String identifier;
+    private final UserGroupProviderLookup userGroupProviderLookup;
+    private final AccessPolicyProviderLookup accessPolicyProviderLookup;
     private final AuthorizerLookup authorizerLookup;
 
-    public StandardAuthorizerInitializationContext(String identifier, AuthorizerLookup authorizerLookup) {
+    public StandardAuthorizerInitializationContext(String identifier, UserGroupProviderLookup userGroupProviderLookup,
+                                                   AccessPolicyProviderLookup accessPolicyProviderLookup, AuthorizerLookup authorizerLookup) {
         this.identifier = identifier;
+        this.userGroupProviderLookup = userGroupProviderLookup;
+        this.accessPolicyProviderLookup = accessPolicyProviderLookup;
         this.authorizerLookup = authorizerLookup;
     }
 
@@ -38,4 +43,13 @@ public class StandardAuthorizerInitializationContext implements AuthorizerInitia
         return authorizerLookup;
     }
 
+    @Override
+    public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
+        return accessPolicyProviderLookup;
+    }
+
+    @Override
+    public UserGroupProviderLookup getUserGroupProviderLookup() {
+        return userGroupProviderLookup;
+    }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
new file mode 100644
index 0000000..8e726f7
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/StandardManagedAuthorizer.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.stream.XMLOutputFactory;
+import javax.xml.stream.XMLStreamException;
+import javax.xml.stream.XMLStreamWriter;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+
+public class StandardManagedAuthorizer implements ManagedAuthorizer {
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
+
+    private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
+    private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider";
+
+    private AccessPolicyProviderLookup accessPolicyProviderLookup;
+    private AccessPolicyProvider accessPolicyProvider;
+    private UserGroupProvider userGroupProvider;
+
+    @Override
+    public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
+        accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup();
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
+        final String accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider").getValue();
+        accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey);
+
+        // ensure the desired access policy provider was found
+        if (accessPolicyProvider == null) {
+            throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey));
+        }
+
+        userGroupProvider = accessPolicyProvider.getUserGroupProvider();
+
+        // ensure the desired access policy provider has a user group provider
+        if (userGroupProvider == null) {
+            throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey));
+        }
+    }
+
+    @Override
+    public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
+        final String resourceIdentifier = request.getResource().getIdentifier();
+        final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction());
+        if (policy == null) {
+            return AuthorizationResult.resourceNotFound();
+        }
+
+        final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity());
+
+        final User user = userAndGroups.getUser();
+        if (user == null) {
+            return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity()));
+        }
+
+        final Set<Group> userGroups = userAndGroups.getGroups();
+        if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
+            return AuthorizationResult.approved();
+        }
+
+        return AuthorizationResult.denied(request.getExplanationSupplier().get());
+    }
+
+    /**
+     * Determines if the policy contains one of the user's groups.
+     *
+     * @param userGroups the set of the user's groups
+     * @param policy the policy
+     * @return true if one of the Groups in userGroups is contained in the policy
+     */
+    private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
+        if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) {
+            return false;
+        }
+
+        for (Group userGroup : userGroups) {
+            if (policy.getGroups().contains(userGroup.getIdentifier())) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("managedAuthorizations");
+
+            writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT);
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint());
+            }
+            writer.writeEndElement();
+
+            writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT);
+            if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+                writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint());
+            }
+            writer.writeEndElement();
+
+            writer.writeEndElement();
+            writer.writeEndDocument();
+            writer.flush();
+        } catch (XMLStreamException e) {
+            throw new AuthorizationAccessException("Unable to generate fingerprint", e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (XMLStreamException e) {
+                    // nothing to do here
+                }
+            }
+        }
+
+        return out.toString();
+    }
+
+    @Override
+    public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        if (StringUtils.isBlank(fingerprint)) {
+            return;
+        }
+
+        final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint);
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+            ((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint());
+        }
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) {
+            ((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint());
+        }
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
+        final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint);
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) {
+            if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
+                ((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint());
+            } else {
+                throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting.");
+            }
+        }
+
+        if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) {
+            if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
+                ((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint());
+            } else {
+                throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting.");
+            }
+        }
+    }
+
+    private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
+        final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
+
+        try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
+            final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
+            final Document document = docBuilder.parse(in);
+            final Element rootElement = document.getDocumentElement();
+
+            final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT);
+            if (accessPolicyProviderList.getLength() != 1) {
+                throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint));
+            }
+
+            final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT);
+            if (userGroupProviderList.getLength() != 1) {
+                throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint));
+            }
+
+            final Node accessPolicyProvider = accessPolicyProviderList.item(0);
+            final Node userGroupProvider = userGroupProviderList.item(0);
+            return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent());
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+        }
+    }
+
+    @Override
+    public AccessPolicyProvider getAccessPolicyProvider() {
+        return accessPolicyProvider;
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+
+    }
+
+    private static class FingerprintHolder {
+        private final String policyFingerprint;
+        private final String userGroupFingerprint;
+
+        public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) {
+            this.policyFingerprint = policyFingerprint;
+            this.userGroupFingerprint = userGroupFingerprint;
+        }
+
+        public String getPolicyFingerprint() {
+            return policyFingerprint;
+        }
+
+        public String getUserGroupFingerprint() {
+            return userGroupFingerprint;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
index 2a82795..8c1619a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/user/StandardNiFiUser.java
@@ -16,7 +16,11 @@
  */
 package org.apache.nifi.authorization.user;
 
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.Collections;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * An implementation of NiFiUser.
@@ -24,42 +28,20 @@ import java.util.Objects;
 public class StandardNiFiUser implements NiFiUser {
 
     public static final String ANONYMOUS_IDENTITY = "anonymous";
-    public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser(ANONYMOUS_IDENTITY, null, null, true);
+    public static final StandardNiFiUser ANONYMOUS = new Builder().identity(ANONYMOUS_IDENTITY).anonymous(true).build();
 
     private final String identity;
+    private final Set<String> groups;
     private final NiFiUser chain;
     private final String clientAddress;
     private final boolean isAnonymous;
 
-    public StandardNiFiUser(String identity) {
-        this(identity, null, null, false);
-    }
-
-    public StandardNiFiUser(String identity, String clientAddress) {
-        this(identity, null, clientAddress, false);
-    }
-
-    public StandardNiFiUser(String identity, NiFiUser chain) {
-        this(identity, chain, null, false);
-    }
-
-    public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
-        this(identity, chain, clientAddress, false);
-    }
-
-    /**
-     * This constructor is private as the only instance of this class which should have {@code isAnonymous} set to true is the singleton ANONYMOUS.
-     *
-     * @param identity      the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
-     * @param chain         the proxy chain that leads to this users
-     * @param clientAddress the source address of the request
-     * @param isAnonymous   true to represent the canonical "anonymous" user
-     */
-    private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
-        this.identity = identity;
-        this.chain = chain;
-        this.clientAddress = clientAddress;
-        this.isAnonymous = isAnonymous;
+    private StandardNiFiUser(final Builder builder) {
+        this.identity = builder.identity;
+        this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
+        this.chain = builder.chain;
+        this.clientAddress = builder.clientAddress;
+        this.isAnonymous = builder.isAnonymous;
     }
 
     /**
@@ -70,7 +52,7 @@ public class StandardNiFiUser implements NiFiUser {
      * @return an anonymous user instance with the identity "anonymous"
      */
     public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) {
-        return new StandardNiFiUser(ANONYMOUS_IDENTITY, chain, clientAddress, true);
+        return new Builder().identity(ANONYMOUS_IDENTITY).chain(chain).clientAddress(clientAddress).anonymous(true).build();
     }
 
     @Override
@@ -79,6 +61,11 @@ public class StandardNiFiUser implements NiFiUser {
     }
 
     @Override
+    public Set<String> getGroups() {
+        return groups;
+    }
+
+    @Override
     public NiFiUser getChain() {
         return chain;
     }
@@ -116,6 +103,87 @@ public class StandardNiFiUser implements NiFiUser {
 
     @Override
     public String toString() {
-        return String.format("identity[%s]", getIdentity());
+        final String formattedGroups;
+        if (groups == null) {
+            formattedGroups = "none";
+        } else {
+            formattedGroups = StringUtils.join(groups, ", ");
+        }
+
+        return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups);
+    }
+
+    /**
+     * Builder for a StandardNiFiUser
+     */
+    public static class Builder {
+
+        private String identity;
+        private Set<String> groups;
+        private NiFiUser chain;
+        private String clientAddress;
+        private boolean isAnonymous = false;
+
+        /**
+         * Sets the identity.
+         *
+         * @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
+         * @return the builder
+         */
+        public Builder identity(final String identity) {
+            this.identity = identity;
+            return this;
+        }
+
+        /**
+         * Sets the groups.
+         *
+         * @param groups the user groups
+         * @return the builder
+         */
+        public Builder groups(final Set<String> groups) {
+            this.groups = groups;
+            return this;
+        }
+
+        /**
+         * Sets the chain.
+         *
+         * @param chain the proxy chain that leads to this users
+         * @return the builder
+         */
+        public Builder chain(final NiFiUser chain) {
+            this.chain = chain;
+            return this;
+        }
+
+        /**
+         * Sets the client address.
+         *
+         * @param clientAddress the source address of the request
+         * @return the builder
+         */
+        public Builder clientAddress(final String clientAddress) {
+            this.clientAddress = clientAddress;
+            return this;
+        }
+
+        /**
+         * Sets whether this user is the canonical "anonymous" user
+         *
+         * @param isAnonymous true to represent the canonical "anonymous" user
+         * @return the builder
+         */
+        private Builder anonymous(final boolean isAnonymous) {
+            this.isAnonymous = isAnonymous;
+            return this;
+        }
+
+        /**
+         * @return builds a StandardNiFiUser from the current state of the builder
+         */
+        public StandardNiFiUser build() {
+            return new StandardNiFiUser(this);
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
new file mode 100644
index 0000000..a4dc27e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/util/UserGroupUtil.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization.util;
+
+import org.apache.nifi.authorization.Authorizer;
+import org.apache.nifi.authorization.Group;
+import org.apache.nifi.authorization.ManagedAuthorizer;
+import org.apache.nifi.authorization.UserAndGroups;
+import org.apache.nifi.authorization.UserGroupProvider;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class UserGroupUtil {
+
+    /**
+     * Gets the groups for the user with the specified identity. Returns null if the authorizer is not able to load user groups.
+     *
+     * @param authorizer the authorizer to load the groups from
+     * @param userIdentity the user identity
+     * @return the listing of groups for the user
+     */
+    public static Set<String> getUserGroups(final Authorizer authorizer, final String userIdentity) {
+        if (authorizer instanceof ManagedAuthorizer) {
+            final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
+            final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
+            final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity);
+            final Set<Group> userGroups = userAndGroups.getGroups();
+
+            if (userGroups == null || userGroups.isEmpty()) {
+                return Collections.EMPTY_SET;
+            } else {
+                return userAndGroups.getGroups().stream().map(group -> group.getName()).collect(Collectors.toSet());
+            }
+        } else {
+            return null;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
new file mode 100755
index 0000000..966a289
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/resources/META-INF/services/org.apache.nifi.authorization.Authorizer
@@ -0,0 +1,15 @@
+# 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.
+org.apache.nifi.authorization.StandardManagedAuthorizer

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
new file mode 100644
index 0000000..a40c6f9
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/StandardManagedAuthorizerTest.java
@@ -0,0 +1,438 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.authorization;
+
+
+import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
+import org.apache.nifi.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.authorization.exception.AuthorizerCreationException;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class StandardManagedAuthorizerTest {
+
+    private static final String EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+            + "<managedAuthorizations>"
+                + "<accessPolicyProvider></accessPolicyProvider>"
+                + "<userGroupProvider></userGroupProvider>"
+            + "</managedAuthorizations>";
+
+    private static final String NON_EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+            + "<managedAuthorizations>"
+                + "<accessPolicyProvider>"
+                    + "&lt;accessPolicies&gt;"
+                        + "&lt;policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\"&gt;"
+                            + "&lt;policyUser identifier=\"user-id-1\"&gt;&lt;/policyUser&gt;"
+                            + "&lt;policyGroup identifier=\"group-id-1\"&gt;&lt;/policyGroup&gt;"
+                        + "&lt;/policy&gt;"
+                    + "&lt;/accessPolicies&gt;"
+                + "</accessPolicyProvider>"
+                + "<userGroupProvider>"
+                    + "&lt;tenants&gt;"
+                        + "&lt;user identifier=\"user-id-1\" identity=\"user-1\"&gt;&lt;/user&gt;"
+                        + "&lt;group identifier=\"group-id-1\" name=\"group-1\"&gt;"
+                            + "&lt;groupUser identifier=\"user-id-1\"&gt;&lt;/groupUser&gt;"
+                        + "&lt;/group&gt;"
+                    + "&lt;/tenants&gt;"
+                + "</userGroupProvider>"
+            + "</managedAuthorizations>";
+
+    private static final String ACCESS_POLICY_FINGERPRINT =
+            "<accessPolicies>"
+                + "<policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\">"
+                    + "<policyUser identifier=\"user-id-1\"></policyUser>"
+                    + "<policyGroup identifier=\"group-id-1\"></policyGroup>"
+                + "</policy>"
+            + "</accessPolicies>";
+
+    private static final String TENANT_FINGERPRINT =
+            "<tenants>"
+                + "<user identifier=\"user-id-1\" identity=\"user-1\"></user>"
+                + "<group identifier=\"group-id-1\" name=\"group-1\">"
+                    + "<groupUser identifier=\"user-id-1\"></groupUser>"
+                + "</group>"
+            + "</tenants>";
+
+    private static final Resource TEST_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return "1";
+        }
+
+        @Override
+        public String getName() {
+            return "resource1";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "description1";
+        }
+    };
+
+    @Test(expected = AuthorizerCreationException.class)
+    public void testNullAccessPolicyProvider() throws Exception {
+        getStandardManagedAuthorizer(null);
+    }
+
+    @Test
+    public void testEmptyFingerPrint() throws Exception {
+        final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
+
+        final AccessPolicyProvider accessPolicyProvider = mock(AccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        Assert.assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+    }
+
+    @Test
+    public void testNonEmptyFingerPrint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getFingerprint()).thenReturn(ACCESS_POLICY_FINGERPRINT);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        Assert.assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
+    }
+
+    @Test
+    public void testInheritEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint(EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+        verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
+    }
+
+    @Test(expected = AuthorizationAccessException.class)
+    public void testInheritInvalidFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint("not a valid fingerprint");
+    }
+
+    @Test
+    public void testInheritNonEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT);
+        verify(accessPolicyProvider, times(1)).inheritFingerprint(ACCESS_POLICY_FINGERPRINT);
+    }
+
+    @Test
+    public void testCheckInheritEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability(EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
+        verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
+    }
+
+    @Test(expected = AuthorizationAccessException.class)
+    public void testCheckInheritInvalidFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability("not a valid fingerprint");
+    }
+
+    @Test
+    public void testCheckInheritNonEmptyFingerprint() throws Exception {
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        managedAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
+
+        verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT);
+        verify(accessPolicyProvider, times(1)).checkInheritability(ACCESS_POLICY_FINGERPRINT);
+    }
+
+    @Test
+    public void testAuthorizationByUser() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser(userIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testAuthorizationByGroup() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+        final String groupIdentifier = "groupIdentifier1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final Group group = new Group.Builder()
+                .identifier(groupIdentifier)
+                .name(groupIdentifier)
+                .addUser(user.getIdentifier())
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addGroup(groupIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Stream.of(group).collect(Collectors.toSet());
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testResourceNotFound() throws Exception {
+        final String userIdentity = "userIdentity1";
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertEquals(AuthorizationResult.resourceNotFound(), managedAuthorizer.authorize(request));
+    }
+
+    @Test
+    public void testUnauthorizedDueToUnknownUser() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+        final String notUser1Identity = "not userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser(userIdentifier)
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(notUser1Identity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return null;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(notUser1Identity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
+    }
+
+    @Test
+    public void testUnauthorizedDueToLackOfPermission() throws Exception {
+        final String userIdentifier = "userIdentifier1";
+        final String userIdentity = "userIdentity1";
+
+        final User user = new User.Builder()
+                .identity(userIdentity)
+                .identifier(userIdentifier)
+                .build();
+
+        final AccessPolicy policy = new AccessPolicy.Builder()
+                .identifier("1")
+                .resource(TEST_RESOURCE.getIdentifier())
+                .addUser("userIdentity2")
+                .action(RequestAction.READ)
+                .build();
+
+        final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
+        when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return Collections.EMPTY_SET;
+            }
+        });
+
+        final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
+        when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
+        when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
+
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(userIdentity)
+                .resource(TEST_RESOURCE)
+                .action(RequestAction.READ)
+                .accessAttempt(true)
+                .anonymous(false)
+                .build();
+
+        final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
+        assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
+    }
+
+    private StandardManagedAuthorizer getStandardManagedAuthorizer(final AccessPolicyProvider accessPolicyProvider) {
+        final StandardManagedAuthorizer managedAuthorizer = new StandardManagedAuthorizer();
+
+        final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
+        when(configurationContext.getProperty("Access Policy Provider")).thenReturn(new StandardPropertyValue("access-policy-provider", null));
+
+        final AccessPolicyProviderLookup accessPolicyProviderLookup = mock(AccessPolicyProviderLookup.class);
+        when(accessPolicyProviderLookup.getAccessPolicyProvider("access-policy-provider")).thenReturn(accessPolicyProvider);
+
+        final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
+        when(initializationContext.getAccessPolicyProviderLookup()).thenReturn(accessPolicyProviderLookup);
+
+        managedAuthorizer.initialize(initializationContext);
+        managedAuthorizer.onConfigured(configurationContext);
+
+        return managedAuthorizer;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
index 069bf79..003835f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/test/java/org/apache/nifi/authorization/resource/DataAuthorizableTest.java
@@ -16,14 +16,6 @@
  */
 package org.apache.nifi.authorization.resource;
 
-import static org.junit.Assert.assertEquals;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
 import org.apache.nifi.authorization.AccessDeniedException;
 import org.apache.nifi.authorization.AuthorizationRequest;
 import org.apache.nifi.authorization.AuthorizationResult;
@@ -31,11 +23,19 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
 import org.apache.nifi.authorization.Authorizer;
 import org.apache.nifi.authorization.RequestAction;
 import org.apache.nifi.authorization.user.NiFiUser;
-import org.apache.nifi.authorization.user.StandardNiFiUser;
+import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
 import org.junit.Before;
 import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
 public class DataAuthorizableTest {
 
     private static final String IDENTITY_1 = "identity-1";
@@ -83,20 +83,20 @@ public class DataAuthorizableTest {
 
     @Test(expected = AccessDeniedException.class)
     public void testAuthorizeUnauthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser("unknown");
+        final NiFiUser user = new Builder().identity("unknown").build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
     }
 
     @Test
     public void testCheckAuthorizationUnauthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser("unknown");
+        final NiFiUser user = new Builder().identity("unknown").build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
         assertEquals(Result.Denied, result.getResult());
     }
 
     @Test
     public void testAuthorizedUser() {
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        final NiFiUser user = new Builder().identity(IDENTITY_1).build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
 
         verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
@@ -109,7 +109,7 @@ public class DataAuthorizableTest {
 
     @Test
     public void testCheckAuthorizationUser() {
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
+        final NiFiUser user = new Builder().identity(IDENTITY_1).build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
 
         assertEquals(Result.Approved, result.getResult());
@@ -123,9 +123,9 @@ public class DataAuthorizableTest {
 
     @Test
     public void testAuthorizedUserChain() {
-        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
-        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
+        final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
+        final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
         testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
 
         verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class));
@@ -136,9 +136,9 @@ public class DataAuthorizableTest {
 
     @Test
     public void testCheckAuthorizationUserChain() {
-        final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
-        final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
-        final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
+        final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
+        final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
+        final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
         final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
 
         assertEquals(Result.Approved, result.getResult());

http://git-wip-us.apache.org/repos/asf/nifi/blob/4ed7511b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index bbaeb26..2fc55a4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -16,6 +16,7 @@
  */
 package org.apache.nifi.cluster.coordination.http;
 
+import org.apache.nifi.cluster.coordination.http.endpoints.AccessPolicyEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.BulletinBoardEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
@@ -60,9 +61,14 @@ import org.apache.nifi.cluster.coordination.http.endpoints.RemoteProcessGroupsEn
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskTypesEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTasksEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.SearchUsersEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.StatusHistoryEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.SystemDiagnosticsEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.endpoints.TemplatesEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger;
 import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
 import org.apache.nifi.cluster.manager.NodeResponse;
 import org.apache.nifi.stream.io.NullOutputStream;
@@ -141,6 +147,12 @@ public class StandardHttpResponseMapper implements HttpResponseMapper {
         endpointMergers.add(new FunnelEndpointMerger());
         endpointMergers.add(new FunnelsEndpointMerger());
         endpointMergers.add(new ControllerEndpointMerger());
+        endpointMergers.add(new UsersEndpointMerger());
+        endpointMergers.add(new UserEndpointMerger());
+        endpointMergers.add(new UserGroupsEndpointMerger());
+        endpointMergers.add(new UserGroupEndpointMerger());
+        endpointMergers.add(new AccessPolicyEndpointMerger());
+        endpointMergers.add(new SearchUsersEndpointMerger());
     }
 
     @Override