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; + } + } +}
