http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java
 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java
new file mode 100644
index 0000000..0c219d3
--- /dev/null
+++ 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/AuthorizationsHolder.java
@@ -0,0 +1,185 @@
+/*
+ * 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.authorization.file;
+
+
+import org.apache.nifi.registry.authorization.file.generated.Authorizations;
+import org.apache.nifi.registry.authorization.file.generated.Policies;
+import org.apache.nifi.registry.authorization.AccessPolicy;
+import org.apache.nifi.registry.authorization.RequestAction;
+
+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 data structures.
+ */
+public class AuthorizationsHolder {
+
+    private final Authorizations authorizations;
+
+    private final Set<AccessPolicy> allPolicies;
+    private final Map<String, Set<AccessPolicy>> policiesByResource;
+    private final Map<String, AccessPolicy> policiesById;
+
+    /**
+     * Creates a new holder and populates all convenience authorizations data 
structures.
+     *
+     * @param authorizations the current authorizations instance
+     */
+    public AuthorizationsHolder(final Authorizations authorizations) {
+        this.authorizations = authorizations;
+
+        // load all access policies
+        final Policies policies = authorizations.getPolicies();
+        final Set<AccessPolicy> allPolicies = 
Collections.unmodifiableSet(createAccessPolicies(policies));
+
+        // create a convenience map from resource id to policies
+        final Map<String, Set<AccessPolicy>> policiesByResourceMap = 
Collections.unmodifiableMap(createResourcePolicyMap(allPolicies));
+
+        // create a convenience map from policy id to policy
+        final Map<String, AccessPolicy> policiesByIdMap = 
Collections.unmodifiableMap(createPoliciesByIdMap(allPolicies));
+
+        // set all the holders
+        this.allPolicies = allPolicies;
+        this.policiesByResource = policiesByResourceMap;
+        this.policiesById = policiesByIdMap;
+    }
+
+    /**
+     * Creates AccessPolicies from the JAXB Policies.
+     *
+     * @param policies the JAXB Policies element
+     * @return a set of AccessPolicies corresponding to the provided Resources
+     */
+    private Set<AccessPolicy> 
createAccessPolicies(org.apache.nifi.registry.authorization.file.generated.Policies
 policies) {
+        Set<AccessPolicy> allPolicies = new HashSet<>();
+        if (policies == null || policies.getPolicy() == null) {
+            return allPolicies;
+        }
+
+        // load the new authorizations
+        for (final 
org.apache.nifi.registry.authorization.file.generated.Policy policy : 
policies.getPolicy()) {
+            final String policyIdentifier = policy.getIdentifier();
+            final String resourceIdentifier = policy.getResource();
+
+            // start a new builder and set the policy and resource identifiers
+            final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                    .identifier(policyIdentifier)
+                    .resource(resourceIdentifier);
+
+            // add each user identifier
+            for 
(org.apache.nifi.registry.authorization.file.generated.Policy.User user : 
policy.getUser()) {
+                builder.addUser(user.getIdentifier());
+            }
+
+            // add each group identifier
+            for 
(org.apache.nifi.registry.authorization.file.generated.Policy.Group group : 
policy.getGroup()) {
+                builder.addGroup(group.getIdentifier());
+            }
+
+            // add the appropriate request actions
+            final String authorizationCode = policy.getAction();
+            if (authorizationCode.equals(FileAccessPolicyProvider.READ_CODE)) {
+                builder.action(RequestAction.READ);
+            } else if 
(authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){
+                builder.action(RequestAction.WRITE);
+            } else {
+                throw new IllegalStateException("Unknown Policy Action: " + 
authorizationCode);
+            }
+
+            // build the policy and add it to the map
+            allPolicies.add(builder.build());
+        }
+
+        return allPolicies;
+    }
+
+    /**
+     * Creates a map from resource identifier to the set of policies for the 
given resource.
+     *
+     * @param allPolicies the set of all policies
+     * @return a map from resource identifier to policies
+     */
+    private Map<String, Set<AccessPolicy>> createResourcePolicyMap(final 
Set<AccessPolicy> allPolicies) {
+        Map<String, Set<AccessPolicy>> resourcePolicies = new HashMap<>();
+
+        for (AccessPolicy policy : allPolicies) {
+            Set<AccessPolicy> policies = 
resourcePolicies.get(policy.getResource());
+            if (policies == null) {
+                policies = new HashSet<>();
+                resourcePolicies.put(policy.getResource(), policies);
+            }
+            policies.add(policy);
+        }
+
+        return resourcePolicies;
+    }
+
+    /**
+     * Creates a Map from policy identifier to AccessPolicy.
+     *
+     * @param policies the set of all access policies
+     * @return the Map from policy identifier to AccessPolicy
+     */
+    private Map<String, AccessPolicy> createPoliciesByIdMap(final 
Set<AccessPolicy> policies) {
+        Map<String,AccessPolicy> policyMap = new HashMap<>();
+        for (AccessPolicy policy : policies) {
+            policyMap.put(policy.getIdentifier(), policy);
+        }
+        return policyMap;
+    }
+
+    public Authorizations getAuthorizations() {
+        return authorizations;
+    }
+
+    public Set<AccessPolicy> getAllPolicies() {
+        return allPolicies;
+    }
+
+    public Map<String, Set<AccessPolicy>> getPoliciesByResource() {
+        return policiesByResource;
+    }
+
+    public Map<String, AccessPolicy> getPoliciesById() {
+        return policiesById;
+    }
+
+    public AccessPolicy getAccessPolicy(final String resourceIdentifier, final 
RequestAction action) {
+        if (resourceIdentifier == null) {
+            throw new IllegalArgumentException("Resource Identifier cannot be 
null");
+        }
+
+        final Set<AccessPolicy> resourcePolicies = 
policiesByResource.get(resourceIdentifier);
+        if (resourcePolicies == null) {
+            return null;
+        }
+
+        for (AccessPolicy accessPolicy : resourcePolicies) {
+            if (accessPolicy.getAction() == action) {
+                return accessPolicy;
+            }
+        }
+
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java
 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.java
new file mode 100644
index 0000000..d17edec
--- /dev/null
+++ 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAccessPolicyProvider.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.authorization.file;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.util.PropertyValue;
+import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.authorization.file.generated.Authorizations;
+import org.apache.nifi.registry.authorization.file.generated.Policies;
+import org.apache.nifi.registry.authorization.file.generated.Policy;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.apache.nifi.registry.authorization.AccessPolicy;
+import 
org.apache.nifi.registry.authorization.AccessPolicyProviderInitializationContext;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.registry.authorization.ConfigurableAccessPolicyProvider;
+import org.apache.nifi.registry.authorization.RequestAction;
+import 
org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.authorization.User;
+import org.apache.nifi.registry.authorization.UserGroupProvider;
+import org.apache.nifi.registry.authorization.UserGroupProviderLookup;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+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.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Pattern;
+
+public class FileAccessPolicyProvider implements 
ConfigurableAccessPolicyProvider {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(FileAccessPolicyProvider.class);
+
+    private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
+    private static final String JAXB_AUTHORIZATIONS_PATH = 
"org.apache.nifi.registry.authorization.file.generated";
+
+    private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = 
initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH);
+
+    /**
+     * Load the JAXBContext.
+     */
+    private static JAXBContext initializeJaxbContext(final String contextPath) 
{
+        try {
+            return JAXBContext.newInstance(contextPath, 
FileAuthorizer.class.getClassLoader());
+        } catch (JAXBException e) {
+            throw new RuntimeException("Unable to create JAXBContext.");
+        }
+    }
+
+    private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = 
DocumentBuilderFactory.newInstance();
+    private static final XMLOutputFactory XML_OUTPUT_FACTORY = 
XMLOutputFactory.newInstance();
+
+    private static final String POLICY_ELEMENT = "policy";
+    private static final String POLICY_USER_ELEMENT = "policyUser";
+    private static final String POLICY_GROUP_ELEMENT = "policyGroup";
+    private static final String IDENTIFIER_ATTR = "identifier";
+    private static final String RESOURCE_ATTR = "resource";
+    private static final String ACTIONS_ATTR = "actions";
+
+    static final String READ_CODE = "R";
+    static final String WRITE_CODE = "W";
+    /* TODO, add DELETE_CODE */
+
+    /*  TODO - move this somewhere into nifi-registry-security-framework so it 
can be applied to any ConfigurableAccessPolicyProvider
+     *  (and also gets us away from requiring magic strings here) */
+    private static final ResourceActionPair[] INITIAL_ADMIN_ACCESS_POLICIES = {
+            new ResourceActionPair("/resources", READ_CODE),
+            new ResourceActionPair("/resources", WRITE_CODE),
+            new ResourceActionPair("/tenants", READ_CODE),
+            new ResourceActionPair("/tenants", WRITE_CODE),
+            new ResourceActionPair("/policies", READ_CODE),
+            new ResourceActionPair("/policies", WRITE_CODE),
+            new ResourceActionPair("/buckets", READ_CODE),
+            new ResourceActionPair("/buckets", WRITE_CODE),
+    };
+
+    static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity ";
+    static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
+    static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
+    static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
+    static final Pattern NODE_IDENTITY_PATTERN = 
Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+");
+
+    private Schema authorizationsSchema;
+    private NiFiRegistryProperties properties;
+    private File authorizationsFile;
+    private String initialAdminIdentity;
+    private List<IdentityMapping> identityMappings;
+
+    private UserGroupProvider userGroupProvider;
+    private UserGroupProviderLookup userGroupProviderLookup;
+    private final AtomicReference<AuthorizationsHolder> authorizationsHolder = 
new AtomicReference<>();
+
+    @Override
+    public void initialize(AccessPolicyProviderInitializationContext 
initializationContext) throws AuthorizerCreationException {
+        userGroupProviderLookup = 
initializationContext.getUserGroupProviderLookup();
+
+        try {
+            final SchemaFactory schemaFactory = 
SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            authorizationsSchema = 
schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
+//            usersSchema = 
schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
+        } catch (Exception e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+        try {
+            final PropertyValue userGroupProviderIdentifier = 
configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
+            if (!userGroupProviderIdentifier.isSet()) {
+                throw new AuthorizerCreationException("The user group provider 
must be specified.");
+            }
+
+            userGroupProvider = 
userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
+            if (userGroupProvider == null) {
+                throw new AuthorizerCreationException("Unable to locate user 
group provider with identifier " + userGroupProviderIdentifier.getValue());
+            }
+
+            final PropertyValue authorizationsPath = 
configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
+            if (StringUtils.isBlank(authorizationsPath.getValue())) {
+                throw new AuthorizerCreationException("The authorizations file 
must be specified.");
+            }
+
+            // get the authorizations file and ensure it exists
+            authorizationsFile = new File(authorizationsPath.getValue());
+            if (!authorizationsFile.exists()) {
+                logger.info("Creating new authorizations file at {}", new 
Object[] {authorizationsFile.getAbsolutePath()});
+                saveAuthorizations(new Authorizations());
+            }
+
+            // extract the identity mappings from nifi.properties if any are 
provided
+            identityMappings = 
Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
+            // get the value of the initial admin identity
+            final PropertyValue initialAdminIdentityProp = 
configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
+            initialAdminIdentity = initialAdminIdentityProp.isSet() ? 
IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), 
identityMappings) : null;
+
+//            // extract any node identities
+//            nodeIdentities = new HashSet<>();
+//            for (Map.Entry<String,String> entry : 
configurationContext.getProperties().entrySet()) {
+//                Matcher matcher = 
NODE_IDENTITY_PATTERN.matcher(entry.getKey());
+//                if (matcher.matches() && 
!StringUtils.isBlank(entry.getValue())) {
+//                    
nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), 
identityMappings));
+//                }
+//            }
+
+            // load the authorizations
+            load();
+
+            logger.info(String.format("Authorizations file loaded at %s", new 
Date().toString()));
+        } catch (AuthorizerCreationException | JAXBException | 
IllegalStateException | SAXException e) {
+            throw new AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public UserGroupProvider getUserGroupProvider() {
+        return userGroupProvider;
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws 
AuthorizationAccessException {
+        return authorizationsHolder.get().getAllPolicies();
+    }
+
+    @Override
+    public synchronized AccessPolicy addAccessPolicy(AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        // create the new JAXB Policy
+        final Policy policy = createJAXBPolicy(accessPolicy);
+
+        // add the new Policy to the top-level list of policies
+        final AuthorizationsHolder holder = authorizationsHolder.get();
+        final Authorizations authorizations = holder.getAuthorizations();
+        authorizations.getPolicies().getPolicy().add(policy);
+
+        saveAndRefreshHolder(authorizations);
+
+        return 
authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String identifier) throws 
AuthorizationAccessException {
+        if (identifier == null) {
+            return null;
+        }
+
+        final AuthorizationsHolder holder = authorizationsHolder.get();
+        return holder.getPoliciesById().get(identifier);
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(String resourceIdentifier, 
RequestAction action) throws AuthorizationAccessException {
+        return authorizationsHolder.get().getAccessPolicy(resourceIdentifier, 
action);
+    }
+
+    @Override
+    public synchronized AccessPolicy updateAccessPolicy(AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        final AuthorizationsHolder holder = this.authorizationsHolder.get();
+        final Authorizations authorizations = holder.getAuthorizations();
+
+        // try to find an existing Authorization that matches the policy id
+        Policy updatePolicy = null;
+        for (Policy policy : authorizations.getPolicies().getPolicy()) {
+            if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
+                updatePolicy = policy;
+                break;
+            }
+        }
+
+        // no matching Policy so return null
+        if (updatePolicy == null) {
+            return null;
+        }
+
+        // update the Policy, save, reload, and return
+        transferUsersAndGroups(accessPolicy, updatePolicy);
+        saveAndRefreshHolder(authorizations);
+
+        return 
this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
+    }
+
+    @Override
+    public synchronized AccessPolicy deleteAccessPolicy(AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        if (accessPolicy == null) {
+            throw new IllegalArgumentException("AccessPolicy cannot be null");
+        }
+
+        return deleteAccessPolicy(accessPolicy.getIdentifier());
+    }
+
+    @Override
+    public synchronized AccessPolicy deleteAccessPolicy(String 
accessPolicyIdentifer) throws AuthorizationAccessException {
+        if (accessPolicyIdentifer == null) {
+            throw new IllegalArgumentException("Access policy identifier 
cannot be null");
+        }
+
+        final AuthorizationsHolder holder = this.authorizationsHolder.get();
+        AccessPolicy deletedPolicy = 
holder.getPoliciesById().get(accessPolicyIdentifer);
+        if (deletedPolicy == null) {
+            return null;
+        }
+
+        // find the matching Policy and remove it
+        final Authorizations authorizations = holder.getAuthorizations();
+        Iterator<Policy> policyIter = 
authorizations.getPolicies().getPolicy().iterator();
+        while (policyIter.hasNext()) {
+            final Policy policy = policyIter.next();
+            if (policy.getIdentifier().equals(accessPolicyIdentifer)) {
+                policyIter.remove();
+                break;
+            }
+        }
+
+        saveAndRefreshHolder(authorizations);
+        return deletedPolicy;
+    }
+
+    AuthorizationsHolder getAuthorizationsHolder() {
+        return authorizationsHolder.get();
+    }
+
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiRegistryProperties properties) {
+        this.properties = properties;
+    }
+
+    @Override
+    public synchronized void inheritFingerprint(String fingerprint) throws 
AuthorizationAccessException {
+        parsePolicies(fingerprint).forEach(policy -> addAccessPolicy(policy));
+    }
+
+    @Override
+    public void checkInheritability(String proposedFingerprint) throws 
AuthorizationAccessException, UninheritableAuthorizationsException {
+        try {
+            // ensure we can understand the proposed fingerprint
+            parsePolicies(proposedFingerprint);
+        } catch (final AuthorizationAccessException e) {
+            throw new UninheritableAuthorizationsException("Unable to parse 
the proposed fingerprint: " + e);
+        }
+
+        // ensure we are in a proper state to inherit the fingerprint
+        if (!getAccessPolicies().isEmpty()) {
+            throw new UninheritableAuthorizationsException("Proposed 
fingerprint is not inheritable because the current access policies is not 
empty.");
+        }
+    }
+
+    @Override
+    public String getFingerprint() throws AuthorizationAccessException {
+        final List<AccessPolicy> policies = new 
ArrayList<>(getAccessPolicies());
+        Collections.sort(policies, 
Comparator.comparing(AccessPolicy::getIdentifier));
+
+        XMLStreamWriter writer = null;
+        final StringWriter out = new StringWriter();
+        try {
+            writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
+            writer.writeStartDocument();
+            writer.writeStartElement("accessPolicies");
+
+            for (AccessPolicy policy : policies) {
+                writePolicy(writer, policy);
+            }
+
+            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 List<AccessPolicy> parsePolicies(final String fingerprint) {
+        final List<AccessPolicy> policies = 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 policies and add them to the current access 
policy provider
+            NodeList policyNodes = 
rootElement.getElementsByTagName(POLICY_ELEMENT);
+            for (int i = 0; i < policyNodes.getLength(); i++) {
+                Node policyNode = policyNodes.item(i);
+                policies.add(parsePolicy((Element) policyNode));
+            }
+        } catch (SAXException | ParserConfigurationException | IOException e) {
+            throw new AuthorizationAccessException("Unable to parse 
fingerprint", e);
+        }
+
+        return policies;
+    }
+
+    private AccessPolicy parsePolicy(final Element element) {
+        final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                .identifier(element.getAttribute(IDENTIFIER_ATTR))
+                .resource(element.getAttribute(RESOURCE_ATTR));
+
+        final String actions = element.getAttribute(ACTIONS_ATTR);
+        if (actions.equals(RequestAction.READ.name())) {
+            builder.action(RequestAction.READ);
+        } else if (actions.equals(RequestAction.WRITE.name())) {
+            builder.action(RequestAction.WRITE);
+        } else {
+            throw new IllegalStateException("Unknown Policy Action: " + 
actions);
+        }
+
+        NodeList policyUsers = 
element.getElementsByTagName(POLICY_USER_ELEMENT);
+        for (int i=0; i < policyUsers.getLength(); i++) {
+            Element policyUserNode = (Element) policyUsers.item(i);
+            builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        NodeList policyGroups = 
element.getElementsByTagName(POLICY_GROUP_ELEMENT);
+        for (int i=0; i < policyGroups.getLength(); i++) {
+            Element policyGroupNode = (Element) policyGroups.item(i);
+            builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR));
+        }
+
+        return builder.build();
+    }
+
+    private void writePolicy(final XMLStreamWriter writer, final AccessPolicy 
policy) throws XMLStreamException {
+        // sort the users for the policy
+        List<String> policyUsers = new ArrayList<>(policy.getUsers());
+        Collections.sort(policyUsers);
+
+        // sort the groups for this policy
+        List<String> policyGroups = new ArrayList<>(policy.getGroups());
+        Collections.sort(policyGroups);
+
+        writer.writeStartElement(POLICY_ELEMENT);
+        writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier());
+        writer.writeAttribute(RESOURCE_ATTR, policy.getResource());
+        writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name());
+
+        for (String policyUser : policyUsers) {
+            writer.writeStartElement(POLICY_USER_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, policyUser);
+            writer.writeEndElement();
+        }
+
+        for (String policyGroup : policyGroups) {
+            writer.writeStartElement(POLICY_GROUP_ELEMENT);
+            writer.writeAttribute(IDENTIFIER_ATTR, policyGroup);
+            writer.writeEndElement();
+        }
+
+        writer.writeEndElement();
+    }
+
+    /**
+     * Loads the authorizations file and populates the AuthorizationsHolder, 
only called during start-up.
+     *
+     * @throws JAXBException            Unable to reload the authorized users 
file
+     */
+    private synchronized void load() throws JAXBException, SAXException {
+        // attempt to unmarshal
+        final Authorizations authorizations = unmarshallAuthorizations();
+        if (authorizations.getPolicies() == null) {
+            authorizations.setPolicies(new Policies());
+        }
+
+        final AuthorizationsHolder authorizationsHolder = new 
AuthorizationsHolder(authorizations);
+        final boolean emptyAuthorizations = 
authorizationsHolder.getAllPolicies().isEmpty();
+        final boolean hasInitialAdminIdentity = (initialAdminIdentity != null 
&& !StringUtils.isBlank(initialAdminIdentity));
+
+        // if we are starting fresh then we might need to populate an initial 
admin
+        if (emptyAuthorizations && hasInitialAdminIdentity) {
+            logger.info("Populating authorizations for Initial Admin: " + 
initialAdminIdentity);
+            populateInitialAdmin(authorizations);
+            saveAndRefreshHolder(authorizations);
+        } else {
+            this.authorizationsHolder.set(authorizationsHolder);
+        }
+    }
+
+    private void saveAuthorizations(final Authorizations authorizations) 
throws JAXBException {
+        final Marshaller marshaller = 
JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
+        marshaller.setSchema(authorizationsSchema);
+        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+        marshaller.marshal(authorizations, authorizationsFile);
+    }
+
+    private Authorizations unmarshallAuthorizations() throws JAXBException {
+        final Unmarshaller unmarshaller = 
JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
+        unmarshaller.setSchema(authorizationsSchema);
+
+        final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new 
StreamSource(authorizationsFile), Authorizations.class);
+        return element.getValue();
+    }
+
+    /**
+     *  Creates the initial admin user and sets policies managing buckets, 
users, and policies.
+     *
+     *  TODO - move this somewhere into nifi-registry-security-framework so it 
can be applied to any ConfigurableAccessPolicyProvider
+     */
+    private void populateInitialAdmin(final Authorizations authorizations) {
+        final User initialAdmin = 
userGroupProvider.getUserByIdentity(initialAdminIdentity);
+        if (initialAdmin == null) {
+            throw new AuthorizerCreationException("Unable to locate initial 
admin " + initialAdminIdentity + " to seed policies");
+        }
+
+        for (ResourceActionPair resourceAction : 
INITIAL_ADMIN_ACCESS_POLICIES) {
+            addUserToAccessPolicy(authorizations, resourceAction.resource, 
initialAdmin.getIdentifier(), resourceAction.actionCode);
+        }
+    }
+
+//    /**
+//     * Creates a user for each node and gives the nodes write permission to 
/proxy.
+//     *
+//     * @param authorizations the overall authorizations
+//     */
+//    private void populateNodes(Authorizations authorizations) {
+//        for (String nodeIdentity : nodeIdentities) {
+//            final User node = 
userGroupProvider.getUserByIdentity(nodeIdentity);
+//            if (node == null) {
+//                throw new AuthorizerCreationException("Unable to locate node 
" + nodeIdentity + " to seed policies.");
+//            }
+//
+//            // grant access to the proxy resource
+//            addUserToAccessPolicy(authorizations, 
ResourceType.Proxy.getValue(), node.getIdentifier(), WRITE_CODE);
+//
+//            // grant the user read/write access data of the root group
+//            if (rootGroupId != null) {
+//                addUserToAccessPolicy(authorizations, 
ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + 
rootGroupId, node.getIdentifier(), READ_CODE);
+//                addUserToAccessPolicy(authorizations, 
ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + 
rootGroupId, node.getIdentifier(), WRITE_CODE);
+//            }
+//        }
+//    }
+
+
+    /**
+     * Creates and adds an access policy for the given resource, identity, and 
actions to the specified authorizations.
+     *
+     * @param authorizations the Authorizations instance to add the policy to
+     * @param resource the resource for the policy
+     * @param userIdentifier the identifier for the user to add to the policy
+     * @param action the action for the policy
+     */
+    private void addUserToAccessPolicy(final Authorizations authorizations, 
final String resource, final String userIdentifier, final String action) {
+        // first try to find an existing policy for the given resource and 
action
+        Policy foundPolicy = null;
+        for (Policy policy : authorizations.getPolicies().getPolicy()) {
+            if (policy.getResource().equals(resource) && 
policy.getAction().equals(action)) {
+                foundPolicy = policy;
+                break;
+            }
+        }
+
+        if (foundPolicy == null) {
+            // if we didn't find an existing policy create a new one
+            final String uuidSeed = resource + action;
+
+            final AccessPolicy.Builder builder = new AccessPolicy.Builder()
+                    .identifierGenerateFromSeed(uuidSeed)
+                    .resource(resource)
+                    .addUser(userIdentifier);
+
+            if (action.equals(READ_CODE)) {
+                builder.action(RequestAction.READ);
+            } else if (action.equals(WRITE_CODE)) {
+                builder.action(RequestAction.WRITE);
+            } else {
+                throw new IllegalStateException("Unknown Policy Action: " + 
action);
+            }
+
+            final AccessPolicy accessPolicy = builder.build();
+            final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
+            authorizations.getPolicies().getPolicy().add(jaxbPolicy);
+        } else {
+            // otherwise add the user to the existing policy
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            foundPolicy.getUser().add(policyUser);
+        }
+    }
+
+    private Policy createJAXBPolicy(final AccessPolicy accessPolicy) {
+        final Policy policy = new Policy();
+        policy.setIdentifier(accessPolicy.getIdentifier());
+        policy.setResource(accessPolicy.getResource());
+
+        switch (accessPolicy.getAction()) {
+            case READ:
+                policy.setAction(READ_CODE);
+                break;
+            case WRITE:
+                policy.setAction(WRITE_CODE);
+                break;
+            default:
+                break;
+        }
+
+        transferUsersAndGroups(accessPolicy, policy);
+        return policy;
+    }
+
+    /**
+     * Sets the given Policy to the state of the provided AccessPolicy. Users 
and Groups will be cleared and
+     * set to match the AccessPolicy, the resource and action will be set to 
match the AccessPolicy.
+     *
+     * Does not set the identifier.
+     *
+     * @param accessPolicy the AccessPolicy to transfer state from
+     * @param policy the Policy to transfer state to
+     */
+    private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy 
policy) {
+        // add users to the policy
+        policy.getUser().clear();
+        for (String userIdentifier : accessPolicy.getUsers()) {
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            policy.getUser().add(policyUser);
+        }
+
+        // add groups to the policy
+        policy.getGroup().clear();
+        for (String groupIdentifier : accessPolicy.getGroups()) {
+            Policy.Group policyGroup = new Policy.Group();
+            policyGroup.setIdentifier(groupIdentifier);
+            policy.getGroup().add(policyGroup);
+        }
+    }
+
+    /**
+     * Adds the given user identifier to the policy if it doesn't already 
exist.
+     *
+     * @param userIdentifier a user identifier
+     * @param policy a policy to add the user to
+     */
+    private void addUserToPolicy(final String userIdentifier, final Policy 
policy) {
+        // determine if the user already exists in the policy
+        boolean userExists = false;
+        for (Policy.User policyUser : policy.getUser()) {
+            if (policyUser.getIdentifier().equals(userIdentifier)) {
+                userExists = true;
+                break;
+            }
+        }
+
+        // add the user to the policy if doesn't already exist
+        if (!userExists) {
+            Policy.User policyUser = new Policy.User();
+            policyUser.setIdentifier(userIdentifier);
+            policy.getUser().add(policyUser);
+        }
+    }
+
+    /**
+     * Adds the given group identifier to the policy if it doesn't already 
exist.
+     *
+     * @param groupIdentifier a group identifier
+     * @param policy a policy to add the user to
+     */
+    private void addGroupToPolicy(final String groupIdentifier, final Policy 
policy) {
+        // determine if the group already exists in the policy
+        boolean groupExists = false;
+        for (Policy.Group policyGroup : policy.getGroup()) {
+            if (policyGroup.getIdentifier().equals(groupIdentifier)) {
+                groupExists = true;
+                break;
+            }
+        }
+
+        // add the group to the policy if doesn't already exist
+        if (!groupExists) {
+            Policy.Group policyGroup = new Policy.Group();
+            policyGroup.setIdentifier(groupIdentifier);
+            policy.getGroup().add(policyGroup);
+        }
+    }
+
+    /**
+     * Finds the Policy matching the resource and action, or creates a new one 
and adds it to the list of policies.
+     *
+     * @param policies the policies to search through
+     * @param seedIdentity the seedIdentity to use when creating identifiers 
for new policies
+     * @param resource the resource for the policy
+     * @param action the action string for the police (R or RW)
+     * @return the matching policy or a new policy
+     */
+    private Policy getOrCreatePolicy(final List<Policy> policies, final String 
seedIdentity, final String resource, final String action) {
+        Policy foundPolicy = null;
+
+        // try to find a policy with the same resource and actions
+        for (Policy policy : policies) {
+            if (policy.getResource().equals(resource) && 
policy.getAction().equals(action)) {
+                foundPolicy = policy;
+                break;
+            }
+        }
+
+        // if a matching policy wasn't found then create one
+        if (foundPolicy == null) {
+            final String uuidSeed = resource + action + seedIdentity;
+            final String policyIdentifier = 
IdentifierUtil.getIdentifier(uuidSeed);
+
+            foundPolicy = new Policy();
+            foundPolicy.setIdentifier(policyIdentifier);
+            foundPolicy.setResource(resource);
+            foundPolicy.setAction(action);
+
+            policies.add(foundPolicy);
+        }
+
+        return foundPolicy;
+    }
+
+    /**
+     * 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 authorizations the authorizations to save and populate from
+     * @throws AuthorizationAccessException if an error occurs saving the 
authorizations
+     */
+    private synchronized void saveAndRefreshHolder(final Authorizations 
authorizations) throws AuthorizationAccessException {
+        try {
+            saveAuthorizations(authorizations);
+
+            this.authorizationsHolder.set(new 
AuthorizationsHolder(authorizations));
+        } catch (JAXBException e) {
+            throw new AuthorizationAccessException("Unable to save 
Authorizations", e);
+        }
+    }
+
+    @Override
+    public void preDestruction() throws AuthorizerDestructionException {
+    }
+
+    private static class ResourceActionPair {
+        public String resource;
+        public String actionCode;
+        public ResourceActionPair(String resource, String actionCode) {
+            this.resource = resource;
+            this.actionCode = actionCode;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAuthorizer.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAuthorizer.java
 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAuthorizer.java
new file mode 100644
index 0000000..ef273d9
--- /dev/null
+++ 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileAuthorizer.java
@@ -0,0 +1,288 @@
+/*
+ * 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.authorization.file;
+
+import 
org.apache.nifi.registry.authorization.StandardAuthorizerConfigurationContext;
+import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.authorization.AbstractPolicyBasedAuthorizer;
+import org.apache.nifi.registry.authorization.AccessPolicy;
+import 
org.apache.nifi.registry.authorization.AccessPolicyProviderInitializationContext;
+import org.apache.nifi.registry.authorization.AccessPolicyProviderLookup;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
+import org.apache.nifi.registry.authorization.AuthorizerInitializationContext;
+import org.apache.nifi.registry.authorization.Group;
+import org.apache.nifi.registry.authorization.RequestAction;
+import org.apache.nifi.registry.authorization.User;
+import 
org.apache.nifi.registry.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.authorization.UserGroupProviderLookup;
+import org.apache.nifi.registry.authorization.UsersAndAccessPolicies;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+
+/**
+ * Provides authorizes requests to resources using policies persisted in a 
file.
+ */
+public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
+
+    private static final Logger logger = 
LoggerFactory.getLogger(FileAuthorizer.class);
+
+    private static final String FILE_USER_GROUP_PROVIDER_ID = 
"file-user-group-provider";
+    private static final String FILE_ACCESS_POLICY_PROVIDER_ID = 
"file-access-policy-provider";
+
+    static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized 
Users File";
+
+    private FileUserGroupProvider userGroupProvider = new 
FileUserGroupProvider();
+    private FileAccessPolicyProvider accessPolicyProvider = new 
FileAccessPolicyProvider();
+
+    @Override
+    public void initialize(final AuthorizerInitializationContext 
initializationContext) throws AuthorizerCreationException {
+        // initialize the user group provider
+        userGroupProvider.initialize(new 
UserGroupProviderInitializationContext() {
+            @Override
+            public String getIdentifier() {
+                return FILE_USER_GROUP_PROVIDER_ID;
+            }
+
+            @Override
+            public UserGroupProviderLookup getUserGroupProviderLookup() {
+                return (identifier) -> null;
+            }
+        });
+
+        // initialize the access policy provider
+        accessPolicyProvider.initialize(new 
AccessPolicyProviderInitializationContext() {
+            @Override
+            public String getIdentifier() {
+                return FILE_ACCESS_POLICY_PROVIDER_ID;
+            }
+
+            @Override
+            public UserGroupProviderLookup getUserGroupProviderLookup() {
+                return (identifier) -> {
+                    if (FILE_USER_GROUP_PROVIDER_ID.equals(identifier)) {
+                        return userGroupProvider;
+                    }
+
+                    return null;
+                };
+            }
+
+            @Override
+            public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
+                return (identifier) ->  null;
+            }
+        });
+    }
+
+    @Override
+    public void doOnConfigured(final AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+        final Map<String, String> configurationProperties = 
configurationContext.getProperties();
+
+        // relay the relevant config
+        final Map<String, String> userGroupProperties = new HashMap<>();
+        if 
(configurationProperties.containsKey(FileUserGroupProvider.PROP_TENANTS_FILE)) {
+            userGroupProperties.put(FileUserGroupProvider.PROP_TENANTS_FILE, 
configurationProperties.get(FileUserGroupProvider.PROP_TENANTS_FILE));
+        }
+        if 
(configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))
 {
+            
userGroupProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, 
configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
+        }
+
+        // relay the relevant config
+        final Map<String, String> accessPolicyProperties = new HashMap<>();
+        
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_USER_GROUP_PROVIDER, 
FILE_USER_GROUP_PROVIDER_ID);
+        if 
(configurationProperties.containsKey(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))
 {
+            
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, 
configurationProperties.get(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE));
+        }
+        if 
(configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))
 {
+            
accessPolicyProperties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY,
 
configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+        }
+        if 
(configurationProperties.containsKey(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))
 {
+            
accessPolicyProperties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, 
configurationProperties.get(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE));
+        }
+
+        // ensure all node identities are seeded into the user provider
+        configurationProperties.forEach((property, value) -> {
+            final Matcher matcher = 
FileAccessPolicyProvider.NODE_IDENTITY_PATTERN.matcher(property);
+            if (matcher.matches()) {
+                accessPolicyProperties.put(property, value);
+                
userGroupProperties.put(property.replace(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX,
 FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX), value);
+            }
+        });
+
+        // ensure the initial admin is seeded into the user provider if 
appropriate
+        if 
(configurationProperties.containsKey(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY))
 {
+            int i = 0;
+            while (true) {
+                final String key = 
FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
+                if (!userGroupProperties.containsKey(key)) {
+                    userGroupProperties.put(key, 
configurationProperties.get(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY));
+                    break;
+                }
+            }
+        }
+
+        // configure the user group provider
+        userGroupProvider.onConfigured(new 
StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, 
userGroupProperties));
+
+        // configure the access policy provider
+        accessPolicyProvider.onConfigured(new 
StandardAuthorizerConfigurationContext(FILE_USER_GROUP_PROVIDER_ID, 
accessPolicyProperties));
+    }
+
+    @Override
+    public void preDestruction() {
+
+    }
+
+    // ------------------ Groups ------------------
+
+    @Override
+    public synchronized Group doAddGroup(Group group) throws 
AuthorizationAccessException {
+        return userGroupProvider.addGroup(group);
+    }
+
+    @Override
+    public Group getGroup(String identifier) throws 
AuthorizationAccessException {
+        return userGroupProvider.getGroup(identifier);
+    }
+
+    @Override
+    public synchronized Group doUpdateGroup(Group group) throws 
AuthorizationAccessException {
+        return userGroupProvider.updateGroup(group);
+    }
+
+    @Override
+    public synchronized Group deleteGroup(Group group) throws 
AuthorizationAccessException {
+        return userGroupProvider.deleteGroup(group);
+    }
+
+    @Override
+    public synchronized Group deleteGroup(String groupId) throws 
AuthorizationAccessException {
+        return userGroupProvider.deleteGroup(groupId);
+    }
+
+    @Override
+    public Set<Group> getGroups() throws AuthorizationAccessException {
+        return userGroupProvider.getGroups();
+    }
+
+    // ------------------ Users ------------------
+
+    @Override
+    public synchronized User doAddUser(final User user) throws 
AuthorizationAccessException {
+        return userGroupProvider.addUser(user);
+    }
+
+    @Override
+    public User getUser(final String identifier) throws 
AuthorizationAccessException {
+        return userGroupProvider.getUser(identifier);
+    }
+
+    @Override
+    public User getUserByIdentity(final String identity) throws 
AuthorizationAccessException {
+        return userGroupProvider.getUserByIdentity(identity);
+    }
+
+    @Override
+    public synchronized User doUpdateUser(final User user) throws 
AuthorizationAccessException {
+        return userGroupProvider.updateUser(user);
+    }
+
+    @Override
+    public synchronized User deleteUser(final User user) throws 
AuthorizationAccessException {
+        return userGroupProvider.deleteUser(user);
+    }
+
+    @Override
+    public synchronized User deleteUser(final String userId) throws 
AuthorizationAccessException {
+        return userGroupProvider.deleteUser(userId);
+    }
+
+    @Override
+    public Set<User> getUsers() throws AuthorizationAccessException {
+        return userGroupProvider.getUsers();
+    }
+
+    // ------------------ AccessPolicies ------------------
+
+    @Override
+    public synchronized AccessPolicy doAddAccessPolicy(final AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        return accessPolicyProvider.addAccessPolicy(accessPolicy);
+    }
+
+    @Override
+    public AccessPolicy getAccessPolicy(final String identifier) throws 
AuthorizationAccessException {
+        return accessPolicyProvider.getAccessPolicy(identifier);
+    }
+
+    @Override
+    public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        return accessPolicyProvider.updateAccessPolicy(accessPolicy);
+    }
+
+    @Override
+    public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy 
accessPolicy) throws AuthorizationAccessException {
+        return accessPolicyProvider.deleteAccessPolicy(accessPolicy);
+    }
+
+    @Override
+    public synchronized AccessPolicy deleteAccessPolicy(final String 
accessPolicyIdentifier) throws AuthorizationAccessException {
+        return accessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier);
+    }
+
+    @Override
+    public Set<AccessPolicy> getAccessPolicies() throws 
AuthorizationAccessException {
+        return accessPolicyProvider.getAccessPolicies();
+    }
+
+    @AuthorizerContext
+    public void setNiFiProperties(NiFiRegistryProperties properties) {
+        userGroupProvider.setNiFiProperties(properties);
+        accessPolicyProvider.setNiFiProperties(properties);
+    }
+
+    @Override
+    public synchronized UsersAndAccessPolicies getUsersAndAccessPolicies() 
throws AuthorizationAccessException {
+        final AuthorizationsHolder authorizationsHolder = 
accessPolicyProvider.getAuthorizationsHolder();
+        final UserGroupHolder userGroupHolder = 
userGroupProvider.getUserGroupHolder();
+
+        return new UsersAndAccessPolicies() {
+            @Override
+            public AccessPolicy getAccessPolicy(String resourceIdentifier, 
RequestAction action) {
+                return 
authorizationsHolder.getAccessPolicy(resourceIdentifier, action);
+            }
+
+            @Override
+            public User getUser(String identity) {
+                return userGroupHolder.getUser(identity);
+            }
+
+            @Override
+            public Set<Group> getGroups(String userIdentity) {
+                return userGroupHolder.getGroups(userIdentity);
+            }
+        };
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileUserGroupProvider.java
----------------------------------------------------------------------
diff --git 
a/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileUserGroupProvider.java
 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileUserGroupProvider.java
new file mode 100644
index 0000000..54c3d96
--- /dev/null
+++ 
b/nifi-registry-security-api-impl/src/main/java/org/apache/nifi/registry/authorization/file/FileUserGroupProvider.java
@@ -0,0 +1,775 @@
+/*
+ * 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.authorization.file;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.registry.authorization.annotation.AuthorizerContext;
+import org.apache.nifi.registry.authorization.file.tenants.generated.Groups;
+import org.apache.nifi.registry.authorization.file.tenants.generated.Tenants;
+import org.apache.nifi.registry.authorization.file.tenants.generated.Users;
+import org.apache.nifi.registry.properties.util.IdentityMapping;
+import org.apache.nifi.registry.properties.util.IdentityMappingUtil;
+import org.apache.nifi.registry.util.PropertyValue;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizationAccessException;
+import org.apache.nifi.registry.authorization.AuthorizerConfigurationContext;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizerCreationException;
+import 
org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException;
+import org.apache.nifi.registry.authorization.ConfigurableUserGroupProvider;
+import org.apache.nifi.registry.authorization.Group;
+import 
org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException;
+import org.apache.nifi.registry.authorization.User;
+import org.apache.nifi.registry.authorization.UserAndGroups;
+import 
org.apache.nifi.registry.authorization.UserGroupProviderInitializationContext;
+import org.apache.nifi.registry.properties.NiFiRegistryProperties;
+import org.apache.nifi.registry.util.FileUtils;
+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.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 AuthorizerCreationException {
+        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 AuthorizerCreationException(e);
+        }
+    }
+
+    @Override
+    public void onConfigured(AuthorizerConfigurationContext 
configurationContext) throws AuthorizerCreationException {
+        try {
+            final PropertyValue tenantsPath = 
configurationContext.getProperty(PROP_TENANTS_FILE);
+            if (StringUtils.isBlank(tenantsPath.getValue())) {
+                throw new AuthorizerCreationException("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.properties if any are 
provided
+            identityMappings = 
Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
+
+            // extract any node 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 | AuthorizerCreationException | JAXBException | 
IllegalStateException | SAXException e) {
+            throw new AuthorizerCreationException(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.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.authorization.file.tenants.generated.User> users 
= tenants.getUsers().getUser();
+
+        // fine the User that needs to be updated
+        org.apache.nifi.registry.authorization.file.tenants.generated.User 
updateUser = null;
+        for 
(org.apache.nifi.registry.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.authorization.file.tenants.generated.Group group : 
tenants.getGroups().getGroup()) {
+            
Iterator<org.apache.nifi.registry.authorization.file.tenants.generated.Group.User>
 groupUserIter = group.getUser().iterator();
+            while (groupUserIter.hasNext()) {
+                
org.apache.nifi.registry.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.authorization.file.tenants.generated.User> 
iter = tenants.getUsers().getUser().iterator();
+        while (iter.hasNext()) {
+            org.apache.nifi.registry.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();
+
+        // determine that all users in the group exist before doing anything, 
throw an exception if they don't
+        checkGroupUsers(group, tenants.getUsers().getUser());
+
+        // create a new JAXB Group based on the incoming Group
+        final 
org.apache.nifi.registry.authorization.file.tenants.generated.Group jaxbGroup =
+                new 
org.apache.nifi.registry.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.authorization.file.tenants.generated.Group.User 
jaxbGroupUser =
+                    new 
org.apache.nifi.registry.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.authorization.file.tenants.generated.Group 
updateGroup = null;
+        for 
(org.apache.nifi.registry.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.authorization.file.tenants.generated.Group.User 
jaxbGroupUser =
+                    new 
org.apache.nifi.registry.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.authorization.file.tenants.generated.Group> 
iter = tenants.getGroups().getGroup().iterator();
+        while (iter.hasNext()) {
+            
org.apache.nifi.registry.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.authorization.file.tenants.generated.User 
createJAXBUser(User user) {
+        final 
org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser =
+                new 
org.apache.nifi.registry.authorization.file.tenants.generated.User();
+        jaxbUser.setIdentifier(user.getIdentifier());
+        jaxbUser.setIdentity(user.getIdentity());
+        return jaxbUser;
+    }
+
+    private 
Set<org.apache.nifi.registry.authorization.file.tenants.generated.User> 
checkGroupUsers(
+            final Group group,
+            final 
List<org.apache.nifi.registry.authorization.file.tenants.generated.User> users) 
{
+        final 
Set<org.apache.nifi.registry.authorization.file.tenants.generated.User> 
jaxbUsers = new HashSet<>();
+        for (String groupUser : group.getUsers()) {
+            boolean found = false;
+            for 
(org.apache.nifi.registry.authorization.file.tenants.generated.User jaxbUser : 
users) {
+                if (jaxbUser.getIdentifier().equals(groupUser)) {
+                    jaxbUsers.add(jaxbUser);
+                    found = true;
+                    break;
+                }
+            }
+
+            if (!found) {
+                throw new IllegalStateException("Unable to add group because 
user " + groupUser + " does not exist");
+            }
+        }
+        return jaxbUsers;
+    }
+
+    /**
+     * 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();
+//        final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile 
!= null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
+
+        if (emptyTenants) {
+//            if (hasLegacyAuthorizedUsers) {
+//                logger.info("Loading users from legacy model " + 
legacyAuthorizedUsersFile + " into new users file.");
+//                convertLegacyAuthorizedUsers(tenants);
+//            }
+
+            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.authorization.file.tenants.generated.User 
getOrCreateUser(final Tenants tenants, final String userIdentity) {
+        if (StringUtils.isBlank(userIdentity)) {
+            return null;
+        }
+
+        org.apache.nifi.registry.authorization.file.tenants.generated.User 
foundUser = null;
+        for 
(org.apache.nifi.registry.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.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.authorization.file.tenants.generated.Group 
getOrCreateGroup(final Tenants tenants, final String groupName) {
+        if (StringUtils.isBlank(groupName)) {
+            return null;
+        }
+
+        org.apache.nifi.registry.authorization.file.tenants.generated.Group 
foundGroup = null;
+        for 
(org.apache.nifi.registry.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.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 AuthorizerDestructionException {
+    }
+
+    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;
+        }
+    }
+}

Reply via email to