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
This commit is contained in:
Bryan Bende 2016-06-06 16:32:19 -04:00 committed by Matt Gilman
parent db9a79f79d
commit 679ad93f57
15 changed files with 1241 additions and 158 deletions

View File

@ -30,11 +30,6 @@ public interface AuthorizerConfigurationContext {
*/ */
String getIdentifier(); String getIdentifier();
/**
* @return the id of the root process group
*/
String getRootGroupId();
/** /**
* Retrieves all properties the component currently understands regardless * Retrieves all properties the component currently understands regardless
* of whether a value has been set for them or not. If no value is present * of whether a value has been set for them or not. If no value is present

View File

@ -23,7 +23,6 @@ import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException; import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.authorization.generated.Authorizers; import org.apache.nifi.authorization.generated.Authorizers;
import org.apache.nifi.authorization.generated.Property; import org.apache.nifi.authorization.generated.Property;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.nar.ExtensionManager; import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable; import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.NiFiProperties;
@ -72,13 +71,8 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
private Authorizer authorizer; private Authorizer authorizer;
private NiFiProperties properties; private NiFiProperties properties;
private FlowController flowController;
private final Map<String, Authorizer> authorizers = new HashMap<>(); private final Map<String, Authorizer> authorizers = new HashMap<>();
public void setFlowController(FlowController flowController) {
this.flowController = flowController;
}
@Override @Override
public Authorizer getAuthorizer(String identifier) { public Authorizer getAuthorizer(String identifier) {
return authorizers.get(identifier); return authorizers.get(identifier);
@ -195,7 +189,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
authorizerProperties.put(property.getName(), property.getValue()); 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 { private void performMethodInjection(final Authorizer instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {

View File

@ -21,7 +21,6 @@
<!-- user/entity authorizer --> <!-- user/entity authorizer -->
<bean id="authorizer" class="org.apache.nifi.authorization.AuthorizerFactoryBean"> <bean id="authorizer" class="org.apache.nifi.authorization.AuthorizerFactoryBean">
<property name="properties" ref="nifiProperties"/> <property name="properties" ref="nifiProperties"/>
<property name="flowController" ref="flowController"/>
</bean> </bean>
</beans> </beans>

View File

@ -93,6 +93,10 @@
<groupId>org.apache.nifi</groupId> <groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-authorization</artifactId> <artifactId>nifi-framework-authorization</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core</artifactId>
</dependency>
<dependency> <dependency>
<groupId>commons-codec</groupId> <groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId> <artifactId>commons-codec</artifactId>

View File

@ -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.Policies;
import org.apache.nifi.authorization.file.generated.Policy; import org.apache.nifi.authorization.file.generated.Policy;
import org.apache.nifi.authorization.file.generated.Users; 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.components.PropertyValue;
import org.apache.nifi.util.NiFiProperties; import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.file.FileUtils; import org.apache.nifi.util.file.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.XMLConstants;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
@ -37,18 +41,29 @@ import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller; import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller; 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.transform.stream.StreamSource;
import javax.xml.validation.Schema; import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.SchemaFactory;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets; 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.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;
/** /**
* Provides identity checks and grants authorities. * Provides identity checks and grants authorities.
@ -56,30 +71,47 @@ import java.util.concurrent.atomic.AtomicReference;
public class FileAuthorizer extends AbstractPolicyBasedAuthorizer { public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
private static final Logger logger = LoggerFactory.getLogger(FileAuthorizer.class); 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"; private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
static final String WRITE_CODE = "W"; 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. * Load the JAXBContext.
*/ */
private static JAXBContext initializeJaxbContext() { private static JAXBContext initializeJaxbContext(final String contextPath) {
try { try {
return JAXBContext.newInstance(JAXB_GENERATED_PATH, FileAuthorizer.class.getClassLoader()); return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
} catch (JAXBException e) { } catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext."); 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 SchemaFactory schemaFactory;
private NiFiProperties properties; private NiFiProperties properties;
private File authorizationsFile; private File authorizationsFile;
private File restoreAuthorizationsFile; private File restoreAuthorizationsFile;
private String rootGroupId; private String rootGroupId;
private String initialAdminIdentity;
private String legacyAuthorizedUsersFile;
private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>(); private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
@ -87,7 +119,9 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
try { try {
schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 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) { } catch (Exception e) {
throw new AuthorizerCreationException(e); throw new AuthorizerCreationException(e);
} }
@ -96,7 +130,7 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
@Override @Override
public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { public void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try { try {
final PropertyValue authorizationsPath = configurationContext.getProperty("Authorizations File"); final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
if (StringUtils.isBlank(authorizationsPath.getValue())) { if (StringUtils.isBlank(authorizationsPath.getValue())) {
throw new AuthorizerCreationException("The authorizations file must be specified."); 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 PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
final String initialAdminIdentity = initialAdminIdentityProp == null ? null : initialAdminIdentityProp.getValue(); 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 the authorizations
load(initialAdminIdentity); load();
// if we've copied the authorizations file to a restore directory synchronize it // if we've copied the authorizations file to a restore directory synchronize it
if (restoreAuthorizationsFile != null) { if (restoreAuthorizationsFile != null) {
@ -145,13 +185,71 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
logger.info(String.format("Authorizations file loaded at %s", new Date().toString())); logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
this.rootGroupId = configurationContext.getRootGroupId();
} catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException e) { } catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException e) {
throw new AuthorizerCreationException(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. * 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 IOException Unable to sync file with restore
* @throws IllegalStateException 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 // attempt to unmarshal
final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(schema); unmarshaller.setSchema(authorizationsSchema);
final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class); final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class);
final Authorizations authorizations = element.getValue(); final Authorizations authorizations = element.getValue();
@ -178,11 +276,24 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
} }
final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations); 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 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 we are starting fresh then we might need to populate an initial admin or convert legacy users
if (hasInitialAdminIdentity && authorizationsHolder.getAllUsers().isEmpty() && authorizationsHolder.getAllPolicies().isEmpty()) { if (emptyAuthorizations) {
populateInitialAdmin(authorizations, initialAdminIdentity);
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); saveAndRefreshHolder(authorizations);
} else { } else {
this.authorizationsHolder.set(authorizationsHolder); 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. * 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 // generate an identifier and add a User with the given identifier and identity
final UUID adminIdentifier = UUID.nameUUIDFromBytes(adminIdentity.getBytes(StandardCharsets.UTF_8)); final UUID adminIdentifier = UUID.nameUUIDFromBytes(initialAdminIdentity.getBytes(StandardCharsets.UTF_8));
final User adminUser = new User.Builder().identifier(adminIdentifier.toString()).identity(adminIdentity).build(); final User adminUser = new User.Builder().identifier(adminIdentifier.toString()).identity(initialAdminIdentity).build();
final org.apache.nifi.authorization.file.generated.User jaxbAdminUser = createJAXBUser(adminUser); final org.apache.nifi.authorization.file.generated.User jaxbAdminUser = createJAXBUser(adminUser);
authorizations.getUsers().getUser().add(jaxbAdminUser); authorizations.getUsers().getUser().add(jaxbAdminUser);
// grant the user read access to the /flow resource // grant the user read access to the /flow resource
final AccessPolicy flowPolicy = createInitialAdminPolicy("/flow", adminUser.getIdentifier(), RequestAction.READ); addAccessPolicy(authorizations, ResourceType.Flow.getValue(), adminUser.getIdentifier(), READ_CODE);
final Policy jaxbFlowPolicy = createJAXBPolicy(flowPolicy);
authorizations.getPolicies().getPolicy().add(jaxbFlowPolicy); // 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 // grant the user read/write access to the /users resource
final AccessPolicy usersPolicy = createInitialAdminPolicy("/users", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); addAccessPolicy(authorizations, ResourceType.User.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
final Policy jaxbUsersPolicy = createJAXBPolicy(usersPolicy);
authorizations.getPolicies().getPolicy().add(jaxbUsersPolicy);
// grant the user read/write access to the /groups resource // grant the user read/write access to the /groups resource
final AccessPolicy groupsPolicy = createInitialAdminPolicy("/groups", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); addAccessPolicy(authorizations, ResourceType.Group.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
final Policy jaxbGroupsPolicy = createJAXBPolicy(groupsPolicy);
authorizations.getPolicies().getPolicy().add(jaxbGroupsPolicy);
// grant the user read/write access to the /policies resource // grant the user read/write access to the /policies resource
final AccessPolicy policiesPolicy = createInitialAdminPolicy("/policies", adminUser.getIdentifier(), RequestAction.READ, RequestAction.WRITE); addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
final Policy jaxbPoliciesPolicy = createJAXBPolicy(policiesPolicy);
authorizations.getPolicies().getPolicy().add(jaxbPoliciesPolicy);
} }
/** /**
* 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 authorizations the current Authorizations instance that users, groups, and policies will be added to
* @param adminIdentity the identity of the admin user to add to the policy * @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
* @param actions the actions for the policy * @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
* @return the AccessPolicy based on the given parameters
*/ */
private AccessPolicy createInitialAdminPolicy(final String resource, final String adminIdentity, final RequestAction ... actions) { private void convertLegacyAuthorizedUsers(final Authorizations authorizations) throws AuthorizerCreationException, JAXBException {
final String uuidSeed = resource + adminIdentity; final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
final UUID flowPolicyIdentifier = UUID.nameUUIDFromBytes(uuidSeed.getBytes(StandardCharsets.UTF_8)); if (!authorizedUsersFile.exists()) {
throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(flowPolicyIdentifier.toString())
.resource(resource)
.addUser(adminIdentity);
for (RequestAction action : actions) {
builder.addAction(action);
} }
return builder.build(); 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(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<Role,Set<RoleAccessPolicy>> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId);
final List<Policy> readPolicies = new ArrayList<>();
final List<Policy> 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<RoleAccessPolicy> policies = roleAccessPolicies.get(role);
for (RoleAccessPolicy roleAccessPolicy : policies) {
// determine if we should use the read policies or read-write policies
List<Policy> 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<Policy> 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<Policy> merge(List<Policy> readPolicies, List<Policy> readWritePolicies) {
final List<Policy> 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<Policy.User> 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<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 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 { private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException {
try { try {
final Marshaller marshaller = JAXB_CONTEXT.createMarshaller(); final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
marshaller.setSchema(schema); marshaller.setSchema(authorizationsSchema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(authorizations, authorizationsFile); marshaller.marshal(authorizations, authorizationsFile);

View File

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

View File

@ -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<Role,Set<RoleAccessPolicy>> getMappings(final String rootGroupId) {
final Map<Role,Set<RoleAccessPolicy>> roleAccessPolicies = new HashMap<>();
final Set<RoleAccessPolicy> 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<RoleAccessPolicy> provenancePolicies = new HashSet<>();
provenancePolicies.add(new RoleAccessPolicy(ResourceType.Provenance.getValue(), READ_ACTION));
roleAccessPolicies.put(Role.ROLE_PROVENANCE, Collections.unmodifiableSet(provenancePolicies));
final Set<RoleAccessPolicy> 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<RoleAccessPolicy> 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<RoleAccessPolicy> proxyPolicies = new HashSet<>();
proxyPolicies.add(new RoleAccessPolicy(ResourceType.Proxy.getValue(), READ_WRITE_ACTION));
roleAccessPolicies.put(Role.ROLE_PROXY, Collections.unmodifiableSet(proxyPolicies));
final Set<RoleAccessPolicy> 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;
}
}

View File

@ -20,6 +20,7 @@ import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.authorization.AuthorizationResult.Result; import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.resource.ResourceFactory; 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.NiFiProperties;
import org.apache.nifi.util.file.FileUtils; import org.apache.nifi.util.file.FileUtils;
import org.junit.After; import org.junit.After;
@ -31,6 +32,9 @@ import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
@ -97,17 +101,14 @@ public class FileAuthorizerTest {
" </policies>" + " </policies>" +
"</authorizations>"; "</authorizations>";
private static final String UPDATED_AUTHORIZATIONS = // This is the root group id from the flow.xml.gz in src/test/resources
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" private static final String ROOT_GROUP_ID = "e530e14c-adcf-41c2-b5d6-d9a59ba8765c";
+ "<resources>"
+ "<resource identifier=\"/flow\">"
+ "<authorization identity=\"user-1\" action=\"RW\"/>"
+ "</resource>"
+ "</resources>";
private NiFiProperties properties;
private FileAuthorizer authorizer; private FileAuthorizer authorizer;
private File primary; private File primary;
private File restore; private File restore;
private File flow;
private AuthorizerConfigurationContext configurationContext; private AuthorizerConfigurationContext configurationContext;
@ -121,8 +122,12 @@ public class FileAuthorizerTest {
restore = new File("target/restore/authorizations.xml"); restore = new File("target/restore/authorizations.xml");
FileUtils.ensureDirectoryExistAndCanAccess(restore.getParentFile()); 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.getRestoreDirectory()).thenReturn(restore.getParentFile());
when(properties.getFlowConfigurationFile()).thenReturn(flow);
configurationContext = mock(AuthorizerConfigurationContext.class); configurationContext = mock(AuthorizerConfigurationContext.class);
when(configurationContext.getProperty(Mockito.eq("Authorizations File"))).thenReturn(new StandardPropertyValue(primary.getPath(), null)); when(configurationContext.getProperty(Mockito.eq("Authorizations File"))).thenReturn(new StandardPropertyValue(primary.getPath(), null));
@ -139,11 +144,9 @@ public class FileAuthorizerTest {
} }
@Test @Test
public void testOnConfiguredWhenInitialAdminProvided() throws Exception { public void testOnConfiguredWhenLegacyUsersFileProvidedWithOverlappingRoles() throws Exception {
final String adminIdentity = "admin-user"; when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
.thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-multirole.xml", null));
when(configurationContext.getProperty(Mockito.eq("Initial Admin Identity")))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);
authorizer.onConfigured(configurationContext); authorizer.onConfigured(configurationContext);
@ -151,11 +154,159 @@ public class FileAuthorizerTest {
final Set<User> users = authorizer.getUsers(); final Set<User> users = authorizer.getUsers();
assertEquals(1, users.size()); assertEquals(1, users.size());
final User adminUser = users.iterator().next(); // the user has monitor and DFM, but we should only end up with one policy per resource
assertEquals(adminIdentity, adminUser.getIdentity()); // 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<User> 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<Group> groups = authorizer.getGroups();
assertEquals(1, groups.size());
assertEquals("group1", groups.iterator().next().getName());
// verify more than one policy got created
final Set<AccessPolicy> policies = authorizer.getAccessPolicies(); final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
assertEquals(4, policies.size()); assertTrue(policies.size() > 0);
// verify user1's policies
final Map<String,Set<RequestAction>> 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<String,Set<RequestAction>> 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<String,Set<RequestAction>> 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<String,Set<RequestAction>> 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<String,Set<RequestAction>> 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<String,Set<RequestAction>> user6Policies = getResourceActions(policies, user6);
assertEquals(2, user6Policies.size());
assertTrue(user6Policies.containsKey(ResourceType.SiteToSite.getValue()));
assertEquals(2, user6Policies.get(ResourceType.SiteToSite.getValue()).size());
}
private Map<String,Set<RequestAction>> getResourceActions(final Set<AccessPolicy> policies, final User user) {
Map<String,Set<RequestAction>> resourceActionMap = new HashMap<>();
for (AccessPolicy accessPolicy : policies) {
if (accessPolicy.getUsers().contains(user.getIdentifier())) {
Set<RequestAction> 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 @Test
@ -170,6 +321,113 @@ public class FileAuthorizerTest {
assertEquals(0, policies.size()); 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<User> users = authorizer.getUsers();
assertEquals(1, users.size());
final User adminUser = users.iterator().next();
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> 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<User> users = authorizer.getUsers();
assertEquals(1, users.size());
final User adminUser = users.iterator().next();
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> 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<User> users = authorizer.getUsers();
assertEquals(1, users.size());
final User adminUser = users.iterator().next();
assertEquals(adminIdentity, adminUser.getIdentity());
final Set<AccessPolicy> 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 @Test
public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception { public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception {
writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE); writeAuthorizationsFile(primary, EMPTY_AUTHORIZATIONS_CONCISE);

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
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.
-->
<users>
<user dn="user1" group="group1">
<role name="ROLE_MONITOR"/>
<role name="ROLE_DFM" />
</user>
</users>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--
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.
-->
<users>
<user dn="user1" group="group1">
<role name="ROLE_MONITOR"/>
</user>
<user dn="user2">
<role name="ROLE_PROVENANCE"/>
</user>
<user dn="user3">
<role name="ROLE_DFM"/>
</user>
<user dn="user4">
<role name="ROLE_ADMIN"/>
</user>
<user dn="user5">
<role name="ROLE_PROXY"/>
</user>
<user dn="user6">
<role name="ROLE_NIFI"/>
</user>
</users>

View File

@ -29,12 +29,10 @@ import java.util.Map;
public class StandardAuthorizerConfigurationContext implements AuthorizerConfigurationContext { public class StandardAuthorizerConfigurationContext implements AuthorizerConfigurationContext {
private final String identifier; private final String identifier;
private final String rootGroupId;
private final Map<String, String> properties; private final Map<String, String> properties;
public StandardAuthorizerConfigurationContext(String identifier, String rootGroupId, Map<String, String> properties) { public StandardAuthorizerConfigurationContext(String identifier, Map<String, String> properties) {
this.identifier = identifier; this.identifier = identifier;
this.rootGroupId = rootGroupId;
this.properties = Collections.unmodifiableMap(new HashMap<String, String>(properties)); this.properties = Collections.unmodifiableMap(new HashMap<String, String>(properties));
} }
@ -43,11 +41,6 @@ public class StandardAuthorizerConfigurationContext implements AuthorizerConfigu
return identifier; return identifier;
} }
@Override
public String getRootGroupId() {
return rootGroupId;
}
@Override @Override
public Map<String, String> getProperties() { public Map<String, String> getProperties() {
return properties; return properties;

View File

@ -22,46 +22,22 @@ import java.util.Objects;
public final class ResourceFactory { public final class ResourceFactory {
private final static Resource FLOW_RESOURCE = new Resource() { private final static Resource CONNECTION_RESOURCE = new Resource() {
@Override @Override
public String getIdentifier() { public String getIdentifier() {
return "/flow"; return ResourceType.Connection.getValue();
} }
@Override @Override
public String getName() { public String getName() {
return "NiFi Flow"; return "Connection";
}
};
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";
} }
}; };
private final static Resource CONTROLLER_RESOURCE = new Resource() { private final static Resource CONTROLLER_RESOURCE = new Resource() {
@Override @Override
public String getIdentifier() { public String getIdentifier() {
return "/controller"; return ResourceType.Controller.getValue();
} }
@Override @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() { private final static Resource PROVENANCE_RESOURCE = new Resource() {
@Override @Override
public String getIdentifier() { public String getIdentifier() {
return "/provenance"; return ResourceType.Provenance.getValue();
} }
@Override @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 @Override
public String getIdentifier() { public String getIdentifier() {
return "/token"; return ResourceType.Proxy.getValue();
} }
@Override @Override
public String getName() { 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() { private final static Resource SITE_TO_SITE_RESOURCE = new Resource() {
@Override @Override
public String getIdentifier() { public String getIdentifier() {
return "/site-to-site"; return ResourceType.SiteToSite.getValue();
} }
@Override @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 @Override
public String getIdentifier() { public String getIdentifier() {
return "/proxy"; return ResourceType.System.getValue();
} }
@Override @Override
public String getName() { 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. * 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() { public static Resource getGroupResource() {
return RESOURCE_RESOURCE; 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() { public static Resource getInputPortResource() {
return SYSTEM_RESOURCE; 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() { public static Resource getLabelResource() {
return CONTROLLER_RESOURCE; 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() { public static Resource getProxyResource() {
return TOKEN_RESOURCE; 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() { public static Resource getSystemResource() {
return PROXY_RESOURCE; 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;
} }
/** /**

View File

@ -17,17 +17,28 @@
package org.apache.nifi.authorization.resource; package org.apache.nifi.authorization.resource;
public enum ResourceType { public enum ResourceType {
Processor("/processors"),
InputPort("/input-ports"),
OutputPort("/output-ports"),
Funnel("/funnel"),
Connection("/connections"), Connection("/connections"),
ProcessGroup("/process-groups"), Controller("/controller"),
RemoteProcessGroup("/remote-process-groups"),
Label("/labels"),
ControllerService("/controller-services"), 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"), ReportingTask("/reporting-tasks"),
Template("/templates"); Resource("/resources"),
SiteToSite("/site-to-site"),
System("/system"),
Template("/templates"),
Token("/token"),
User("/users");
final String value; final String value;

View File

@ -24,5 +24,6 @@
<class>org.apache.nifi.authorization.FileAuthorizer</class> <class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property> <property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity"></property> <property name="Initial Admin Identity"></property>
<property name="Legacy Authorized Users File"></property>
</authorizer> </authorizer>
</authorizers> </authorizers>