From 679ad93f577e70d5d322fe368a5c333b525fb6da Mon Sep 17 00:00:00 2001 From: Bryan Bende Date: Mon, 6 Jun 2016 16:32:19 -0400 Subject: [PATCH] NIFI-1804 Adding ability for FileAuthorizer to automatically convert existing authorized-users.xml to new model - Removing Resources class from file authorizer and updating ResourceType enum - Updating ResourceFactory to be in sync with ResourceType enum and adding additional required permissions to the auto-conversion - Adding root process group to the seeding of the initial admin - Improvement so that users that are already part of a read-write policy, won't end up in a read policy for the same resource - Removing rootGroupId from authorization context and auto-detecting it from the flow provided through nifi.properties - This closes #507 --- .../AuthorizerConfigurationContext.java | 5 - .../authorization/AuthorizerFactoryBean.java | 8 +- .../resources/nifi-authorizer-context.xml | 1 - .../nifi-file-authorizer/pom.xml | 4 + .../nifi/authorization/FileAuthorizer.java | 463 +++++++++++++++--- .../org/apache/nifi/authorization/Role.java | 30 ++ .../nifi/authorization/RoleAccessPolicy.java | 99 ++++ .../authorization/FileAuthorizerTest.java | 290 ++++++++++- .../resources/authorized-users-multirole.xml | 21 + .../src/test/resources/authorized-users.xml | 35 ++ .../src/test/resources/flow.xml.gz | Bin 0 -> 566 bytes ...tandardAuthorizerConfigurationContext.java | 9 +- .../resource/ResourceFactory.java | 406 ++++++++++++--- .../authorization/resource/ResourceType.java | 27 +- .../src/main/resources/conf/authorizers.xml | 1 + 15 files changed, 1241 insertions(+), 158 deletions(-) create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/Role.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-multirole.xml create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users.xml create mode 100644 nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow.xml.gz diff --git a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java index 20cb69ea5a..3721ab4e57 100644 --- a/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java +++ b/nifi-api/src/main/java/org/apache/nifi/authorization/AuthorizerConfigurationContext.java @@ -30,11 +30,6 @@ public interface AuthorizerConfigurationContext { */ String getIdentifier(); - /** - * @return the id of the root process group - */ - String getRootGroupId(); - /** * Retrieves all properties the component currently understands regardless * of whether a value has been set for them or not. If no value is present diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java index e4d7318a87..d061fe1940 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/java/org/apache/nifi/authorization/AuthorizerFactoryBean.java @@ -23,7 +23,6 @@ import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerDestructionException; import org.apache.nifi.authorization.generated.Authorizers; import org.apache.nifi.authorization.generated.Property; -import org.apache.nifi.controller.FlowController; import org.apache.nifi.nar.ExtensionManager; import org.apache.nifi.nar.NarCloseable; import org.apache.nifi.util.NiFiProperties; @@ -72,13 +71,8 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho private Authorizer authorizer; private NiFiProperties properties; - private FlowController flowController; private final Map authorizers = new HashMap<>(); - public void setFlowController(FlowController flowController) { - this.flowController = flowController; - } - @Override public Authorizer getAuthorizer(String identifier) { return authorizers.get(identifier); @@ -195,7 +189,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho authorizerProperties.put(property.getName(), property.getValue()); } - return new StandardAuthorizerConfigurationContext(authorizer.getIdentifier(), flowController.getRootGroupId(), authorizerProperties); + return new StandardAuthorizerConfigurationContext(authorizer.getIdentifier(), authorizerProperties); } private void performMethodInjection(final Authorizer instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/resources/nifi-authorizer-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/resources/nifi-authorizer-context.xml index b95db114e1..71bf684893 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/resources/nifi-authorizer-context.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-authorizer/src/main/resources/nifi-authorizer-context.xml @@ -21,7 +21,6 @@ - diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml index 4f97220516..c7fbb2cfa7 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/pom.xml @@ -93,6 +93,10 @@ org.apache.nifi nifi-framework-authorization + + org.apache.nifi + nifi-framework-core + commons-codec commons-codec diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java index ca99e09a74..20a43c37fa 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java @@ -25,11 +25,15 @@ import org.apache.nifi.authorization.file.generated.Groups; import org.apache.nifi.authorization.file.generated.Policies; import org.apache.nifi.authorization.file.generated.Policy; import org.apache.nifi.authorization.file.generated.Users; +import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.components.PropertyValue; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.file.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; import javax.xml.XMLConstants; import javax.xml.bind.JAXBContext; @@ -37,18 +41,29 @@ 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.transform.stream.StreamSource; import javax.xml.validation.Schema; import javax.xml.validation.SchemaFactory; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.Collections; import java.util.Date; 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.zip.GZIPInputStream; /** * Provides identity checks and grants authorities. @@ -56,30 +71,47 @@ import java.util.concurrent.atomic.AtomicReference; public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class); - private static final String USERS_XSD = "/authorizations.xsd"; - private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authorization.file.generated"; - private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); - static final String READ_CODE = "R"; - static final String WRITE_CODE = "W"; + 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 USERS_XSD = "/users.xsd"; + private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated"; + + private static final String FLOW_XSD = "/FlowConfiguration.xsd"; + + private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH); + private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH); /** * Load the JAXBContext. */ - private static JAXBContext initializeJaxbContext() { + private static JAXBContext initializeJaxbContext(final String contextPath) { try { - return JAXBContext.newInstance(JAXB_GENERATED_PATH, FileAuthorizer.class.getClassLoader()); + return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader()); } catch (JAXBException e) { throw new RuntimeException("Unable to create JAXBContext."); } } - private Schema schema; + static final String READ_CODE = "R"; + static final String WRITE_CODE = "W"; + + static final String PROP_AUTHORIZATIONS_FILE = "Authorizations 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 String PROP_ROOT_GROUP_ID = "Root Group ID"; + + private Schema flowSchema; + private Schema usersSchema; + private Schema authorizationsSchema; private SchemaFactory schemaFactory; private NiFiProperties properties; private File authorizationsFile; private File restoreAuthorizationsFile; private String rootGroupId; + private String initialAdminIdentity; + private String legacyAuthorizedUsersFile; private final AtomicReference authorizationsHolder = new AtomicReference<>(); @@ -87,7 +119,9 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { try { schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); - schema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); + authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD)); + usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD)); + flowSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(FLOW_XSD)); } catch (Exception e) { throw new AuthorizerCreationException(e); } @@ -96,7 +130,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { @Override public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { try { - final PropertyValue authorizationsPath = configurationContext.getProperty("Authorizations File"); + final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE); if (StringUtils.isBlank(authorizationsPath.getValue())) { throw new AuthorizerCreationException("The authorizations file must be specified."); } @@ -132,11 +166,17 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { } } - final PropertyValue initialAdminIdentityProp = configurationContext.getProperty("Initial Admin Identity"); - final String initialAdminIdentity = initialAdminIdentityProp == null ? null : initialAdminIdentityProp.getValue(); + final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY); + initialAdminIdentity = initialAdminIdentityProp == null ? null : initialAdminIdentityProp.getValue(); + + final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(PROP_LEGACY_AUTHORIZED_USERS_FILE); + legacyAuthorizedUsersFile = legacyAuthorizedUsersProp == null ? null : legacyAuthorizedUsersProp.getValue(); + + // try to extract the root group id from the flow configuration file specified in nifi.properties + rootGroupId = getRootGroupId(); // load the authorizations - load(initialAdminIdentity); + load(); // if we've copied the authorizations file to a restore directory synchronize it if (restoreAuthorizationsFile != null) { @@ -145,13 +185,71 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { logger.info(String.format("Authorizations file loaded at %s", new Date().toString())); - this.rootGroupId = configurationContext.getRootGroupId(); - } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException e) { throw new AuthorizerCreationException(e); } } + /** + * Extracts the root group id from the flow configuration file provided in nifi.properties. + * + * @return the root group id, or null if the files doesn't exist, was empty, or could not be parsed + */ + private String getRootGroupId() { + final File flowFile = properties.getFlowConfigurationFile(); + if (flowFile == null) { + logger.debug("Flow Configuration file was null"); + return null; + } + + // if the flow doesn't exist or is 0 bytes, then return null + final Path flowPath = flowFile.toPath(); + try { + if (!Files.exists(flowPath) || Files.size(flowPath) == 0) { + logger.debug("Flow Configuration does not exist or was empty"); + return null; + } + } catch (IOException e) { + logger.debug("An error occurred determining the size of the Flow Configuration file"); + return null; + } + + // otherwise create the appropriate input streams to read the file + try (final InputStream in = Files.newInputStream(flowPath, StandardOpenOption.READ); + final InputStream gzipIn = new GZIPInputStream(in)) { + + // create validating document builder + final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); + docFactory.setNamespaceAware(true); + docFactory.setSchema(flowSchema); + + // parse the flow + final DocumentBuilder docBuilder = docFactory.newDocumentBuilder(); + final Document document = docBuilder.parse(gzipIn); + + // extract the root group id + final Element rootElement = document.getDocumentElement(); + + final Element rootGroupElement = (Element) rootElement.getElementsByTagName("rootGroup").item(0); + if (rootGroupElement == null) { + logger.debug("rootGroup element not found in Flow Configuration file"); + return null; + } + + final Element rootGroupIdElement = (Element) rootGroupElement.getElementsByTagName("id").item(0); + if (rootGroupIdElement == null) { + logger.debug("id element not found under rootGroup in Flow Configuration file"); + return null; + } + + return rootGroupIdElement.getTextContent(); + + } catch (final SAXException | ParserConfigurationException | IOException ex) { + logger.error("Unable to find root group id in {} due to {}", new Object[] { flowPath.toAbsolutePath(), ex }); + return null; + } + } + /** * Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up. * @@ -159,10 +257,10 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { * @throws IOException Unable to sync file with restore * @throws IllegalStateException Unable to sync file with restore */ - private synchronized void load(final String initialAdminIdentity) throws JAXBException, IOException, IllegalStateException { + private synchronized void load() throws JAXBException, IOException, IllegalStateException { // attempt to unmarshal - final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); - unmarshaller.setSchema(schema); + final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(authorizationsSchema); final JAXBElement element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class); final Authorizations authorizations = element.getValue(); @@ -178,11 +276,24 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { } final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations); + final boolean emptyAuthorizations = authorizationsHolder.getAllUsers().isEmpty() && authorizationsHolder.getAllPolicies().isEmpty(); final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity)); + final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile)); - // if an initial admin was provided and there are no users or policies then automatically create the admin user & policies - if (hasInitialAdminIdentity && authorizationsHolder.getAllUsers().isEmpty() && authorizationsHolder.getAllPolicies().isEmpty()) { - populateInitialAdmin(authorizations, initialAdminIdentity); + // if we are starting fresh then we might need to populate an initial admin or convert legacy users + if (emptyAuthorizations) { + + if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) { + throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File"); + } else if (hasInitialAdminIdentity) { + logger.debug("Populating authorizations for Initial Admin: " + initialAdminIdentity); + populateInitialAdmin(authorizations); + } else if (hasLegacyAuthorizedUsers) { + logger.debug("Converting " + legacyAuthorizedUsersFile + " to new authorizations model"); + convertLegacyAuthorizedUsers(authorizations); + } + + // save any changes that were made and repopulate the holder saveAndRefreshHolder(authorizations); } else { this.authorizationsHolder.set(authorizationsHolder); @@ -191,60 +302,298 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { /** * Creates the initial admin user and policies for access the flow and managing users and policies. - * - * @param adminIdentity the identity of the admin user */ - private void populateInitialAdmin(final Authorizations authorizations, final String adminIdentity) { + private void populateInitialAdmin(final Authorizations authorizations) { // generate an identifier and add a User with the given identifier and identity - final UUID adminIdentifier = UUID.nameUUIDFromBytes(adminIdentity.getBytes(StandardCharsets.UTF_8)); - final User adminUser = new User.Builder().identifier(adminIdentifier.toString()).identity(adminIdentity).build(); + final UUID adminIdentifier = UUID.nameUUIDFromBytes(initialAdminIdentity.getBytes(StandardCharsets.UTF_8)); + final User adminUser = new User.Builder().identifier(adminIdentifier.toString()).identity(initialAdminIdentity).build(); final org.apache.nifi.authorization.file.generated.User jaxbAdminUser = createJAXBUser(adminUser); authorizations.getUsers().getUser().add(jaxbAdminUser); // grant the user read access to the /flow resource - final AccessPolicy flowPolicy = createInitialAdminPolicy("/flow", adminUser.getIdentifier(), RequestAction.READ); - final Policy jaxbFlowPolicy = createJAXBPolicy(flowPolicy); - authorizations.getPolicies().getPolicy().add(jaxbFlowPolicy); + 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.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE + WRITE_CODE); + } // grant the user read/write access to the /users resource - final AccessPolicy usersPolicy = createInitialAdminPolicy("/users", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); - final Policy jaxbUsersPolicy = createJAXBPolicy(usersPolicy); - authorizations.getPolicies().getPolicy().add(jaxbUsersPolicy); + addAccessPolicy(authorizations, ResourceType.User.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE); // grant the user read/write access to the /groups resource - final AccessPolicy groupsPolicy = createInitialAdminPolicy("/groups", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); - final Policy jaxbGroupsPolicy = createJAXBPolicy(groupsPolicy); - authorizations.getPolicies().getPolicy().add(jaxbGroupsPolicy); + addAccessPolicy(authorizations, ResourceType.Group.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE); // grant the user read/write access to the /policies resource - final AccessPolicy policiesPolicy = createInitialAdminPolicy("/policies", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); - final Policy jaxbPoliciesPolicy = createJAXBPolicy(policiesPolicy); - authorizations.getPolicies().getPolicy().add(jaxbPoliciesPolicy); + addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE); } /** - * Creates an AccessPolicy based on the given parameters, generating an identifier from the resource and admin identity. + * Unmarshalls an existing authorized-users.xml and converts the object model to the new model. * - * @param resource the resource for the policy - * @param adminIdentity the identity of the admin user to add to the policy - * @param actions the actions for the policy - * @return the AccessPolicy based on the given parameters + * @param authorizations the current Authorizations instance that users, groups, and policies 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 AccessPolicy createInitialAdminPolicy(final String resource, final String adminIdentity, final RequestAction ... actions) { - final String uuidSeed = resource + adminIdentity; - final UUID flowPolicyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); - - final AccessPolicy.Builder builder = new AccessPolicy.Builder() - .identifier(flowPolicyIdentifier.toString()) - .resource(resource) - .addUser(adminIdentity); - - for (RequestAction action : actions) { - builder.addAction(action); + private void convertLegacyAuthorizedUsers(final Authorizations authorizations) throws AuthorizerCreationException, JAXBException { + final File authorizedUsersFile = new File(legacyAuthorizedUsersFile); + if (!authorizedUsersFile.exists()) { + throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists"); } - return builder.build(); + final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(usersSchema); + + final JAXBElement 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 userIdentities = new ArrayList<>(); + for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { + userIdentities.add(legacyUser.getDn()); + } + + // 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> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId); + + final List readPolicies = new ArrayList<>(); + final List readWritePolicies = new ArrayList<>(); + + for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) { + // create the identifier of the new user based on the DN + String legacyUserDn = legacyUser.getDn(); + String userIdentifier = UUID.nameUUIDFromBytes(legacyUserDn.getBytes(StandardCharsets.UTF_8)).toString(); + + // create the new User and add it to the list of users + org.apache.nifi.authorization.file.generated.User user = new org.apache.nifi.authorization.file.generated.User(); + user.setIdentifier(userIdentifier); + user.setIdentity(legacyUserDn); + authorizations.getUsers().getUser().add(user); + + // if there was a group name find or create the group and add the user to it + org.apache.nifi.authorization.file.generated.Group group = getOrCreateGroup(authorizations, legacyUser.getGroup()); + if (group != null) { + org.apache.nifi.authorization.file.generated.User.Group userGroup = new org.apache.nifi.authorization.file.generated.User.Group(); + userGroup.setIdentifier(group.getIdentifier()); + user.getGroup().add(userGroup); + } + + // create policies based on the given role + for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) { + Role role = Role.valueOf(jaxbRole.getName()); + Set policies = roleAccessPolicies.get(role); + + for (RoleAccessPolicy roleAccessPolicy : policies) { + // determine if we should use the read policies or read-write policies + List searchPolicies = roleAccessPolicy.getActions().equals(RoleAccessPolicy.READ_ACTION) + ? readPolicies : readWritePolicies; + + // get the matching policy, or create a new one + Policy policy = getOrCreatePolicy( + searchPolicies, + seedIdentity, + roleAccessPolicy.getResource(), + roleAccessPolicy.getActions()); + + // 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); + } + } + } + + } + + // merge the policies and add the result to the overall authorizations instance + final List mergedPolicies = merge(readPolicies, readWritePolicies); + authorizations.getPolicies().getPolicy().addAll(mergedPolicies); + } + + /** + * Merges the provided read and read-write policies. Any users that are in a read policy and also in a read-write + * policy for the same resource will be removed from the read policy. If users are still left in the read policy + * after checking each user, then the read policy will still be included in the merged list. + * + * @param readPolicies the read policies + * @param readWritePolicies the read-write policies + * @return the merged list of policies + */ + private List merge(List readPolicies, List readWritePolicies) { + final List mergedPolicies = new ArrayList<>(readWritePolicies); + + logger.debug("Merging {} read policies and {} read-write policies", + new Object[] {readPolicies.size(), readWritePolicies.size()}); + + for (Policy readPolicy : readPolicies) { + logger.debug("Processing read policy {} for resource {} with actions {}", + new Object[] {readPolicy.getIdentifier(), readPolicy.getResource(), readPolicy.getAction()}); + + // try to find a matching read-write policy for the same resource + Policy foundReadWritePolicy = null; + for (Policy readWritePolicy : readWritePolicies) { + if (readWritePolicy.getResource().equals(readPolicy.getResource())) { + foundReadWritePolicy = readWritePolicy; + break; + } + } + + // if we didn't find a match then we just add the current read policy to the merged list + if (foundReadWritePolicy == null) { + logger.debug("no matching write policy found, adding read policy {} to merged policies", + new Object[] {readPolicy.getIdentifier()}); + mergedPolicies.add(readPolicy); + } else { + // check each user from the read policy + Iterator userIter = readPolicy.getUser().iterator(); + while (userIter.hasNext()) { + Policy.User readUser = userIter.next(); + + // determine if the user from the read policy exists in the read-write policy + boolean userInReadWrite = false; + for (Policy.User readWriteUser : foundReadWritePolicy.getUser()) { + if (readWriteUser.getIdentifier().equals(readUser.getIdentifier())) { + userInReadWrite = true; + break; + } + } + + // if the user was in the read-write policy, remove them from read policy + if (userInReadWrite) { + logger.debug("Removing user {} from read policy {}", new Object[] {readUser.getIdentifier(), readPolicy.getIdentifier()}); + userIter.remove(); + } + } + + // after checking all users, see if any are still left in the read policy + // if there are still some users, then add the read policy to the merged list + if (readPolicy.getUser().size() > 0) { + logger.debug("Read policy still has {} users, adding read policy {} to merged list", + new Object[] {readPolicy.getUser().size(), readPolicy.getIdentifier()}); + mergedPolicies.add(readPolicy); + } + } + } + + return mergedPolicies; + } + + /** + * Finds the Group with the given name, or creates a new one and adds it to Authorizations. + * + * @param authorizations the Authorizations reference + * @param groupName the name of the group to look for + * @return the Group from Authorizations with the given name, or a new instance + */ + private org.apache.nifi.authorization.file.generated.Group getOrCreateGroup(final Authorizations authorizations, final String groupName) { + if (StringUtils.isBlank(groupName)) { + return null; + } + + org.apache.nifi.authorization.file.generated.Group foundGroup = null; + for (org.apache.nifi.authorization.file.generated.Group group : authorizations.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.generated.Group(); + foundGroup.setIdentifier(newGroupIdentifier.toString()); + foundGroup.setName(groupName); + authorizations.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 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 actions the actions for the policy + */ + private void addAccessPolicy(final Authorizations authorizations, final String resource, final String identity, final String actions) { + final String uuidSeed = resource + identity; + 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 (actions.contains(READ_CODE)) { + builder.addAction(RequestAction.READ); + } + + if (actions.contains(WRITE_CODE)) { + builder.addAction(RequestAction.WRITE); + } + + final AccessPolicy accessPolicy = builder.build(); + final Policy jaxbPolicy = createJAXBPolicy(accessPolicy); + authorizations.getPolicies().getPolicy().add(jaxbPolicy); } /** @@ -258,8 +607,8 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { */ private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException { try { - final Marshaller marshaller = JAXB_CONTEXT.createMarshaller(); - marshaller.setSchema(schema); + final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller(); + marshaller.setSchema(authorizationsSchema); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.marshal(authorizations, authorizationsFile); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/Role.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/Role.java new file mode 100644 index 0000000000..0f7b7401e5 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/Role.java @@ -0,0 +1,30 @@ +/* + * 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; + +/** + * Legacy Roles prior to the new authorization model. + */ +public enum Role { + ROLE_MONITOR, + ROLE_PROVENANCE, + ROLE_DFM, + ROLE_ADMIN, + ROLE_PROXY, + ROLE_NIFI; + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java new file mode 100644 index 0000000000..67574022fe --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java @@ -0,0 +1,99 @@ +/* + * 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.nifi.authorization.resource.ResourceType; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Defines the mapping from legacy roles to access policies. + */ +public final class RoleAccessPolicy { + + static final String READ_ACTION = "R"; + static final String READ_WRITE_ACTION = "RW"; + + private final String resource; + private final String actions; + + private RoleAccessPolicy(final String resource, final String actions) { + this.resource = resource; + this.actions = actions; + } + + public String getResource() { + return resource; + } + + public String getActions() { + return actions; + } + + public static Map> getMappings(final String rootGroupId) { + final Map> roleAccessPolicies = new HashMap<>(); + + final Set monitorPolicies = new HashSet<>(); + monitorPolicies.add(new RoleAccessPolicy(ResourceType.Flow.getValue(), READ_ACTION)); + monitorPolicies.add(new RoleAccessPolicy(ResourceType.Controller.getValue(), READ_ACTION)); + monitorPolicies.add(new RoleAccessPolicy(ResourceType.System.getValue(), READ_ACTION)); + if (rootGroupId != null) { + monitorPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); + } + roleAccessPolicies.put(Role.ROLE_MONITOR, Collections.unmodifiableSet(monitorPolicies)); + + final Set provenancePolicies = new HashSet<>(); + provenancePolicies.add(new RoleAccessPolicy(ResourceType.Provenance.getValue(), READ_ACTION)); + roleAccessPolicies.put(Role.ROLE_PROVENANCE, Collections.unmodifiableSet(provenancePolicies)); + + final Set dfmPolicies = new HashSet<>(); + dfmPolicies.add(new RoleAccessPolicy(ResourceType.Flow.getValue(), READ_ACTION)); + dfmPolicies.add(new RoleAccessPolicy(ResourceType.Controller.getValue(), READ_WRITE_ACTION)); + dfmPolicies.add(new RoleAccessPolicy(ResourceType.System.getValue(), READ_ACTION)); + if (rootGroupId != null) { + dfmPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_WRITE_ACTION)); + } + roleAccessPolicies.put(Role.ROLE_DFM, Collections.unmodifiableSet(dfmPolicies)); + + final Set adminPolicies = new HashSet<>(); + adminPolicies.add(new RoleAccessPolicy(ResourceType.Flow.getValue(), READ_ACTION)); + adminPolicies.add(new RoleAccessPolicy(ResourceType.Controller.getValue(), READ_ACTION)); + if (rootGroupId != null) { + adminPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION)); + } + adminPolicies.add(new RoleAccessPolicy(ResourceType.User.getValue(), READ_WRITE_ACTION)); + adminPolicies.add(new RoleAccessPolicy(ResourceType.Group.getValue(), READ_WRITE_ACTION)); + adminPolicies.add(new RoleAccessPolicy(ResourceType.Policy.getValue(), READ_WRITE_ACTION)); + roleAccessPolicies.put(Role.ROLE_ADMIN, Collections.unmodifiableSet(adminPolicies)); + + final Set proxyPolicies = new HashSet<>(); + proxyPolicies.add(new RoleAccessPolicy(ResourceType.Proxy.getValue(), READ_WRITE_ACTION)); + roleAccessPolicies.put(Role.ROLE_PROXY, Collections.unmodifiableSet(proxyPolicies)); + + final Set nifiPolicies = new HashSet<>(); + nifiPolicies.add(new RoleAccessPolicy(ResourceType.Controller.getValue(), READ_ACTION)); + nifiPolicies.add(new RoleAccessPolicy(ResourceType.SiteToSite.getValue(), READ_WRITE_ACTION)); + roleAccessPolicies.put(Role.ROLE_NIFI, Collections.unmodifiableSet(nifiPolicies)); + + return roleAccessPolicies; + } + +} diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java index 0822b60b62..6d18b4b9b8 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java @@ -20,6 +20,7 @@ import org.apache.nifi.attribute.expression.language.StandardPropertyValue; import org.apache.nifi.authorization.AuthorizationResult.Result; import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.resource.ResourceFactory; +import org.apache.nifi.authorization.resource.ResourceType; import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.file.FileUtils; import org.junit.After; @@ -31,6 +32,9 @@ import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertEquals; @@ -97,17 +101,14 @@ public class FileAuthorizerTest { " " + ""; - private static final String UPDATED_AUTHORIZATIONS = - "" - + "" - + "" - + "" - + "" - + ""; + // This is the root group id from the flow.xml.gz in src/test/resources + private static final String ROOT_GROUP_ID = "e530e14c-adcf-41c2-b5d6-d9a59ba8765c"; + private NiFiProperties properties; private FileAuthorizer authorizer; private File primary; private File restore; + private File flow; private AuthorizerConfigurationContext configurationContext; @@ -121,8 +122,12 @@ public class FileAuthorizerTest { restore = new File("target/restore/authorizations.xml"); FileUtils.ensureDirectoryExistAndCanAccess(restore.getParentFile()); - final NiFiProperties properties = mock(NiFiProperties.class); + flow = new File("src/test/resources/flow.xml.gz"); + FileUtils.ensureDirectoryExistAndCanAccess(flow.getParentFile()); + + properties = mock(NiFiProperties.class); when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(flow); configurationContext = mock(AuthorizerConfigurationContext.class); when(configurationContext.getProperty(Mockito.eq("Authorizations File"))).thenReturn(new StandardPropertyValue(primary.getPath(), null)); @@ -139,11 +144,9 @@ public class FileAuthorizerTest { } @Test - public void testOnConfiguredWhenInitialAdminProvided() throws Exception { - final String adminIdentity = "admin-user"; - - when(configurationContext.getProperty(Mockito.eq("Initial Admin Identity"))) - .thenReturn(new StandardPropertyValue(adminIdentity, null)); + public void testOnConfiguredWhenLegacyUsersFileProvidedWithOverlappingRoles() throws Exception { + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-multirole.xml", null)); writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); authorizer.onConfigured(configurationContext); @@ -151,11 +154,159 @@ public class FileAuthorizerTest { final Set users = authorizer.getUsers(); assertEquals(1, users.size()); - final User adminUser = users.iterator().next(); - assertEquals(adminIdentity, adminUser.getIdentity()); + // the user has monitor and DFM, but we should only end up with one policy per resource + // since DFM has RW to all the same resources that monitor has R + UsersAndAccessPolicies usersAndAccessPolicies = authorizer.getUsersAndAccessPolicies(); + assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.Flow.getValue()).size()); + assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.Controller.getValue()).size()); + assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.System.getValue()).size()); + assertEquals(1, usersAndAccessPolicies.getAccessPolicies(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + } + @Test + public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + + // verify all users got created correctly + final Set users = authorizer.getUsers(); + assertEquals(6, users.size()); + + final User user1 = authorizer.getUserByIdentity("user1"); + assertNotNull(user1); + + final User user2 = authorizer.getUserByIdentity("user2"); + assertNotNull(user2); + + final User user3 = authorizer.getUserByIdentity("user3"); + assertNotNull(user3); + + final User user4 = authorizer.getUserByIdentity("user4"); + assertNotNull(user4); + + final User user5 = authorizer.getUserByIdentity("user5"); + assertNotNull(user5); + + final User user6 = authorizer.getUserByIdentity("user6"); + assertNotNull(user6); + + // verify one group got created + final Set groups = authorizer.getGroups(); + assertEquals(1, groups.size()); + assertEquals("group1", groups.iterator().next().getName()); + + // verify more than one policy got created final Set policies = authorizer.getAccessPolicies(); - assertEquals(4, policies.size()); + assertTrue(policies.size() > 0); + + // verify user1's policies + final Map> user1Policies = getResourceActions(policies, user1); + assertEquals(4, user1Policies.size()); + + assertTrue(user1Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user1Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user1Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user1Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(1, user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + assertTrue(user1Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ)); + + // verify user2's policies + final Map> user2Policies = getResourceActions(policies, user2); + assertEquals(1, user2Policies.size()); + + assertTrue(user2Policies.containsKey(ResourceType.Provenance.getValue())); + assertEquals(1, user2Policies.get(ResourceType.Provenance.getValue()).size()); + assertTrue(user2Policies.get(ResourceType.Provenance.getValue()).contains(RequestAction.READ)); + + // verify user3's policies + final Map> user3Policies = getResourceActions(policies, user3); + assertEquals(4, user3Policies.size()); + + assertTrue(user3Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user3Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user3Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user3Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(2, user3Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + + // verify user4's policies + final Map> user4Policies = getResourceActions(policies, user4); + assertEquals(6, user4Policies.size()); + + assertTrue(user4Policies.containsKey(ResourceType.Flow.getValue())); + assertEquals(1, user4Policies.get(ResourceType.Flow.getValue()).size()); + assertTrue(user4Policies.get(ResourceType.Flow.getValue()).contains(RequestAction.READ)); + + assertTrue(user4Policies.containsKey(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID)); + assertEquals(1, user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size()); + assertTrue(user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ)); + + assertTrue(user4Policies.containsKey(ResourceType.User.getValue())); + assertEquals(2, user4Policies.get(ResourceType.User.getValue()).size()); + + assertTrue(user4Policies.containsKey(ResourceType.Group.getValue())); + assertEquals(2, user4Policies.get(ResourceType.Group.getValue()).size()); + + assertTrue(user4Policies.containsKey(ResourceType.Policy.getValue())); + assertEquals(2, user4Policies.get(ResourceType.Policy.getValue()).size()); + + // verify user5's policies + final Map> user5Policies = getResourceActions(policies, user5); + assertEquals(1, user5Policies.size()); + + assertTrue(user5Policies.containsKey(ResourceType.Proxy.getValue())); + assertEquals(2, user5Policies.get(ResourceType.Proxy.getValue()).size()); + + // verify user6's policies + final Map> user6Policies = getResourceActions(policies, user6); + assertEquals(2, user6Policies.size()); + + assertTrue(user6Policies.containsKey(ResourceType.SiteToSite.getValue())); + assertEquals(2, user6Policies.get(ResourceType.SiteToSite.getValue()).size()); + } + + private Map> getResourceActions(final Set policies, final User user) { + Map> resourceActionMap = new HashMap<>(); + + for (AccessPolicy accessPolicy : policies) { + if (accessPolicy.getUsers().contains(user.getIdentifier())) { + Set actions = resourceActionMap.get(accessPolicy.getResource()); + if (actions == null) { + actions = new HashSet<>(); + resourceActionMap.put(accessPolicy.getResource(), actions); + } + actions.addAll(accessPolicy.getActions()); + } + } + + return resourceActionMap; + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception { + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + } + + @Test(expected = AuthorizerCreationException.class) + public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception { + final String adminIdentity = "admin-user"; + + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))) + .thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); } @Test @@ -170,6 +321,113 @@ public class FileAuthorizerTest { assertEquals(0, policies.size()); } + @Test + public void testOnConfiguredWhenInitialAdminProvided() throws Exception { + final String adminIdentity = "admin-user"; + + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + + final Set users = authorizer.getUsers(); + assertEquals(1, users.size()); + + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = authorizer.getAccessPolicies(); + assertEquals(5, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertTrue(foundRootGroupPolicy); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvidedAndNoFlowExists() throws Exception { + // setup NiFi properties to return a file that does not exist + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(new File("src/test/resources/does-not-exist.xml.gz")); + authorizer.setNiFiProperties(properties); + + final String adminIdentity = "admin-user"; + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + + final Set users = authorizer.getUsers(); + assertEquals(1, users.size()); + + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = authorizer.getAccessPolicies(); + assertEquals(4, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertFalse(foundRootGroupPolicy); + } + + @Test + public void testOnConfiguredWhenInitialAdminProvidedAndFlowIsNull() throws Exception { + // setup NiFi properties to return a file that does not exist + properties = mock(NiFiProperties.class); + when(properties.getRestoreDirectory()).thenReturn(restore.getParentFile()); + when(properties.getFlowConfigurationFile()).thenReturn(null); + authorizer.setNiFiProperties(properties); + + final String adminIdentity = "admin-user"; + when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY))) + .thenReturn(new StandardPropertyValue(adminIdentity, null)); + + writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); + authorizer.onConfigured(configurationContext); + + final Set users = authorizer.getUsers(); + assertEquals(1, users.size()); + + final User adminUser = users.iterator().next(); + assertEquals(adminIdentity, adminUser.getIdentity()); + + final Set policies = authorizer.getAccessPolicies(); + assertEquals(4, policies.size()); + + final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID; + + boolean foundRootGroupPolicy = false; + for (AccessPolicy policy : policies) { + if (policy.getResource().equals(rootGroupResource)) { + foundRootGroupPolicy = true; + break; + } + } + + assertFalse(foundRootGroupPolicy); + } + + @Test public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception { writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-multirole.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-multirole.xml new file mode 100644 index 0000000000..493ed4b4c3 --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users-multirole.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users.xml new file mode 100644 index 0000000000..0515a09eec --- /dev/null +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/authorized-users.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow.xml.gz b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/resources/flow.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..c2ac4ede70641be020cd5c1af1cb45e4f48331fa GIT binary patch literal 566 zcmV-60?GX!iwFP!000000CiN`j@mE~edjBpyeA;yT7s$*52g zX0BB3GwH=Uq5ixoX|)mpbW8C%Cy}1x#5p}CJO@43`~Wif&_LO%Dy1c1KMp?6*4zcc z_Awh>sGlUyRIQZ#qE%U(BaAsC5Doee4l|FkZ08Nb?8!?bHuBh%M$?o|#-k{U11zi< zlIB2Ge94_94jTuTxJnhuaEoI)$C=wduk2KPgF2t7JV#|sSZI}jF-li)r6x+*coGg% zZvx|~Hymdpj}8YIqQZ2P4tC?g|NvE~YJLxVn_{QeVbjQK<~U2ls%rd(^4`ZCiHZxH;go;IubvoW-%w zXaqdr&VE7nj7xkqKK8zSlW4o<_pt?i;8|_SSVN&S?stDN24Zp0{o9b properties; - public StandardAuthorizerConfigurationContext(String identifier, String rootGroupId, Map properties) { + public StandardAuthorizerConfigurationContext(String identifier, Map properties) { this.identifier = identifier; - this.rootGroupId = rootGroupId; this.properties = Collections.unmodifiableMap(new HashMap(properties)); } @@ -43,11 +41,6 @@ public class StandardAuthorizerConfigurationContext implements AuthorizerConfigu return identifier; } - @Override - public String getRootGroupId() { - return rootGroupId; - } - @Override public Map getProperties() { return properties; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java index ce8f1bf020..1e9b8c2a3a 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java @@ -22,46 +22,22 @@ import java.util.Objects; public final class ResourceFactory { - private final static Resource FLOW_RESOURCE = new Resource() { + private final static Resource CONNECTION_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/flow"; + return ResourceType.Connection.getValue(); } @Override public String getName() { - return "NiFi Flow"; - } - }; - - private final static Resource RESOURCE_RESOURCE = new Resource() { - @Override - public String getIdentifier() { - return "/resources"; - } - - @Override - public String getName() { - return "NiFi Resources"; - } - }; - - private final static Resource SYSTEM_RESOURCE = new Resource() { - @Override - public String getIdentifier() { - return "/system"; - } - - @Override - public String getName() { - return "System"; + return "Connection"; } }; private final static Resource CONTROLLER_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/controller"; + return ResourceType.Controller.getValue(); } @Override @@ -70,10 +46,130 @@ public final class ResourceFactory { } }; + private final static Resource CONTROLLER_SERVICE_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.ControllerService.getValue(); + } + + @Override + public String getName() { + return "Controller Service"; + } + }; + + private final static Resource FUNNEL_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Funnel.getValue(); + } + + @Override + public String getName() { + return "Funnel"; + } + }; + + private final static Resource FLOW_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Flow.getValue(); + } + + @Override + public String getName() { + return "NiFi Flow"; + } + }; + + private final static Resource GROUP_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Group.getValue(); + } + + @Override + public String getName() { + return "Group"; + } + }; + + private final static Resource INPUT_PORT_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.InputPort.getValue(); + } + + @Override + public String getName() { + return "Input Port"; + } + }; + + private final static Resource LABEL_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Label.getValue(); + } + + @Override + public String getName() { + return "Label"; + } + }; + + private final static Resource OUTPUT_PORT_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.OutputPort.getValue(); + } + + @Override + public String getName() { + return "Output Port"; + } + }; + + private final static Resource POLICY_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Policy.getValue(); + } + + @Override + public String getName() { + return "Policy"; + } + }; + + private final static Resource PROCESSOR_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Processor.getValue(); + } + + @Override + public String getName() { + return "Processor"; + } + }; + + private final static Resource PROCESS_GROUP_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.ProcessGroup.getValue(); + } + + @Override + public String getName() { + return "Process Group"; + } + }; + private final static Resource PROVENANCE_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/provenance"; + return ResourceType.Provenance.getValue(); } @Override @@ -82,22 +178,58 @@ public final class ResourceFactory { } }; - private final static Resource TOKEN_RESOURCE = new Resource() { + private final static Resource PROXY_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/token"; + return ResourceType.Proxy.getValue(); } @Override public String getName() { - return "API access token"; + return "Proxy User Requests"; + } + }; + + private final static Resource REMOTE_PROCESS_GROUP_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.RemoteProcessGroup.getValue(); + } + + @Override + public String getName() { + return "Remote Process Group"; + } + }; + + private final static Resource REPORTING_TASK_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.ReportingTask.getValue(); + } + + @Override + public String getName() { + return "Reporting Task"; + } + }; + + private final static Resource RESOURCE_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Resource.getValue(); + } + + @Override + public String getName() { + return "NiFi Resources"; } }; private final static Resource SITE_TO_SITE_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/site-to-site"; + return ResourceType.SiteToSite.getValue(); } @Override @@ -106,18 +238,90 @@ public final class ResourceFactory { } }; - private final static Resource PROXY_RESOURCE = new Resource() { + private final static Resource SYSTEM_RESOURCE = new Resource() { @Override public String getIdentifier() { - return "/proxy"; + return ResourceType.System.getValue(); } @Override public String getName() { - return "Proxy User Requests"; + return "System"; } }; + private final static Resource TEMPLATE_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Template.getValue(); + } + + @Override + public String getName() { + return "Template"; + } + }; + + private final static Resource TOKEN_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Token.getValue(); + } + + @Override + public String getName() { + return "API access token"; + } + }; + + private final static Resource USER_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.User.getValue(); + } + + @Override + public String getName() { + return "User"; + } + }; + + /** + * Gets the Resource for accessing Connections. + * + * @return The resource for accessing connections + */ + public static Resource getConnectionResource() { + return CONNECTION_RESOURCE; + } + + /** + * Gets the Resource for accessing the Controller. This includes Controller level configuration, bulletins, reporting tasks, and the cluster. + * + * @return The resource for accessing the Controller + */ + public static Resource getControllerResource() { + return CONTROLLER_RESOURCE; + } + + /** + * Gets the Resource for accessing Controller Services. + * + * @return The resource for accessing Controller Services + */ + public static Resource getControllerServiceResource() { + return CONTROLLER_SERVICE_RESOURCE; + } + + /** + * Gets the Resource for accessing Funnels. + * + * @return The resource for accessing Funnels. + */ + public static Resource getFunnelResource() { + return FUNNEL_RESOURCE; + } + /** * Gets the Resource for accessing the NiFi flow. This includes the data flow structure, component status, search results, and banner/about text. * @@ -128,30 +332,66 @@ public final class ResourceFactory { } /** - * Gets the Resource for detailing all available NiFi Resources. + * Gets the Resource for accessing Groups which allows management of user groups. * - * @return The Resource resource + * @return The resource for accessing Groups */ - public static Resource getResourceResource() { - return RESOURCE_RESOURCE; + public static Resource getGroupResource() { + return GROUP_RESOURCE; } /** - * Gets the Resource for accessing details of the System NiFi is running on. + * Gets the Resource for accessing Input Ports. * - * @return The System resource + * @return The resource for accessing Input Ports */ - public static Resource getSystemResource() { - return SYSTEM_RESOURCE; + public static Resource getInputPortResource() { + return INPUT_PORT_RESOURCE; } /** - * Gets the Resource for accessing the Controller. This includes Controller level configuration, bulletins, reporting tasks, and the cluster. + * Gets the Resource for accessing Labels. * - * @return The resource for accessing the Controller + * @return The resource for accessing Labels */ - public static Resource getControllerResource() { - return CONTROLLER_RESOURCE; + public static Resource getLabelResource() { + return LABEL_RESOURCE; + } + + /** + * Gets the Resource for accessing Output Ports. + * + * @return The resource for accessing Output Ports + */ + public static Resource getOutputPortResource() { + return OUTPUT_PORT_RESOURCE; + } + + /** + * Gets the Resource for accessing Policies which allows management of Access Policies. + * + * @return The resource for accessing Policies + */ + public static Resource getPolicyResource() { + return POLICY_RESOURCE; + } + + /** + * Gets the Resource for accessing Processors. + * + * @return The resource for accessing Processors + */ + public static Resource getProcessorResource() { + return PROCESSOR_RESOURCE; + } + + /** + * Gets the Resource for accessing Process Groups. + * + * @return The resource for accessing Process Groups + */ + public static Resource getProcessGroupResource() { + return PROCESS_GROUP_RESOURCE; } /** @@ -165,12 +405,39 @@ public final class ResourceFactory { } /** - * Gets the Resource for creating API access tokens. + * Gets the Resource for proxying a user request. * - * @return The token request resource + * @return The resource for proxying a user request */ - public static Resource getTokenResource() { - return TOKEN_RESOURCE; + public static Resource getProxyResource() { + return PROXY_RESOURCE; + } + + /** + * Gets the Resource for accessing Remote Process Groups. + * + * @return The resource accessing Remote Process Groups + */ + public static Resource getRemoteProcessGroupResource() { + return REMOTE_PROCESS_GROUP_RESOURCE; + } + + /** + * Gets the Resource for accessing Reporting Tasks. + * + * @return The resource for accessing Reporting Tasks + */ + public static Resource getReportingTaskResource() { + return REPORTING_TASK_RESOURCE; + } + + /** + * Gets the Resource for detailing all available NiFi Resources. + * + * @return The Resource resource + */ + public static Resource getResourceResource() { + return RESOURCE_RESOURCE; } /** @@ -184,12 +451,39 @@ public final class ResourceFactory { } /** - * Gets the Resource for proxying a user request. + * Gets the Resource for accessing details of the System NiFi is running on. * - * @return The resource for proxying a user request + * @return The System resource */ - public static Resource getProxyResource() { - return PROXY_RESOURCE; + public static Resource getSystemResource() { + return SYSTEM_RESOURCE; + } + + /** + * Gets the Resource for accessing Templates. + * + * @return The Resource for accessing Tempaltes + */ + public static Resource getTemplateResource() { + return TEMPLATE_RESOURCE; + } + + /** + * Gets the Resource for creating API access tokens. + * + * @return The token request resource + */ + public static Resource getTokenResource() { + return TOKEN_RESOURCE; + } + + /** + * Gets the Resource for accessing Users which includes creating, modifying, and deleting Users. + * + * @return The Resource for accessing Users + */ + public static Resource getUserResource() { + return USER_RESOURCE; } /** diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java index f3e9b6ce4d..784af6dd17 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java @@ -17,17 +17,28 @@ package org.apache.nifi.authorization.resource; public enum ResourceType { - Processor("/processors"), - InputPort("/input-ports"), - OutputPort("/output-ports"), - Funnel("/funnel"), Connection("/connections"), - ProcessGroup("/process-groups"), - RemoteProcessGroup("/remote-process-groups"), - Label("/labels"), + Controller("/controller"), ControllerService("/controller-services"), + Funnel("/funnel"), + Flow("/flow"), + Group("/groups"), + InputPort("/input-ports"), + Label("/labels"), + OutputPort("/output-ports"), + Policy("/policies"), + Processor("/processors"), + ProcessGroup("/process-groups"), + Provenance("/provenance"), + Proxy("/proxy"), + RemoteProcessGroup("/remote-process-groups"), ReportingTask("/reporting-tasks"), - Template("/templates"); + Resource("/resources"), + SiteToSite("/site-to-site"), + System("/system"), + Template("/templates"), + Token("/token"), + User("/users"); final String value; diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml index 74255dc175..12efb79284 100644 --- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml +++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/authorizers.xml @@ -24,5 +24,6 @@ org.apache.nifi.authorization.FileAuthorizer ./conf/authorizations.xml + \ No newline at end of file