/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.nifi.authorization; import org.apache.commons.lang3.StringUtils; import org.apache.nifi.authorization.annotation.AuthorizerContext; import org.apache.nifi.authorization.exception.AuthorizationAccessException; import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.file.generated.Authorizations; import org.apache.nifi.authorization.file.tenants.generated.Groups; import org.apache.nifi.authorization.file.generated.Policies; import org.apache.nifi.authorization.file.generated.Policy; import org.apache.nifi.authorization.file.tenants.generated.Users; import org.apache.nifi.authorization.file.tenants.generated.Tenants; import org.apache.nifi.authorization.resource.ResourceFactory; import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.authorization.util.IdentityMapping; import org.apache.nifi.authorization.util.IdentityMappingUtil; import org.apache.nifi.components.PropertyValue; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.file.FileUtils; import org.apache.nifi.web.api.dto.PortDTO; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; 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.UUID; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * 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 AUTHORIZATIONS_XSD = "/authorizations.xsd"; private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated"; private static final String TENANTS_XSD = "/tenants.xsd"; private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated"; private static final String USERS_XSD = "/legacy-users.xsd"; private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated"; private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH); private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH); private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_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."); } } static final String READ_CODE = "R"; static final String WRITE_CODE = "W"; static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File"; static final String PROP_TENANTS_FILE = "Users File"; static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity"; static final String PROP_LEGACY_AUTHORIZED_USERS_FILE = "Legacy Authorized Users File"; static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile("Node Identity \\S+"); private Schema usersSchema; private Schema tenantsSchema; private Schema authorizationsSchema; private SchemaFactory schemaFactory; private NiFiProperties properties; private File tenantsFile; private File authorizationsFile; private File restoreAuthorizationsFile; private File restoreTenantsFile; private String rootGroupId; private String initialAdminIdentity; private String legacyAuthorizedUsersFile; private Set<String> nodeIdentities; private List<PortDTO> ports = new ArrayList<>(); private List<IdentityMapping> identityMappings; private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>(); @Override public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { try { schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD)); 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 doOnConfigured(final 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 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()); } final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile(); final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile(); // the restore directory is optional and may be null final File restoreDirectory = properties.getRestoreDirectory(); if (restoreDirectory != null) { // sanity check that restore directory is a directory, creating it if necessary FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory); // check that restore directory is not the same as the authorizations directory if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ", authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); } // check that restore directory is not the same as the user's directory if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) { throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ", tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath())); } // the restore copy will have same file name, but reside in a different directory restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName()); restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName()); try { // sync the primary copy with the restore copy FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger); FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger); } catch (final IOException | IllegalStateException ioe) { throw new AuthorizerCreationException(ioe); } } // 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 == null ? null : IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings); // get the value of the legacy authorized users file final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE); legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue(); // 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(); // if we've copied the authorizations file to a restore directory synchronize it if (restoreAuthorizationsFile != null) { FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger); } // 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("Authorizations file loaded at %s", new Date().toString())); } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) { throw new AuthorizerCreationException(e); } } /** * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. * * @throws JAXBException Unable to reload the authorized users file * @throws IOException Unable to sync file with restore * @throws IllegalStateException Unable to sync file with restore */ private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException { // attempt to unmarshal final Authorizations authorizations = unmarshallAuthorizations(); if (authorizations.getPolicies() == null) { authorizations.setPolicies(new Policies()); } final Tenants tenants = unmarshallTenants(); if (tenants.getUsers() == null) { tenants.setUsers(new Users()); } if (tenants.getGroups() == null) { tenants.setGroups(new Groups()); } final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants); final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty(); final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile)); // if we are starting fresh then we might need to populate an initial admin or convert legacy users if (emptyAuthorizations) { parseFlow(); if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) { throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File"); } else if (hasInitialAdminIdentity) { logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity); populateInitialAdmin(authorizations, tenants); } else if (hasLegacyAuthorizedUsers) { logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model"); convertLegacyAuthorizedUsers(authorizations, tenants); } populateNodes(authorizations, tenants); // save any changes that were made and repopulate the holder saveAndRefreshHolder(authorizations, tenants); } else { this.authorizationsHolder.set(authorizationsHolder); } } 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(); } 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(); } /** * Try to parse the flow configuration file to extract the root group id and port information. * * @throws SAXException if an error occurs creating the schema */ private void parseFlow() throws SAXException { final FlowParser flowParser = new FlowParser(); final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile()); if (flowInfo != null) { rootGroupId = flowInfo.getRootGroupId(); ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts(); } } /** * Creates the initial admin user and policies for access the flow and managing users and policies. */ private void populateInitialAdmin(final Authorizations authorizations, Tenants tenants) { final org.apache.nifi.authorization.file.tenants.generated.User adminUser = getOrCreateUser(tenants, initialAdminIdentity); // grant the user read access to the /flow resource addAccessPolicy(authorizations, ResourceType.Flow.getValue(), adminUser.getIdentifier(), READ_CODE); // grant the user read access to the root process group resource if (rootGroupId != null) { addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE); addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), WRITE_CODE); } // grant the user write to restricted components addAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), adminUser.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /tenants resource addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /policies resource addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), WRITE_CODE); // grant the user read/write access to the /controller resource addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Controller.getValue(), adminUser.getIdentifier(), WRITE_CODE); } /** * Creates a user for each node and gives the nodes write permission to /proxy. * * @param authorizations the overall authorizations */ private void populateNodes(Authorizations authorizations, Tenants tenants) { for (String nodeIdentity : nodeIdentities) { final org.apache.nifi.authorization.file.tenants.generated.User jaxbNodeUser = getOrCreateUser(tenants, nodeIdentity); // grant access to the proxy resource addAccessPolicy(authorizations, ResourceType.Proxy.getValue(), jaxbNodeUser.getIdentifier(), WRITE_CODE); // grant the user read/write access data of the root group if (rootGroupId != null) { addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), READ_CODE); addAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, jaxbNodeUser.getIdentifier(), WRITE_CODE); } } } /** * Unmarshalls an existing authorized-users.xml and converts the object model to the new model. * * @param authorizations the current Authorizations instance that policies will be added to * @param tenants the current Tenants instance users and groups will be added to * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled */ private void convertLegacyAuthorizedUsers(final Authorizations authorizations, final Tenants tenants) throws AuthorizerCreationException, JAXBException { final File authorizedUsersFile = new File(legacyAuthorizedUsersFile); if (!authorizedUsersFile.exists()) { throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists"); } final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller(); unmarshaller.setSchema(usersSchema); final JAXBElement<org.apache.nifi.user.generated.Users> element = unmarshaller.unmarshal( new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class); final org.apache.nifi.user.generated.Users users = element.getValue(); if (users.getUser().isEmpty()) { logger.info("Legacy Authorized Users File contained no users, nothing to convert"); return; } // get all the user DNs into a list List<String> userIdentities = new ArrayList<>(); for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings)); } // sort the list and pull out the first identity Collections.sort(userIdentities); final String seedIdentity = userIdentities.get(0); // create mapping from Role to access policies final Map<Role,Set<RoleAccessPolicy>> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId); final List<Policy> allPolicies = new ArrayList<>(); for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { // create the identifier of the new user based on the DN final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings); org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn); // if there was a group name find or create the group and add the user to it org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup()); if (group != null) { org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); groupUser.setIdentifier(user.getIdentifier()); group.getUser().add(groupUser); } // create policies based on the given role for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) { Role role = Role.valueOf(jaxbRole.getName()); Set<RoleAccessPolicy> policies = roleAccessPolicies.get(role); for (RoleAccessPolicy roleAccessPolicy : policies) { // get the matching policy, or create a new one Policy policy = getOrCreatePolicy( allPolicies, seedIdentity, roleAccessPolicy.getResource(), roleAccessPolicy.getAction()); // add the user to the policy if it doesn't exist addUserToPolicy(user.getIdentifier(), policy); } } } // convert any access controls on ports to the appropriate policies for (PortDTO portDTO : ports) { final Resource resource; if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) { resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName())); } else { resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName())); } if (portDTO.getUserAccessControl() != null) { for (String userAccessControl : portDTO.getUserAccessControl()) { // need to perform the identity mapping on the access control so it matches the identities in the User objects final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings); // find a user where the identity is the userAccessControl org.apache.nifi.authorization.file.tenants.generated.User foundUser = null; for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : tenants.getUsers().getUser()) { if (jaxbUser.getIdentity().equals(mappedUserAccessControl)) { foundUser = jaxbUser; break; } } // couldn't find the user matching the access control so log a warning and skip if (foundUser == null) { logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...", new Object[] {mappedUserAccessControl}); continue; } // we found the user so create the appropriate policy and add the user to it Policy policy = getOrCreatePolicy( allPolicies, seedIdentity, resource.getIdentifier(), WRITE_CODE); addUserToPolicy(foundUser.getIdentifier(), policy); } } if (portDTO.getGroupAccessControl() != null) { for (String groupAccessControl : portDTO.getGroupAccessControl()) { // find a group where the name is the groupAccessControl org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null; for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) { if (jaxbGroup.getName().equals(groupAccessControl)) { foundGroup = jaxbGroup; break; } } // couldn't find the group matching the access control so log a warning and skip if (foundGroup == null) { logger.warn("Found port with group access control for {} but no group exists with this name, skipping...", new Object[] {groupAccessControl}); continue; } // we found the group so create the appropriate policy and add all the users to it Policy policy = getOrCreatePolicy( allPolicies, seedIdentity, resource.getIdentifier(), WRITE_CODE); addGroupToPolicy(foundGroup.getIdentifier(), policy); } } } authorizations.getPolicies().getPolicy().addAll(allPolicies); } /** * 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 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.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) { if (StringUtils.isBlank(userIdentity)) { return null; } org.apache.nifi.authorization.file.tenants.generated.User foundUser = null; for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) { if (user.getIdentity().equals(userIdentity)) { foundUser = user; break; } } if (foundUser == null) { final String userIdentifier = UUID.nameUUIDFromBytes(userIdentity.getBytes(StandardCharsets.UTF_8)).toString(); foundUser = new org.apache.nifi.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.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) { if (StringUtils.isBlank(groupName)) { return null; } org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null; for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { if (group.getName().equals(groupName)) { foundGroup = group; break; } } if (foundGroup == null) { UUID newGroupIdentifier = UUID.nameUUIDFromBytes(groupName.getBytes(StandardCharsets.UTF_8)); foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group(); foundGroup.setIdentifier(newGroupIdentifier.toString()); foundGroup.setName(groupName); tenants.getGroups().getGroup().add(foundGroup); } return foundGroup; } /** * 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 UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); foundPolicy = new Policy(); foundPolicy.setIdentifier(policyIdentifier.toString()); foundPolicy.setResource(resource); foundPolicy.setAction(action); policies.add(foundPolicy); } return foundPolicy; } /** * Creates and adds an access policy for the given resource, identity, and actions. * * @param authorizations the Authorizations instance to add the policy to * @param resource the resource for the policy * @param identity the identity for the policy * @param action the action for the policy */ private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, 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 + identity + action; final UUID policyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); final AccessPolicy.Builder builder = new AccessPolicy.Builder() .identifier(policyIdentifier.toString()) .resource(resource) .addUser(identity); 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(identity); foundPolicy.getUser().add(policyUser); } } /** * 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 * @param tenants the tenants to save and populate from * @throws AuthorizationAccessException if an error occurs saving the authorizations */ private synchronized void saveAndRefreshHolder(final Authorizations authorizations, final Tenants tenants) throws AuthorizationAccessException { try { saveTenants(tenants); saveAuthorizations(authorizations); final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations, tenants); this.authorizationsHolder.set(authorizationsHolder); } catch (JAXBException e) { throw new AuthorizationAccessException("Unable to save Authorizations", e); } } 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 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); } @AuthorizerContext public void setNiFiProperties(NiFiProperties properties) { this.properties = properties; } @Override public void preDestruction() { } // ------------------ Groups ------------------ @Override public synchronized Group doAddGroup(Group group) throws AuthorizationAccessException { if (group == null) { throw new IllegalArgumentException("Group cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); // determine that all users in the group exist before doing anything, throw an exception if they don't final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = checkGroupUsers(group, tenants.getUsers().getUser()); // create a new JAXB Group based on the incoming Group final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.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.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); jaxbGroupUser.setIdentifier(groupUser); jaxbGroup.getUser().add(jaxbGroupUser); } tenants.getGroups().getGroup().add(jaxbGroup); saveAndRefreshHolder(authorizations, tenants); return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier()); } @Override public Group getGroup(String identifier) throws AuthorizationAccessException { if (identifier == null) { return null; } return authorizationsHolder.get().getGroupsById().get(identifier); } @Override public synchronized Group doUpdateGroup(Group group) throws AuthorizationAccessException { if (group == null) { throw new IllegalArgumentException("Group cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); // find the group that needs to be update org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null; for (org.apache.nifi.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.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User(); jaxbGroupUser.setIdentifier(groupUser); updateGroup.getUser().add(jaxbGroupUser); } updateGroup.setName(group.getName()); saveAndRefreshHolder(authorizations, tenants); return this.authorizationsHolder.get().getGroupsById().get(group.getIdentifier()); } @Override public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException { final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); final List<org.apache.nifi.authorization.file.tenants.generated.Group> groups = tenants.getGroups().getGroup(); // for each policy iterate over the group reference and remove the group reference if it matches the group being deleted for (Policy policy : authorizations.getPolicies().getPolicy()) { Iterator<Policy.Group> policyGroupIter = policy.getGroup().iterator(); while (policyGroupIter.hasNext()) { Policy.Group policyGroup = policyGroupIter.next(); if (policyGroup.getIdentifier().equals(group.getIdentifier())) { policyGroupIter.remove(); break; } } } // now remove the actual group from the top-level list of groups boolean removedGroup = false; Iterator<org.apache.nifi.authorization.file.tenants.generated.Group> iter = groups.iterator(); while (iter.hasNext()) { org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next(); if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) { iter.remove(); removedGroup = true; break; } } if (removedGroup) { saveAndRefreshHolder(authorizations, tenants); return group; } else { return null; } } @Override public Set<Group> getGroups() throws AuthorizationAccessException { return authorizationsHolder.get().getAllGroups(); } private Set<org.apache.nifi.authorization.file.tenants.generated.User> checkGroupUsers(final Group group, final List<org.apache.nifi.authorization.file.tenants.generated.User> users) { final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = new HashSet<>(); for (String groupUser : group.getUsers()) { boolean found = false; for (org.apache.nifi.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; } // ------------------ Users ------------------ @Override public synchronized User doAddUser(final User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user); final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); tenants.getUsers().getUser().add(jaxbUser); saveAndRefreshHolder(authorizations, tenants); return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier()); } private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) { final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User(); jaxbUser.setIdentifier(user.getIdentifier()); jaxbUser.setIdentity(user.getIdentity()); return jaxbUser; } @Override public User getUser(final String identifier) throws AuthorizationAccessException { if (identifier == null) { return null; } final AuthorizationsHolder holder = authorizationsHolder.get(); return holder.getUsersById().get(identifier); } @Override public User getUserByIdentity(final String identity) throws AuthorizationAccessException { if (identity == null) { return null; } final AuthorizationsHolder holder = authorizationsHolder.get(); return holder.getUsersByIdentity().get(identity); } @Override public synchronized User doUpdateUser(final User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser(); // fine the User that needs to be updated org.apache.nifi.authorization.file.tenants.generated.User updateUser = null; for (org.apache.nifi.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(authorizations, tenants); return this.authorizationsHolder.get().getUsersById().get(user.getIdentifier()); } } @Override public synchronized User deleteUser(final User user) throws AuthorizationAccessException { if (user == null) { throw new IllegalArgumentException("User cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser(); // for each group iterate over the user references and remove the user reference if it matches the user being deleted for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) { Iterator<org.apache.nifi.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator(); while (groupUserIter.hasNext()) { org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next(); if (groupUser.getIdentifier().equals(user.getIdentifier())) { groupUserIter.remove(); break; } } } // remove any references to the user being deleted from policies for (Policy policy : authorizations.getPolicies().getPolicy()) { Iterator<Policy.User> policyUserIter = policy.getUser().iterator(); while (policyUserIter.hasNext()) { Policy.User policyUser = policyUserIter.next(); if (policyUser.getIdentifier().equals(user.getIdentifier())) { policyUserIter.remove(); break; } } } // remove the actual user if it exists boolean removedUser = false; Iterator<org.apache.nifi.authorization.file.tenants.generated.User> iter = users.iterator(); while (iter.hasNext()) { org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next(); if (user.getIdentifier().equals(jaxbUser.getIdentifier())) { iter.remove(); removedUser = true; break; } } if (removedUser) { saveAndRefreshHolder(authorizations, tenants); return user; } else { return null; } } @Override public Set<User> getUsers() throws AuthorizationAccessException { return authorizationsHolder.get().getAllUsers(); } // ------------------ AccessPolicies ------------------ @Override public synchronized AccessPolicy doAddAccessPolicy(final 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 = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); authorizations.getPolicies().getPolicy().add(policy); saveAndRefreshHolder(authorizations, tenants); return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); } 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; } @Override public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException { if (identifier == null) { return null; } final AuthorizationsHolder holder = authorizationsHolder.get(); return holder.getPoliciesById().get(identifier); } @Override public synchronized AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { if (accessPolicy == null) { throw new IllegalArgumentException("AccessPolicy cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); 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, tenants); return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier()); } @Override public synchronized AccessPolicy deleteAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException { if (accessPolicy == null) { throw new IllegalArgumentException("AccessPolicy cannot be null"); } final AuthorizationsHolder holder = this.authorizationsHolder.get(); final Tenants tenants = holder.getTenants(); final Authorizations authorizations = holder.getAuthorizations(); // find the matching Policy and remove it boolean deletedPolicy = false; Iterator<Policy> policyIter = authorizations.getPolicies().getPolicy().iterator(); while (policyIter.hasNext()) { final Policy policy = policyIter.next(); if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) { policyIter.remove(); deletedPolicy = true; break; } } // never found a matching Policy so return null if (!deletedPolicy) { return null; } saveAndRefreshHolder(authorizations, tenants); return accessPolicy; } @Override public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException { return authorizationsHolder.get().getAllPolicies(); } @Override public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException { return authorizationsHolder.get(); } /** * 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); } } }