You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by kd...@apache.org on 2018/09/22 02:11:33 UTC

[34/51] [partial] nifi-registry git commit: NIFIREG-201 Refactoring project structure to better isolate extensions

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
new file mode 100644
index 0000000..4736fe2
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/FileUserGroupProvider.java
@@ -0,0 +1,746 @@
+/*
+ * 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.registry.security.authorization.file;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.apache.nifi.registry.security.authorization.AuthorizerConfigurationContext;
+import org.apache.nifi.registry.security.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.User;
+import org.apache.nifi.registry.security.authorization.UserAndGroups;
+import org.apache.nifi.registry.security.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.security.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.security.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.security.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Groups;
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Tenants;
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Users;
+import org.apache.nifi.registry.security.exception.SecurityProviderCreationException;
+import org.apache.nifi.registry.security.exception.SecurityProviderDestructionException;
+import org.apache.nifi.registry.util.FileUtils;
+import org.apache.nifi.registry.util.PropertyValue;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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.XMLConstants;
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.JAXBElement;
+import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
+import javax.xml.bind.Unmarshaller;
+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 javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);
+
+    private static final String TENANTS_XSD = "/tenants.xsd";
+    private static final String JAXB_TENANTS_PATH = "org.apache.nifi.registry.security.authorization.file.tenants.generated";
+
+    private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);
+
+    /**
+     * Load the JAXBContext.
+     */
+    private static JAXBContext initializeJaxbContext(final String contextPath) {
+        try {
+            return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
+            //return JAXBContext.newInstance(contextPath);
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext: " + e);
+        }
+    }
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
+
+    private static final String USER_ELEMENT = "user";
+    private static final String GROUP_USER_ELEMENT = "groupUser";
+    private static final String GROUP_ELEMENT = "group";
+    private static final String IDENTIFIER_ATTR = "identifier";
+    private static final String IDENTITY_ATTR = "identity";
+    private static final String NAME_ATTR = "name";
+
+    static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
+    static final String PROP_TENANTS_FILE = "Users File";
+    static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
+
+    private Schema usersSchema;
+    private Schema tenantsSchema;
+    private NiFiRegistryProperties properties;
+    private File tenantsFile;
+    private File restoreTenantsFile;
+    private Set<String> initialUserIdentities;
+    private List<IdentityMapping> identityMappings;
+
+    private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
+
+    @Override
+    public void initialize(UserGroupProviderInitializationContext initializationContext) throws SecurityProviderCreationException {
+        try {
+            final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
+            //usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
+        } catch (Exception e) {
+            throw new SecurityProviderCreationException(e);
+        }
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext configurationContext) throws SecurityProviderCreationException {
+        try {
+            final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
+            if (StringUtils.isBlank(tenantsPath.getValue())) {
+                throw new SecurityProviderCreationException("The users file must be specified.");
+            }
+
+            // get the tenants file and ensure it exists
+            tenantsFile = new File(tenantsPath.getValue());
+            if (!tenantsFile.exists()) {
+                logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
+                saveTenants(new Tenants());
+            }
+
+            final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
+
+            // extract the identity mappings from nifi-registry.properties if any are provided
+            identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
+            // extract any nifi identities
+            initialUserIdentities = new HashSet<>();
+            for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
+                Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
+                if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
+                    initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
+                }
+            }
+
+            load();
+
+            // if we've copied the authorizations file to a restore directory synchronize it
+            if (restoreTenantsFile != null) {
+                FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
+            }
+
+            logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
+        } catch (IOException | SecurityProviderCreationException | JAXBException | IllegalStateException | SAXException e) {
+            throw new SecurityProviderCreationException(e);
+        }
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return userGroupHolder.get().getAllUsers();
+    }
+
+    @Override
+    public synchronized User addUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+        tenants.getUsers().getUser().add(jaxbUser);
+
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getUsersById().get(user.getIdentifier());
+    }
+
+    @Override
+    public User getUser(String identifier) throws AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        return holder.getUsersById().get(identifier);
+    }
+
+    @Override
+    public synchronized User updateUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        final List<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
+
+        // fine the User that needs to be updated
+        org.apache.nifi.registry.security.authorization.file.tenants.generated.User updateUser = null;
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser : users) {
+            if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
+                updateUser = jaxbUser;
+                break;
+            }
+        }
+
+        // if user wasn't found return null, otherwise update the user and save changes
+        if (updateUser == null) {
+            return null;
+        } else {
+            updateUser.setIdentity(user.getIdentity());
+            saveAndRefreshHolder(tenants);
+
+            return userGroupHolder.get().getUsersById().get(user.getIdentifier());
+        }
+    }
+
+    @Override
+    public User getUserByIdentity(String identity) throws AuthorizationAccessException {
+        if (identity == null) {
+            return null;
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        return holder.getUsersByIdentity().get(identity);
+    }
+
+    @Override
+    public synchronized User deleteUser(User user) throws AuthorizationAccessException {
+        if (user == null) {
+            throw new IllegalArgumentException("User cannot be null");
+        }
+
+        return deleteUser(user.getIdentifier());
+    }
+
+    @Override
+    public synchronized User deleteUser(String userIdentifier) throws AuthorizationAccessException {
+        if (userIdentifier == null) {
+            throw new IllegalArgumentException("User identifier cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final User deletedUser = holder.getUsersById().get(userIdentifier);
+        if (deletedUser == null) {
+            return null;
+        }
+
+        // for each group iterate over the user references and remove the user reference if it matches the user being deleted
+        final Tenants tenants = holder.getTenants();
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
+            Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
+            while (groupUserIter.hasNext()) {
+                org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
+                if (groupUser.getIdentifier().equals(userIdentifier)) {
+                    groupUserIter.remove();
+                    break;
+                }
+            }
+        }
+
+        // remove the actual user
+        Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.User> iter = tenants.getUsers().getUser().iterator();
+        while (iter.hasNext()) {
+            org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser = iter.next();
+            if (userIdentifier.equals(jaxbUser.getIdentifier())) {
+                iter.remove();
+                break;
+            }
+        }
+
+        saveAndRefreshHolder(tenants);
+        return deletedUser;
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return userGroupHolder.get().getAllGroups();
+    }
+
+    @Override
+    public synchronized Group addGroup(Group group) throws AuthorizationAccessException {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        // create a new JAXB Group based on the incoming Group
+        final org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup =
+                new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group();
+        jaxbGroup.setIdentifier(group.getIdentifier());
+        jaxbGroup.setName(group.getName());
+
+        // add each user to the group
+        for (String groupUser : group.getUsers()) {
+            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser =
+                    new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User();
+            jaxbGroupUser.setIdentifier(groupUser);
+            jaxbGroup.getUser().add(jaxbGroupUser);
+        }
+
+        tenants.getGroups().getGroup().add(jaxbGroup);
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+        return userGroupHolder.get().getGroupsById().get(identifier);
+    }
+
+    @Override
+    public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
+        final UserGroupHolder holder = userGroupHolder.get();
+        final User user = holder.getUser(identity);
+        final Set<Group> groups = holder.getGroups(identity);
+
+        return new UserAndGroups() {
+            @Override
+            public User getUser() {
+                return user;
+            }
+
+            @Override
+            public Set<Group> getGroups() {
+                return groups;
+            }
+        };
+    }
+
+    @Override
+    public synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Tenants tenants = holder.getTenants();
+
+        // find the group that needs to be update
+        org.apache.nifi.registry.security.authorization.file.tenants.generated.Group updateGroup = null;
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
+            if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
+                updateGroup = jaxbGroup;
+                break;
+            }
+        }
+
+        // if the group wasn't found return null, otherwise update the group and save changes
+        if (updateGroup == null) {
+            return null;
+        }
+
+        // reset the list of users and add each user to the group
+        updateGroup.getUser().clear();
+        for (String groupUser : group.getUsers()) {
+            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User jaxbGroupUser =
+                    new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User();
+            jaxbGroupUser.setIdentifier(groupUser);
+            updateGroup.getUser().add(jaxbGroupUser);
+        }
+
+        updateGroup.setName(group.getName());
+        saveAndRefreshHolder(tenants);
+
+        return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
+    }
+
+    @Override
+    public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
+        if (group == null) {
+            throw new IllegalArgumentException("Group cannot be null");
+        }
+
+        return deleteGroup(group.getIdentifier());
+    }
+
+    @Override
+    public synchronized Group deleteGroup(String groupIdentifier) throws AuthorizationAccessException {
+        if (groupIdentifier == null) {
+            throw new IllegalArgumentException("Group identifier cannot be null");
+        }
+
+        final UserGroupHolder holder = userGroupHolder.get();
+        final Group deletedGroup = holder.getGroupsById().get(groupIdentifier);
+        if (deletedGroup == null) {
+            return null;
+        }
+
+        // now remove the actual group from the top-level list of groups
+        final Tenants tenants = holder.getTenants();
+        Iterator<org.apache.nifi.registry.security.authorization.file.tenants.generated.Group> iter = tenants.getGroups().getGroup().iterator();
+        while (iter.hasNext()) {
+            org.apache.nifi.registry.security.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
+            if (groupIdentifier.equals(jaxbGroup.getIdentifier())) {
+                iter.remove();
+                break;
+            }
+        }
+
+        saveAndRefreshHolder(tenants);
+        return deletedGroup;
+    }
+
+    UserGroupHolder getUserGroupHolder() {
+        return userGroupHolder.get();
+    }
+
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
+        final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint);
+        usersAndGroups.getUsers().forEach(user -> addUser(user));
+        usersAndGroups.getGroups().forEach(group -> addGroup(group));
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException {
+        try {
+            // ensure we understand the proposed fingerprint
+            parseUsersAndGroups(proposedFingerprint);
+        } catch (final AuthorizationAccessException e) {
+            throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
+        }
+
+        final UserGroupHolder usersAndGroups = userGroupHolder.get();
+
+        // ensure we are in a proper state to inherit the fingerprint
+        if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) {
+            throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty.");
+        }
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        final UserGroupHolder usersAndGroups = userGroupHolder.get();
+
+        final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers());
+        Collections.sort(users, Comparator.comparing(User::getIdentifier));
+
+        final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups());
+        Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
+
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("tenants");
+
+            for (User user : users) {
+                writeUser(writer, user);
+            }
+            for (Group group : groups) {
+                writeGroup(writer, group);
+            }
+
+            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();
+    }
+
+    private UsersAndGroups parseUsersAndGroups(final String fingerprint) {
+        final List<User> users = new ArrayList<>();
+        final List<Group> groups = new ArrayList<>();
+
+        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();
+
+            // parse all the users and add them to the current user group provider
+            NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
+            for (int i=0; i < userNodes.getLength(); i++) {
+                Node userNode = userNodes.item(i);
+                users.add(parseUser((Element) userNode));
+            }
+
+            // parse all the groups and add them to the current user group provider
+            NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
+            for (int i=0; i < groupNodes.getLength(); i++) {
+                Node groupNode = groupNodes.item(i);
+                groups.add(parseGroup((Element) groupNode));
+            }
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse fingerprint", e);
+        }
+
+        return new UsersAndGroups(users, groups);
+    }
+
+    private User parseUser(final Element element) {
+        final User.Builder builder = new User.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .identity(element.getAttribute(IDENTITY_ATTR));
+
+        return builder.build();
+    }
+
+    private Group parseGroup(final Element element) {
+        final Group.Builder builder = new Group.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .name(element.getAttribute(NAME_ATTR));
+
+        NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
+        for (int i=0; i < groupUsers.getLength(); i++) {
+            Element groupUserNode = (Element) groupUsers.item(i);
+            builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        return builder.build();
+    }
+
+    private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
+        writer.writeStartElement(USER_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
+        writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
+        writer.writeEndElement();
+    }
+
+    private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
+        List<String> users = new ArrayList<>(group.getUsers());
+        Collections.sort(users);
+
+        writer.writeStartElement(GROUP_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
+        writer.writeAttribute(NAME_ATTR, group.getName());
+
+        for (String user : users) {
+            writer.writeStartElement(GROUP_USER_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, user);
+            writer.writeEndElement();
+        }
+
+        writer.writeEndElement();
+    }
+
+    private org.apache.nifi.registry.security.authorization.file.tenants.generated.User createJAXBUser(User user) {
+        final org.apache.nifi.registry.security.authorization.file.tenants.generated.User jaxbUser =
+                new org.apache.nifi.registry.security.authorization.file.tenants.generated.User();
+        jaxbUser.setIdentifier(user.getIdentifier());
+        jaxbUser.setIdentity(user.getIdentity());
+        return jaxbUser;
+    }
+
+    /**
+     * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
+     *
+     * @throws JAXBException            Unable to reload the authorized users file
+     * @throws IllegalStateException    Unable to sync file with restore
+     * @throws SAXException             Unable to unmarshall tenants
+     */
+    private synchronized void load() throws JAXBException, IllegalStateException, SAXException {
+        final Tenants tenants = unmarshallTenants();
+        if (tenants.getUsers() == null) {
+            tenants.setUsers(new Users());
+        }
+        if (tenants.getGroups() == null) {
+            tenants.setGroups(new Groups());
+        }
+
+        final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants);
+        final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty();
+
+        if (emptyTenants) {
+
+            populateInitialUsers(tenants);
+
+            // save any changes that were made and repopulate the holder
+            saveAndRefreshHolder(tenants);
+        } else {
+            this.userGroupHolder.set(userGroupHolder);
+        }
+    }
+
+    private void saveTenants(final Tenants tenants) throws JAXBException {
+        final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
+        marshaller.setSchema(tenantsSchema);
+        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        marshaller.marshal(tenants, tenantsFile);
+    }
+
+    private Tenants unmarshallTenants() throws JAXBException {
+        final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(tenantsSchema);
+
+        final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
+        return element.getValue();
+    }
+
+    private void populateInitialUsers(final Tenants tenants) {
+        for (String initialUserIdentity : initialUserIdentities) {
+            getOrCreateUser(tenants, initialUserIdentity);
+        }
+    }
+
+    /**
+     * Finds the User with the given identity, or creates a new one and adds it to the Tenants.
+     *
+     * @param tenants the Tenants reference
+     * @param userIdentity the user identity to find or create
+     * @return the User from Tenants with the given identity, or a new instance that was added to Tenants
+     */
+    private org.apache.nifi.registry.security.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
+        if (StringUtils.isBlank(userIdentity)) {
+            return null;
+        }
+
+        org.apache.nifi.registry.security.authorization.file.tenants.generated.User foundUser = null;
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
+            if (user.getIdentity().equals(userIdentity)) {
+                foundUser = user;
+                break;
+            }
+        }
+
+        if (foundUser == null) {
+            final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity);
+            foundUser = new org.apache.nifi.registry.security.authorization.file.tenants.generated.User();
+            foundUser.setIdentifier(userIdentifier);
+            foundUser.setIdentity(userIdentity);
+            tenants.getUsers().getUser().add(foundUser);
+        }
+
+        return foundUser;
+    }
+
+    /**
+     * Finds the Group with the given name, or creates a new one and adds it to Tenants.
+     *
+     * @param tenants the Tenants reference
+     * @param groupName the name of the group to look for
+     * @return the Group from Tenants with the given name, or a new instance that was added to Tenants
+     */
+    private org.apache.nifi.registry.security.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
+        if (StringUtils.isBlank(groupName)) {
+            return null;
+        }
+
+        org.apache.nifi.registry.security.authorization.file.tenants.generated.Group foundGroup = null;
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
+            if (group.getName().equals(groupName)) {
+                foundGroup = group;
+                break;
+            }
+        }
+
+        if (foundGroup == null) {
+            final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName);
+            foundGroup = new org.apache.nifi.registry.security.authorization.file.tenants.generated.Group();
+            foundGroup.setIdentifier(newGroupIdentifier);
+            foundGroup.setName(groupName);
+            tenants.getGroups().getGroup().add(foundGroup);
+        }
+
+        return foundGroup;
+    }
+
+    /**
+     * Saves the Authorizations instance by marshalling to a file, then re-populates the
+     * in-memory data structures and sets the new holder.
+     *
+     * Synchronized to ensure only one thread writes the file at a time.
+     *
+     * @param tenants the tenants to save and populate from
+     * @throws AuthorizationAccessException if an error occurs saving the authorizations
+     */
+    private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException {
+        try {
+            saveTenants(tenants);
+
+            this.userGroupHolder.set(new UserGroupHolder(tenants));
+        } catch (JAXBException e) {
+            throw new AuthorizationAccessException("Unable to save Authorizations", e);
+        }
+    }
+
+    @Override
+    public void preDestruction() throws SecurityProviderDestructionException {
+    }
+
+    private static class UsersAndGroups {
+        final List<User> users;
+        final List<Group> groups;
+
+        public UsersAndGroups(List<User> users, List<Group> groups) {
+            this.users = users;
+            this.groups = groups;
+        }
+
+        public List<User> getUsers() {
+            return users;
+        }
+
+        public List<Group> getGroups() {
+            return groups;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
new file mode 100644
index 0000000..91e673f
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/IdentifierUtil.java
@@ -0,0 +1,35 @@
+/*
+ * 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.registry.security.authorization.file;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+
+public final class IdentifierUtil {
+
+    static String getIdentifier(final String seed) {
+        if (StringUtils.isBlank(seed)) {
+            return null;
+        }
+
+        return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
+    }
+
+    private IdentifierUtil() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/UserGroupHolder.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/UserGroupHolder.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/UserGroupHolder.java
new file mode 100644
index 0000000..9828a45
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/file/UserGroupHolder.java
@@ -0,0 +1,241 @@
+/*
+ * 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.registry.security.authorization.file;
+
+
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Groups;
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Tenants;
+import org.apache.nifi.registry.security.authorization.file.tenants.generated.Users;
+import org.apache.nifi.registry.security.authorization.Group;
+import org.apache.nifi.registry.security.authorization.User;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A holder to provide atomic access to user group data structures.
+ */
+public class UserGroupHolder {
+
+    private final Tenants tenants;
+
+    private final Set<User> allUsers;
+    private final Map<String,User> usersById;
+    private final Map<String,User> usersByIdentity;
+
+    private final Set<Group> allGroups;
+    private final Map<String,Group> groupsById;
+    private final Map<String, Set<Group>> groupsByUserIdentity;
+
+    /**
+     * Creates a new holder and populates all convenience data structures.
+     *
+     * @param tenants the current tenants instance
+     */
+    public UserGroupHolder(final Tenants tenants) {
+        this.tenants = tenants;
+
+        // load all users
+        final Users users = tenants.getUsers();
+        final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
+
+        // load all groups
+        final Groups groups = tenants.getGroups();
+        final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
+
+        // create a convenience map to retrieve a user by id
+        final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
+
+        // create a convenience map to retrieve a user by identity
+        final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
+
+        // create a convenience map to retrieve a group by id
+        final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
+
+        // create a convenience map to retrieve the groups for a user identity
+        final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
+
+        // set all the holders
+        this.allUsers = allUsers;
+        this.allGroups = allGroups;
+        this.usersById = userByIdMap;
+        this.usersByIdentity = userByIdentityMap;
+        this.groupsById = groupByIdMap;
+        this.groupsByUserIdentity = groupsByUserIdentityMap;
+    }
+
+    /**
+     * Creates a set of Users from the JAXB Users.
+     *
+     * @param users the JAXB Users
+     * @return a set of API Users matching the provided JAXB Users
+     */
+    private Set<User> createUsers(Users users) {
+        Set<User> allUsers = new HashSet<>();
+        if (users == null || users.getUser() == null) {
+            return allUsers;
+        }
+
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.User user : users.getUser()) {
+            final User.Builder builder = new User.Builder()
+                    .identity(user.getIdentity())
+                    .identifier(user.getIdentifier());
+
+            allUsers.add(builder.build());
+        }
+
+        return allUsers;
+    }
+
+    /**
+     * Creates a set of Groups from the JAXB Groups.
+     *
+     * @param groups the JAXB Groups
+     * @return a set of API Groups matching the provided JAXB Groups
+     */
+    private Set<Group> createGroups(Groups groups,
+                                    Users users) {
+        Set<Group> allGroups = new HashSet<>();
+        if (groups == null || groups.getGroup() == null) {
+            return allGroups;
+        }
+
+        for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group group : groups.getGroup()) {
+            final Group.Builder builder = new Group.Builder()
+                    .identifier(group.getIdentifier())
+                    .name(group.getName());
+
+            for (org.apache.nifi.registry.security.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
+                builder.addUser(groupUser.getIdentifier());
+            }
+
+            allGroups.add(builder.build());
+        }
+
+        return allGroups;
+    }
+
+    /**
+     * Creates a Map from user identifier to User.
+     *
+     * @param users the set of all users
+     * @return the Map from user identifier to User
+     */
+    private Map<String,User> createUserByIdMap(final Set<User> users) {
+        Map<String,User> usersMap = new HashMap<>();
+        for (User user : users) {
+            usersMap.put(user.getIdentifier(), user);
+        }
+        return usersMap;
+    }
+
+    /**
+     * Creates a Map from user identity to User.
+     *
+     * @param users the set of all users
+     * @return the Map from user identity to User
+     */
+    private Map<String,User> createUserByIdentityMap(final Set<User> users) {
+        Map<String,User> usersMap = new HashMap<>();
+        for (User user : users) {
+            usersMap.put(user.getIdentity(), user);
+        }
+        return usersMap;
+    }
+
+    /**
+     * Creates a Map from group identifier to Group.
+     *
+     * @param groups the set of all groups
+     * @return the Map from group identifier to Group
+     */
+    private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
+        Map<String,Group> groupsMap = new HashMap<>();
+        for (Group group : groups) {
+            groupsMap.put(group.getIdentifier(), group);
+        }
+        return groupsMap;
+    }
+
+    /**
+     * Creates a Map from user identity to the set of Groups for that identity.
+     *
+     * @param groups all groups
+     * @param users all users
+     * @return a Map from User identity to the set of Groups for that identity
+     */
+    private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
+        Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
+
+        for (User user : users) {
+            Set<Group> userGroups = new HashSet<>();
+            for (Group group : groups) {
+                for (String groupUser : group.getUsers()) {
+                    if (groupUser.equals(user.getIdentifier())) {
+                        userGroups.add(group);
+                    }
+                }
+            }
+
+            groupsByUserIdentity.put(user.getIdentity(), userGroups);
+        }
+
+        return groupsByUserIdentity;
+    }
+
+    public Tenants getTenants() {
+        return tenants;
+    }
+
+    public Set<User> getAllUsers() {
+        return allUsers;
+    }
+
+    public Map<String, User> getUsersById() {
+        return usersById;
+    }
+
+    public Map<String, User> getUsersByIdentity() {
+        return usersByIdentity;
+    }
+
+    public Set<Group> getAllGroups() {
+        return allGroups;
+    }
+
+    public Map<String, Group> getGroupsById() {
+        return groupsById;
+    }
+
+    public User getUser(String identity) {
+        if (identity == null) {
+            throw new IllegalArgumentException("Identity cannot be null");
+        }
+        return usersByIdentity.get(identity);
+    }
+
+    public Set<Group> getGroups(String userIdentity) {
+        if (userIdentity == null) {
+            throw new IllegalArgumentException("User Identity cannot be null");
+        }
+        return groupsByUserIdentity.get(userIdentity);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
new file mode 100644
index 0000000..d08467e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/Authorizable.java
@@ -0,0 +1,300 @@
+/*
+ * 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.registry.security.authorization.resource;
+
+import org.apache.nifi.registry.security.authorization.AuthorizationResult.Result;
+import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+import org.apache.nifi.registry.security.authorization.AuthorizationAuditor;
+import org.apache.nifi.registry.security.authorization.AuthorizationRequest;
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.Resource;
+import org.apache.nifi.registry.security.authorization.UserContextKeys;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public interface Authorizable {
+
+    /**
+     * The parent for this Authorizable. May be null.
+     *
+     * @return the parent authorizable or null
+     */
+    Authorizable getParentAuthorizable();
+
+    /**
+     * The Resource for this Authorizable.
+     *
+     * @return the resource
+     */
+    Resource getResource();
+
+    /**
+     * The originally requested resource for this Authorizable. Because policies are inherited, if a resource
+     * does not have a policy, this Authorizable may represent a parent resource and this method will return
+     * the originally requested resource.
+     *
+     * @return the originally requested resource
+     */
+    default Resource getRequestedResource() {
+        return getResource();
+    }
+
+    /**
+     * Returns whether the current user is authorized for the specified action on the specified resource. This
+     * method does not imply the user is directly attempting to access the specified resource. If the user is
+     * attempting a direct access use Authorizable.authorize().
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @return is authorized
+     */
+    default boolean isAuthorized(Authorizer authorizer, RequestAction action, NiFiUser user) {
+        return Result.Approved.equals(checkAuthorization(authorizer, action, user).getResult());
+    }
+
+    /**
+     * Returns the result of an authorization request for the specified user for the specified action on the specified
+     * resource. This method does not imply the user is directly attempting to access the specified resource. If the user is
+     * attempting a direct access use Authorizable.authorize().
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     * @return is authorized
+     */
+    default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
+        if (user == null) {
+            return AuthorizationResult.denied("Unknown user.");
+        }
+
+        final Map<String,String> userContext;
+        if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
+        final Resource resource = getResource();
+        final Resource requestedResource = getRequestedResource();
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(user.getIdentity())
+                .groups(user.getGroups())
+                .anonymous(user.isAnonymous())
+                .accessAttempt(false)
+                .action(action)
+                .resource(resource)
+                .requestedResource(requestedResource)
+                .resourceContext(resourceContext)
+                .userContext(userContext)
+                .explanationSupplier(() -> {
+                    // build the safe explanation
+                    final StringBuilder safeDescription = new StringBuilder("Unable to ");
+
+                    if (RequestAction.READ.equals(action)) {
+                        safeDescription.append("view ");
+                    } else {
+                        safeDescription.append("modify "); // covers write or delete
+                    }
+                    safeDescription.append(resource.getSafeDescription()).append(".");
+
+                    return safeDescription.toString();
+                })
+                .build();
+
+        // perform the authorization
+        final AuthorizationResult result = authorizer.authorize(request);
+
+        // verify the results
+        if (Result.ResourceNotFound.equals(result.getResult())) {
+            final Authorizable parent = getParentAuthorizable();
+            if (parent == null) {
+                return AuthorizationResult.denied("No applicable policies could be found.");
+            } else {
+                // create a custom authorizable to override the safe description but still defer to the parent authorizable
+                final Authorizable parentProxy = new Authorizable() {
+                    @Override
+                    public Authorizable getParentAuthorizable() {
+                        return parent.getParentAuthorizable();
+                    }
+
+                    @Override
+                    public Resource getRequestedResource() {
+                        return requestedResource;
+                    }
+
+                    @Override
+                    public Resource getResource() {
+                        final Resource parentResource = parent.getResource();
+                        return new Resource() {
+                            @Override
+                            public String getIdentifier() {
+                                return parentResource.getIdentifier();
+                            }
+
+                            @Override
+                            public String getName() {
+                                return parentResource.getName();
+                            }
+
+                            @Override
+                            public String getSafeDescription() {
+                                return resource.getSafeDescription();
+                            }
+                        };
+                    }
+                };
+                return parentProxy.checkAuthorization(authorizer, action, user, resourceContext);
+            }
+        } else {
+            return result;
+        }
+    }
+
+    /**
+     * Returns the result of an authorization request for the specified user for the specified action on the specified
+     * resource. This method does not imply the user is directly attempting to access the specified resource. If the user is
+     * attempting a direct access use Authorizable.authorize().
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     * @return is authorized
+     */
+    default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user) {
+        return checkAuthorization(authorizer, action, user, null);
+    }
+
+    /**
+     * Authorizes the current user for the specified action on the specified resource. This method does imply the user is
+     * directly accessing the specified resource.
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     * @param resourceContext resource context
+     */
+    default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
+        if (user == null) {
+            throw new AccessDeniedException("Unknown user.");
+        }
+
+        final Map<String,String> userContext;
+        if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) {
+            userContext = new HashMap<>();
+            userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress());
+        } else {
+            userContext = null;
+        }
+
+        final Resource resource = getResource();
+        final Resource requestedResource = getRequestedResource();
+        final AuthorizationRequest request = new AuthorizationRequest.Builder()
+                .identity(user.getIdentity())
+                .groups(user.getGroups())
+                .anonymous(user.isAnonymous())
+                .accessAttempt(true)
+                .action(action)
+                .resource(resource)
+                .requestedResource(requestedResource)
+                .resourceContext(resourceContext)
+                .userContext(userContext)
+                .explanationSupplier(() -> {
+                    // build the safe explanation
+                    final StringBuilder safeDescription = new StringBuilder("Unable to ");
+
+                    if (RequestAction.READ.equals(action)) {
+                        safeDescription.append("view ");
+                    } else {
+                        safeDescription.append("modify ");
+                    }
+                    safeDescription.append(resource.getSafeDescription()).append(".");
+
+                    return safeDescription.toString();
+                })
+                .build();
+
+        final AuthorizationResult result = authorizer.authorize(request);
+        if (Result.ResourceNotFound.equals(result.getResult())) {
+            final Authorizable parent = getParentAuthorizable();
+            if (parent == null) {
+                final AuthorizationResult failure = AuthorizationResult.denied("No applicable policies could be found.");
+
+                // audit authorization request
+                if (authorizer instanceof AuthorizationAuditor) {
+                    ((AuthorizationAuditor) authorizer).auditAccessAttempt(request, failure);
+                }
+
+                // denied
+                throw new AccessDeniedException(failure.getExplanation());
+            } else {
+                // create a custom authorizable to override the safe description but still defer to the parent authorizable
+                final Authorizable parentProxy = new Authorizable() {
+                    @Override
+                    public Authorizable getParentAuthorizable() {
+                        return parent.getParentAuthorizable();
+                    }
+
+                    @Override
+                    public Resource getRequestedResource() {
+                        return requestedResource;
+                    }
+
+                    @Override
+                    public Resource getResource() {
+                        final Resource parentResource = parent.getResource();
+                        return new Resource() {
+                            @Override
+                            public String getIdentifier() {
+                                return parentResource.getIdentifier();
+                            }
+
+                            @Override
+                            public String getName() {
+                                return parentResource.getName();
+                            }
+
+                            @Override
+                            public String getSafeDescription() {
+                                return resource.getSafeDescription();
+                            }
+                        };
+                    }
+                };
+                parentProxy.authorize(authorizer, action, user, resourceContext);
+            }
+        } else if (Result.Denied.equals(result.getResult())) {
+            throw new AccessDeniedException(result.getExplanation());
+        }
+    }
+
+    /**
+     * Authorizes the current user for the specified action on the specified resource. This method does imply the user is
+     * directly accessing the specified resource.
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     */
+    default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user) throws AccessDeniedException {
+        authorize(authorizer, action, user, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/InheritingAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/InheritingAuthorizable.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/InheritingAuthorizable.java
new file mode 100644
index 0000000..b029229
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/InheritingAuthorizable.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.nifi.registry.security.authorization.resource;
+
+import org.apache.nifi.registry.security.authorization.AuthorizationResult;
+import org.apache.nifi.registry.security.authorization.AuthorizationResult.Result;
+import org.apache.nifi.registry.security.authorization.Authorizer;
+import org.apache.nifi.registry.security.authorization.RequestAction;
+import org.apache.nifi.registry.security.authorization.exception.AccessDeniedException;
+import org.apache.nifi.registry.security.authorization.user.NiFiUser;
+
+import java.util.Map;
+
+public interface InheritingAuthorizable extends Authorizable {
+
+    /**
+     * Returns the result of an authorization request for the specified user for the specified action on the specified
+     * resource. This method does not imply the user is directly attempting to access the specified resource. If the user is
+     * attempting a direct access use Authorizable.authorize().
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     * @return is authorized
+     */
+    default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) {
+        if (user == null) {
+            throw new AccessDeniedException("Unknown user.");
+        }
+
+        final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext);
+
+        // if we're denied from the resource try inheriting
+        if (Result.Denied.equals(resourceResult.getResult()) && getParentAuthorizable() != null) {
+            return getParentAuthorizable().checkAuthorization(authorizer, action, user, resourceContext);
+        } else {
+            return resourceResult;
+        }
+    }
+
+    /**
+     * Authorizes the current user for the specified action on the specified resource. If the current user is
+     * not in the access policy for the specified resource, the parent authorizable resource will be checked, recursively
+     *
+     * @param authorizer authorizer
+     * @param action action
+     * @param user user
+     * @param resourceContext resource context
+     */
+    default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException {
+        if (user == null) {
+            throw new AccessDeniedException("Unknown user.");
+        }
+
+        try {
+            Authorizable.super.authorize(authorizer, action, user, resourceContext);
+        } catch (final AccessDeniedException resourceDenied) {
+            // if we're denied from the resource try inheriting
+            try {
+                if (getParentAuthorizable() != null) {
+                    getParentAuthorizable().authorize(authorizer, action, user, resourceContext);
+                } else {
+                    throw resourceDenied;
+                }
+            } catch (final AccessDeniedException policiesDenied) {
+                throw resourceDenied;
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java
new file mode 100644
index 0000000..c605d4a
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceFactory.java
@@ -0,0 +1,235 @@
+/*
+ * 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.registry.security.authorization.resource;
+
+import org.apache.nifi.registry.security.authorization.Resource;
+
+import java.util.Objects;
+
+public final class ResourceFactory {
+
+    private final static Resource BUCKETS_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Bucket.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Buckets";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "buckets";
+        }
+    };
+
+    private final static Resource PROXY_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Proxy.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Proxy User Requests";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "proxy requests on behalf of users";
+        }
+    };
+
+    private final static Resource TENANTS_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Tenant.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Tenants";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "users/user groups";
+        }
+    };
+
+    private final static Resource POLICIES_RESOURCE = new Resource() {
+
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Policy.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Access Policies";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "policies";
+        }
+    };
+
+    private final static Resource ACTUATOR_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Actuator.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Actuator";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "actuator";
+        }
+    };
+
+    private final static Resource SWAGGER_RESOURCE = new Resource() {
+        @Override
+        public String getIdentifier() {
+            return ResourceType.Swagger.getValue();
+        }
+
+        @Override
+        public String getName() {
+            return "Swagger";
+        }
+
+        @Override
+        public String getSafeDescription() {
+            return "swagger";
+        }
+    };
+
+    /**
+     * Gets the Resource for actuator system management endpoints.
+     *
+     * @return  The resource for actuator system management endpoints.
+     */
+    public static Resource getActuatorResource() {
+        return ACTUATOR_RESOURCE;
+    }
+
+    /**
+     * Gets the Resource for swagger UI static resources.
+     *
+     * @return  The resource for swagger UI static resources.
+     */
+    public static Resource getSwaggerResource() {
+        return SWAGGER_RESOURCE;
+    }
+
+    /**
+     * Gets the Resource for proxying a user request.
+     *
+     * @return  The resource for proxying a user request
+     */
+    public static Resource getProxyResource() {
+        return PROXY_RESOURCE;
+    }
+
+    /**
+     * Gets the Resource for accessing Tenants which includes creating, modifying, and deleting Users and UserGroups.
+     *
+     * @return The Resource for accessing Tenants
+     */
+    public static Resource getTenantsResource() {
+        return TENANTS_RESOURCE;
+    }
+
+    /**
+     * Gets the {@link Resource} for accessing access policies.
+     * @return The policies resource
+     */
+    public static Resource getPoliciesResource() {
+        return POLICIES_RESOURCE;
+    }
+
+    /**
+     * Gets the {@link Resource} for accessing buckets.
+     * @return The buckets resource
+     */
+    public static Resource getBucketsResource() {
+        return BUCKETS_RESOURCE;
+    }
+
+    /**
+     * Gets the {@link Resource} for accessing buckets.
+     * @return The buckets resource
+     */
+    public static Resource getBucketResource(String bucketIdentifier, String bucketName) {
+        return getChildResource(ResourceType.Bucket, bucketIdentifier, bucketName);
+    }
+
+    /**
+     * Get a Resource object for any object that has a base type and an identifier, ie:
+     * /buckets/{uuid}
+     *
+     * @param parentResourceType - Required, the base resource type
+     * @param childIdentifier - Required, the identity of this sub resource
+     * @param name - Optional, the name of the subresource
+     * @return A resource for this object
+     */
+    private static Resource getChildResource(final ResourceType parentResourceType, final String childIdentifier, final String name) {
+        Objects.requireNonNull(parentResourceType, "The base resource type must be specified.");
+        Objects.requireNonNull(childIdentifier, "The child identifier identifier must be specified.");
+
+        return new Resource() {
+            @Override
+            public String getIdentifier() {
+                return String.format("%s/%s", parentResourceType.getValue(), childIdentifier);
+            }
+
+            @Override
+            public String getName() {
+                return name;
+            }
+
+            @Override
+            public String getSafeDescription() {
+                final StringBuilder safeDescription = new StringBuilder();
+                switch (parentResourceType) {
+                    case Bucket:
+                        safeDescription.append("Bucket");
+                        break;
+                    default:
+                        safeDescription.append("Unknown resource type");
+                        break;
+                }
+                safeDescription.append(" with ID ");
+                safeDescription.append(childIdentifier);
+                return safeDescription.toString();
+            }
+        };
+
+    }
+
+    /**
+     * Prevent outside instantiation.
+     */
+    private ResourceFactory() {}
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java
new file mode 100644
index 0000000..0b77cd2
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/resource/ResourceType.java
@@ -0,0 +1,87 @@
+/*
+ * 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.registry.security.authorization.resource;
+
+public enum ResourceType {
+    Bucket("/buckets"),
+    Policy("/policies"),
+    Proxy("/proxy"),
+    Tenant("/tenants"),
+    Actuator("/actuator"),
+    Swagger("/swagger");
+
+    final String value;
+
+    private ResourceType(final String value) {
+        this.value = value;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public static ResourceType valueOfValue(final String rawValue) {
+        ResourceType type = null;
+
+        for (final ResourceType rt : values()) {
+            if (rt.getValue().equals(rawValue)) {
+                type = rt;
+                break;
+            }
+        }
+
+        if (type == null) {
+            throw new IllegalArgumentException("Unknown resource type value " + rawValue);
+        }
+
+        return type;
+    }
+
+    /**
+     * Map an arbitrary resource path to its base resource type. The base resource type is
+     * what the resource path starts with.
+     *
+     * The resourcePath arg is expected to be a string of the format:
+     *
+     * {ResourceTypeValue}/arbitrary/sub-resource/path
+     *
+     * For example:
+     *   /buckets -> ResourceType.Bucket
+     *   /buckets/bucketId -> ResourceType.Bucket
+     *   /policies/read/buckets -> ResourceType.Policy
+     *
+     * @param resourcePath the path component of a URI (not including the context path)
+     * @return the base resource type
+     */
+    public static ResourceType mapFullResourcePathToResourceType(final String resourcePath) {
+        if (resourcePath == null) {
+            throw new IllegalArgumentException("Resource path must not be null");
+        }
+
+        ResourceType type = null;
+
+        for (final ResourceType rt : values()) {
+            final String rtValue = rt.getValue();
+            if(resourcePath.equals(rtValue) || resourcePath.startsWith(rtValue + "/"))  {
+                type = rt;
+                break;
+            }
+        }
+
+        return type;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUser.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUser.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUser.java
new file mode 100644
index 0000000..47127b6
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUser.java
@@ -0,0 +1,52 @@
+/*
+ * 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.registry.security.authorization.user;
+
+import java.util.Set;
+
+/**
+ * A representation of a NiFi user that has logged into the application
+ */
+public interface NiFiUser {
+
+    /**
+     * @return the unique identity of this user
+     */
+    String getIdentity();
+
+    /**
+     * @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise.
+     */
+    Set<String> getGroups();
+
+    /**
+     * @return the next user in the proxied entities chain, or <code>null</code> if no more users exist in the chain.
+     */
+    NiFiUser getChain();
+
+    /**
+     * @return <code>true</code> if the user is the unauthenticated Anonymous user
+     */
+    boolean isAnonymous();
+
+    /**
+     * @return the address of the client that made the request which created this user
+     */
+    String getClientAddress();
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserDetails.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserDetails.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserDetails.java
new file mode 100644
index 0000000..ca6ea2e
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserDetails.java
@@ -0,0 +1,91 @@
+/*
+ * 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.registry.security.authorization.user;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * User details for a NiFi user.
+ */
+public class NiFiUserDetails implements UserDetails {
+
+    private final NiFiUser user;
+
+    /**
+     * Creates a new NiFiUserDetails.
+     *
+     * @param user user
+     */
+    public NiFiUserDetails(NiFiUser user) {
+        this.user = user;
+    }
+
+    /**
+     * Get the user for this UserDetails.
+     *
+     * @return user
+     */
+    public NiFiUser getNiFiUser() {
+        return user;
+    }
+
+    /**
+     * Returns the authorities that this NiFi user has.
+     *
+     * @return authorities
+     */
+    @Override
+    public Collection<? extends GrantedAuthority> getAuthorities() {
+        return Collections.EMPTY_SET;
+    }
+
+    @Override
+    public String getPassword() {
+        return StringUtils.EMPTY;
+    }
+
+    @Override
+    public String getUsername() {
+        return user.getIdentity();
+    }
+
+    @Override
+    public boolean isAccountNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isAccountNonLocked() {
+        return true;
+    }
+
+    @Override
+    public boolean isCredentialsNonExpired() {
+        return true;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/6f26290d/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserUtils.java
----------------------------------------------------------------------
diff --git a/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserUtils.java b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserUtils.java
new file mode 100644
index 0000000..b5147ea
--- /dev/null
+++ b/nifi-registry-core/nifi-registry-framework/src/main/java/org/apache/nifi/registry/security/authorization/user/NiFiUserUtils.java
@@ -0,0 +1,91 @@
+/*
+ * 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.registry.security.authorization.user;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContext;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility methods for retrieving information about the current application user.
+ *
+ */
+public final class NiFiUserUtils {
+
+    /**
+     * Returns the current NiFiUser or null if the current user is not a NiFiUser.
+     *
+     * @return user
+     */
+    public static NiFiUser getNiFiUser() {
+        NiFiUser user = null;
+
+        // obtain the principal in the current authentication
+        final SecurityContext context = SecurityContextHolder.getContext();
+        final Authentication authentication = context.getAuthentication();
+        if (authentication != null) {
+            Object principal = authentication.getPrincipal();
+            if (principal instanceof NiFiUserDetails) {
+                user = ((NiFiUserDetails) principal).getNiFiUser();
+            }
+        }
+
+        return user;
+    }
+
+    public static String getNiFiUserIdentity() {
+        // get the nifi user to extract the username
+        NiFiUser user = NiFiUserUtils.getNiFiUser();
+        if (user == null) {
+            return "unknown";
+        } else {
+            return user.getIdentity();
+        }
+    }
+
+    /**
+     * Builds the proxy chain for the specified user.
+     *
+     * @param user The current user
+     * @return The proxy chain for that user in List form
+     */
+    public static List<String> buildProxiedEntitiesChain(final NiFiUser user) {
+        // calculate the dn chain
+        final List<String> proxyChain = new ArrayList<>();
+
+        // build the dn chain
+        NiFiUser chainedUser = user;
+        while (chainedUser != null) {
+            // add the entry for this user
+            if (chainedUser.isAnonymous()) {
+                // use an empty string to represent an anonymous user in the proxy entities chain
+                proxyChain.add(StringUtils.EMPTY);
+            } else {
+                proxyChain.add(chainedUser.getIdentity());
+            }
+
+            // go to the next user in the chain
+            chainedUser = chainedUser.getChain();
+        }
+
+        return proxyChain;
+    }
+}