You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by bb...@apache.org on 2017/10/05 18:11:31 UTC
[06/10] nifi-registry git commit: NIFIREG-9: Initial Auth
Implementation
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;
+ }
+ }
+}