http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardAuthorizerFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardAuthorizerFactory.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardAuthorizerFactory.java new file mode 100644 index 0000000..314b9ec --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/StandardAuthorizerFactory.java @@ -0,0 +1,796 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization; + +import org.apache.commons.lang3.StringUtils; +import org.apache.nifi.registry.authorization.annotation.AuthorizerContext; +import org.apache.nifi.registry.authorization.exception.AuthorizationAccessException; +import org.apache.nifi.registry.authorization.exception.AuthorizerCreationException; +import org.apache.nifi.registry.authorization.exception.AuthorizerDestructionException; +import org.apache.nifi.registry.authorization.exception.UninheritableAuthorizationsException; +import org.apache.nifi.registry.authorization.generated.Authorizers; +import org.apache.nifi.registry.authorization.generated.Prop; +import org.apache.nifi.registry.properties.NiFiRegistryProperties; +import org.apache.nifi.registry.provider.StandardProviderFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Unmarshaller; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * This implementation of AuthorizerFactory in NiFi Registry is based on a combination of + * NiFi's AuthorizerFactory and AuthorizerFactoryBean. + */ +public class StandardAuthorizerFactory implements AuthorizerFactory, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup{ + + private static final Logger logger = LoggerFactory.getLogger(StandardProviderFactory.class); + + private static final String AUTHORIZERS_XSD = "/authorizers.xsd"; + private static final String JAXB_GENERATED_PATH = "org.apache.nifi.registry.authorization.generated"; + private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); + + /** + * Load the JAXBContext. + */ + private static JAXBContext initializeJaxbContext() { + try { + return JAXBContext.newInstance(JAXB_GENERATED_PATH, StandardAuthorizerFactory.class.getClassLoader()); + } catch (JAXBException e) { + throw new RuntimeException("Unable to create JAXBContext.", e); + } + } + + private final NiFiRegistryProperties properties; + private Authorizer authorizer; + private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>(); + private final Map<String, AccessPolicyProvider> accessPolicyProviders = new HashMap<>(); + private final Map<String, Authorizer> authorizers = new HashMap<>(); + + public StandardAuthorizerFactory(final NiFiRegistryProperties properties) { + this.properties = properties; + + if (this.properties == null) { + throw new IllegalStateException("NiFiRegistryProperties cannot be null"); + } + } + + /***** UserGroupProviderLookup *****/ + + @Override + public UserGroupProvider getUserGroupProvider(String identifier) { + return userGroupProviders.get(identifier); + } + + /***** AccessPolicyProviderLookup *****/ + + @Override + public AccessPolicyProvider getAccessPolicyProvider(String identifier) { + return accessPolicyProviders.get(identifier); + } + + + /***** AuthorizerLookup *****/ + + @Override + public Authorizer getAuthorizer(String identifier) { + return authorizers.get(identifier); + } + + + /***** AuthorizerFactory *****/ + + @Override + public void initialize() throws AuthorizerFactoryException { +// if (authorizerHolder.get() == null) { +// final File authorizersConfigFile = properties.getAuthorizersConfigurationFile(); +// if (authorizersConfigFile.exists()) { +// try { +// // find the schema +// final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); +// final Schema schema = schemaFactory.newSchema(StandardProviderFactory.class.getResource(AUTHORIZERS_XSD)); +// +// // attempt to unmarshal +// final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); +// unmarshaller.setSchema(schema); +// +// // set the holder for later use +// final JAXBElement<Authorizers> element = unmarshaller.unmarshal(new StreamSource(authorizersConfigFile), Authorizers.class); +// authorizerHolder.set(element.getValue()); +// } catch (SAXException | JAXBException e) { +// throw new AuthorizerFactoryException("Unable to load the authorizer configuration file at: " + authorizersConfigFile.getAbsolutePath(), e); +// } +// } else { +// throw new AuthorizerFactoryException("Unable to find the providers configuration file at " + authorizersConfigFile.getAbsolutePath()); +// } +// } + } + + @Override + public Authorizer getAuthorizer() throws AuthorizerFactoryException { + if (authorizer == null) { + if (properties.getSslPort() == null) { + // use a default authorizer... only allowable when running not securely + authorizer = createDefaultAuthorizer(); + } else { + // look up the authorizer to use + final String authorizerIdentifier = properties.getProperty(NiFiRegistryProperties.SECURITY_AUTHORIZER); + + // ensure the authorizer class name was specified + if (StringUtils.isBlank(authorizerIdentifier)) { + throw new AuthorizerFactoryException("When running securely, the authorizer identifier must be specified in the nifi properties file."); + } else { + + try { + final Authorizers authorizerConfiguration = loadAuthorizersConfiguration(); + + // create each user group provider + for (final org.apache.nifi.registry.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) { + userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz())); + } + + // configure each user group provider + for (final org.apache.nifi.registry.authorization.generated.UserGroupProvider provider : authorizerConfiguration.getUserGroupProvider()) { + final UserGroupProvider instance = userGroupProviders.get(provider.getIdentifier()); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); + } + + // create each access policy provider + for (final org.apache.nifi.registry.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) { + accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz())); + } + + // configure each access policy provider + for (final org.apache.nifi.registry.authorization.generated.AccessPolicyProvider provider : authorizerConfiguration.getAccessPolicyProvider()) { + final AccessPolicyProvider instance = accessPolicyProviders.get(provider.getIdentifier()); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); + } + + // create each authorizer + for (final org.apache.nifi.registry.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) { + authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz(), authorizer.getClasspath())); + } + + // configure each authorizer + for (final org.apache.nifi.registry.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) { + final Authorizer instance = authorizers.get(provider.getIdentifier()); + instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty())); + } + + // get the authorizer instance + authorizer = getAuthorizer(authorizerIdentifier); + + // ensure it was found + if (authorizer == null) { + throw new AuthorizerFactoryException(String.format("The specified authorizer '%s' could not be found.", authorizerIdentifier)); + } + } catch (Exception e) { + throw new AuthorizerFactoryException("Failed to construct Authorizer.", e); + } + } + } + } + return authorizer; + } + + private Authorizers loadAuthorizersConfiguration() throws Exception { + final File authorizersConfigurationFile = properties.getAuthorizersConfigurationFile(); + + // load the authorizers from the specified file + if (authorizersConfigurationFile.exists()) { + try { + // find the schema + final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + final Schema schema = schemaFactory.newSchema(Authorizers.class.getResource(AUTHORIZERS_XSD)); + + // attempt to unmarshal + final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); + unmarshaller.setSchema(schema); + final JAXBElement<Authorizers> element = unmarshaller.unmarshal(new StreamSource(authorizersConfigurationFile), Authorizers.class); + return element.getValue(); + } catch (SAXException | JAXBException e) { + throw new Exception("Unable to load the authorizer configuration file at: " + authorizersConfigurationFile.getAbsolutePath(), e); + } + } else { + throw new Exception("Unable to find the authorizer configuration file at " + authorizersConfigurationFile.getAbsolutePath()); + } + } + + private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Prop> properties) { + final Map<String, String> authorizerProperties = new HashMap<>(); + + for (final Prop property : properties) { + authorizerProperties.put(property.getName(), property.getValue()); + } + return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties); + } + + private UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception { + + final UserGroupProvider instance; + + // attempt to load the class + Class<?> rawUserGroupProviderClass = Class.forName(userGroupProviderClassName); + Class<? extends UserGroupProvider> userGroupProviderClass = rawUserGroupProviderClass.asSubclass(UserGroupProvider.class); + + // otherwise create a new instance + Constructor constructor = userGroupProviderClass.getConstructor(); + instance = (UserGroupProvider) constructor.newInstance(); + + // method injection + performMethodInjection(instance, userGroupProviderClass); + + // field injection + performFieldInjection(instance, userGroupProviderClass); + + // call post construction lifecycle event + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); + + return instance; + } + + private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception { + final AccessPolicyProvider instance; + + // attempt to load the class + Class<?> rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName); + Class<? extends AccessPolicyProvider> accessPolicyClass = rawAccessPolicyProviderClass.asSubclass(AccessPolicyProvider.class); + + // otherwise create a new instance + Constructor constructor = accessPolicyClass.getConstructor(); + instance = (AccessPolicyProvider) constructor.newInstance(); + + // method injection + performMethodInjection(instance, accessPolicyClass); + + // field injection + performFieldInjection(instance, accessPolicyClass); + + // call post construction lifecycle event + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); + + return instance; + } + + private Authorizer createAuthorizer(final String identifier, final String authorizerClassName, final String classpathResources) throws Exception { + final Authorizer instance; + // attempt to load the class + Class<?> rawAuthorizerClass = Class.forName(authorizerClassName); + Class<? extends Authorizer> authorizerClass = rawAuthorizerClass.asSubclass(Authorizer.class); + + // otherwise create a new instance + Constructor constructor = authorizerClass.getConstructor(); + instance = (Authorizer) constructor.newInstance(); + + // method injection + performMethodInjection(instance, authorizerClass); + + // field injection + performFieldInjection(instance, authorizerClass); + + // call post construction lifecycle event + instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this)); + + // TODO, implement and test loading additional resources from the classpath for custom authorizer impls. +// if (StringUtils.isNotEmpty(classpathResources)) { +// URL[] urls = ClassLoaderUtils.getURLsForClasspath(classpathResources, null, true); +// authorizerClassLoader = new URLClassLoader(urls, authorizerClassLoader); +// } + + return installIntegrityChecks(instance); + } + + private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { + for (final Method method : authorizerClass.getMethods()) { + if (method.isAnnotationPresent(AuthorizerContext.class)) { + // make the method accessible + final boolean isAccessible = method.isAccessible(); + method.setAccessible(true); + + try { + final Class<?>[] argumentTypes = method.getParameterTypes(); + + // look for setters (single argument) + if (argumentTypes.length == 1) { + final Class<?> argumentType = argumentTypes[0]; + + // look for well known types + if (NiFiRegistryProperties.class.isAssignableFrom(argumentType)) { + // nifi properties injection + method.invoke(instance, properties); + } + } + } finally { + method.setAccessible(isAccessible); + } + } + } + + final Class parentClass = authorizerClass.getSuperclass(); + if (parentClass != null && Authorizer.class.isAssignableFrom(parentClass)) { + performMethodInjection(instance, parentClass); + } + } + + private void performFieldInjection(final Object instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException { + for (final Field field : authorizerClass.getDeclaredFields()) { + if (field.isAnnotationPresent(AuthorizerContext.class)) { + // make the method accessible + final boolean isAccessible = field.isAccessible(); + field.setAccessible(true); + + try { + // get the type + final Class<?> fieldType = field.getType(); + + // only consider this field if it isn't set yet + if (field.get(instance) == null) { + // look for well known types + if (NiFiRegistryProperties.class.isAssignableFrom(fieldType)) { + // nifi properties injection + field.set(instance, properties); + } + } + + } finally { + field.setAccessible(isAccessible); + } + } + } + + final Class parentClass = authorizerClass.getSuperclass(); + if (parentClass != null && Authorizer.class.isAssignableFrom(parentClass)) { + performFieldInjection(instance, parentClass); + } + } + + + /** + * @return a default Authorizer to use when running unsecurely with no authorizer configured + */ + private Authorizer createDefaultAuthorizer() { + return new Authorizer() { + @Override + public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException { + return AuthorizationResult.approved(); + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + } + }; + } + + public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) { + if (baseAuthorizer instanceof ManagedAuthorizer) { + final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer; + return new ManagedAuthorizer() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseManagedAuthorizer.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseManagedAuthorizer.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseManagedAuthorizer.checkInheritability(proposedFingerprint); + } + + @Override + public AccessPolicyProvider getAccessPolicyProvider() { + final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider(); + if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { + final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider; + return new ConfigurableAccessPolicyProvider() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint); + } + + @Override + public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) { + throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); + } + return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy); + } + + @Override + public boolean isConfigurable(AccessPolicy accessPolicy) { + return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy); + } + + @Override + public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) { + throw new IllegalArgumentException("The specified access policy is not support modification."); + } + return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { + if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) { + throw new IllegalArgumentException("The specified access policy is not support modification."); + } + return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy); + } + + @Override + public AccessPolicy deleteAccessPolicy(String accessPolicyIdentifier) throws AuthorizationAccessException { + if (!baseConfigurableAccessPolicyProvider.isConfigurable(baseConfigurableAccessPolicyProvider.getAccessPolicy(accessPolicyIdentifier))) { + throw new IllegalArgumentException("The specified access policy is not support modification."); + } + return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicyIdentifier); + } + + @Override + public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicies(); + } + + @Override + public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier); + } + + @Override + public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException { + return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action); + } + + @Override + public UserGroupProvider getUserGroupProvider() { + final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider(); + if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) { + final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider; + return new ConfigurableUserGroupProvider() { + @Override + public String getFingerprint() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getFingerprint(); + } + + @Override + public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException { + baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint); + } + + @Override + public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException { + baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint); + } + + @Override + public User addUser(User user) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); + } + return baseConfigurableUserGroupProvider.addUser(user); + } + + @Override + public boolean isConfigurable(User user) { + return baseConfigurableUserGroupProvider.isConfigurable(user); + } + + @Override + public User updateUser(User user) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); + } + if (!baseConfigurableUserGroupProvider.isConfigurable(user)) { + throw new IllegalArgumentException("The specified user does not support modification."); + } + return baseConfigurableUserGroupProvider.updateUser(user); + } + + @Override + public User deleteUser(User user) throws AuthorizationAccessException { + if (!baseConfigurableUserGroupProvider.isConfigurable(user)) { + throw new IllegalArgumentException("The specified user does not support modification."); + } + return baseConfigurableUserGroupProvider.deleteUser(user); + } + + @Override + public User deleteUser(String userIdentifier) throws AuthorizationAccessException { + if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getUser(userIdentifier))) { + throw new IllegalArgumentException("The specified user does not support modification."); + } + return baseConfigurableUserGroupProvider.deleteUser(userIdentifier); + } + + @Override + public Group addGroup(Group group) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); + } + return baseConfigurableUserGroupProvider.addGroup(group); + } + + @Override + public boolean isConfigurable(Group group) { + return baseConfigurableUserGroupProvider.isConfigurable(group); + } + + @Override + public Group updateGroup(Group group) throws AuthorizationAccessException { + if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { + throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); + } + if (!baseConfigurableUserGroupProvider.isConfigurable(group)) { + throw new IllegalArgumentException("The specified group does not support modification."); + } + return baseConfigurableUserGroupProvider.updateGroup(group); + } + + @Override + public Group deleteGroup(Group group) throws AuthorizationAccessException { + if (!baseConfigurableUserGroupProvider.isConfigurable(group)) { + throw new IllegalArgumentException("The specified group does not support modification."); + } + return baseConfigurableUserGroupProvider.deleteGroup(group); + } + + @Override + public Group deleteGroup(String groupId) throws AuthorizationAccessException { + if (!baseConfigurableUserGroupProvider.isConfigurable(baseConfigurableUserGroupProvider.getGroup(groupId))) { + throw new IllegalArgumentException("The specified group does not support modification."); + } + return baseConfigurableUserGroupProvider.deleteGroup(groupId); + } + + @Override + public Set<User> getUsers() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUsers(); + } + + @Override + public User getUser(String identifier) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUser(identifier); + } + + @Override + public User getUserByIdentity(String identity) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUserByIdentity(identity); + } + + @Override + public Set<Group> getGroups() throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getGroups(); + } + + @Override + public Group getGroup(String identifier) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getGroup(identifier); + } + + @Override + public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException { + return baseConfigurableUserGroupProvider.getUserAndGroups(identity); + } + + @Override + public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException { + baseConfigurableUserGroupProvider.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseConfigurableUserGroupProvider.onConfigured(configurationContext); + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseConfigurableUserGroupProvider.preDestruction(); + } + }; + } else { + return baseUserGroupProvider; + } + } + + @Override + public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException { + baseConfigurableAccessPolicyProvider.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseConfigurableAccessPolicyProvider.onConfigured(configurationContext); + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseConfigurableAccessPolicyProvider.preDestruction(); + } + }; + } else { + return baseAccessPolicyProvider; + } + } + + @Override + public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { + final AuthorizationResult result = baseAuthorizer.authorize(request); + + // audit the authorization request + audit(baseAuthorizer, request, result); + + return result; + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + baseManagedAuthorizer.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseManagedAuthorizer.onConfigured(configurationContext); + + final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider(); + final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider(); + + // ensure that only one policy per resource-action exists + for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) { + if (policyExists(accessPolicyProvider, accessPolicy)) { + throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction())); + } + } + + // ensure that only one group exists per identity + for (User user : userGroupProvider.getUsers()) { + if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) { + throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity())); + } + } + + // ensure that only one group exists per identity + for (Group group : userGroupProvider.getGroups()) { + if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) { + throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName())); + } + } + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseManagedAuthorizer.preDestruction(); + } + }; + } else { + return new Authorizer() { + @Override + public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException { + final AuthorizationResult result = baseAuthorizer.authorize(request); + + // audit the authorization request + audit(baseAuthorizer, request, result); + + return result; + } + + @Override + public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException { + baseAuthorizer.initialize(initializationContext); + } + + @Override + public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { + baseAuthorizer.onConfigured(configurationContext); + } + + @Override + public void preDestruction() throws AuthorizerDestructionException { + baseAuthorizer.preDestruction(); + } + }; + } + } + + private static void audit(final Authorizer authorizer, final AuthorizationRequest request, final AuthorizationResult result) { + // audit when... + // 1 - the authorizer supports auditing + // 2 - the request is an access attempt + // 3 - the result is either approved/denied, when resource is not found a subsequent request may be following with the parent resource + if (authorizer instanceof AuthorizationAuditor && request.isAccessAttempt() && !AuthorizationResult.Result.ResourceNotFound.equals(result.getResult())) { + ((AuthorizationAuditor) authorizer).auditAccessAttempt(request, result); + } + } + + /** + * Checks if another policy exists with the same resource and action as the given policy. + * + * @param checkAccessPolicy an access policy being checked + * @return true if another access policy exists with the same resource and action, false otherwise + */ + private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) { + for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) { + if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier()) + && accessPolicy.getResource().equals(checkAccessPolicy.getResource()) + && accessPolicy.getAction().equals(checkAccessPolicy.getAction())) { + return true; + } + } + return false; + } + + /** + * Checks if another user exists with the same identity. + * + * @param identifier identity of the user + * @param identity identity of the user + * @return true if another user exists with the same identity, false otherwise + */ + private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) { + for (User user : userGroupProvider.getUsers()) { + if (!user.getIdentifier().equals(identifier) + && user.getIdentity().equals(identity)) { + return true; + } + } + + for (Group group : userGroupProvider.getGroups()) { + if (!group.getIdentifier().equals(identifier) + && group.getName().equals(identity)) { + return true; + } + } + + return false; + } + +}
http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/AccessPolicyAuthorizable.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/AccessPolicyAuthorizable.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/AccessPolicyAuthorizable.java new file mode 100644 index 0000000..5d9997f --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/AccessPolicyAuthorizable.java @@ -0,0 +1,122 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.resource; + +import org.apache.nifi.registry.authorization.exception.AccessDeniedException; +import org.apache.nifi.registry.authorization.AuthorizationResult; +import org.apache.nifi.registry.authorization.AuthorizationResult.Result; +import org.apache.nifi.registry.authorization.Authorizer; +import org.apache.nifi.registry.authorization.RequestAction; +import org.apache.nifi.registry.authorization.Resource; +import org.apache.nifi.registry.authorization.user.NiFiUser; + +import java.util.Map; + +/** + * Authorizable for policies of an Authorizable. + */ +public class AccessPolicyAuthorizable implements Authorizable, EnforcePolicyPermissionsThroughBaseResource { + + private static final Authorizable POLICIES_AUTHORIZABLE = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return null; + } + + @Override + public Resource getResource() { + return ResourceFactory.getPoliciesResource(); + } + }; + + final Authorizable authorizable; + + public AccessPolicyAuthorizable(Authorizable authorizable) { + this.authorizable = authorizable; + } + + @Override + public Authorizable getBaseAuthorizable() { + return authorizable; + } + + @Override + public Authorizable getParentAuthorizable() { + final Authorizable effectiveAuthorizable = getEffectiveAuthorizable(); + if (effectiveAuthorizable.getParentAuthorizable() == null) { + return POLICIES_AUTHORIZABLE; + } else { + return new AccessPolicyAuthorizable(effectiveAuthorizable.getParentAuthorizable()); + } + } + + @Override + public Resource getResource() { + return ResourceFactory.getPolicyResource(getEffectiveAuthorizable().getResource()); + } + + private Authorizable getEffectiveAuthorizable() { + // possibly consider the base resource if the authorizable uses it to enforce policy permissions + if (authorizable instanceof EnforcePolicyPermissionsThroughBaseResource) { + final Authorizable baseAuthorizable = ((EnforcePolicyPermissionsThroughBaseResource) authorizable).getBaseAuthorizable(); + + // if the base authorizable is for a policy, we don't want to use the base otherwise it would keep unwinding and would eventually + // evaluate to the policy of the component and not the policy of the policies for the component + if (baseAuthorizable instanceof AccessPolicyAuthorizable) { + return authorizable; + } else { + return baseAuthorizable; + } + } else { + return authorizable; + } + } + + @Override + public AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) { + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + final AuthorizationResult resourceResult = Authorizable.super.checkAuthorization(authorizer, action, user, resourceContext); + + // if we're denied from the resource try inheriting + if (Result.Denied.equals(resourceResult.getResult())) { + return getParentAuthorizable().checkAuthorization(authorizer, action, user, resourceContext); + } else { + return resourceResult; + } + } + + @Override + public void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + try { + Authorizable.super.authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException resourceDenied) { + // if we're denied from the resource try inheriting + try { + getParentAuthorizable().authorize(authorizer, action, user, resourceContext); + } catch (final AccessDeniedException policiesDenied) { + throw resourceDenied; + } + } + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java new file mode 100644 index 0000000..3e694e7 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/Authorizable.java @@ -0,0 +1,300 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.resource; + +import org.apache.nifi.registry.authorization.AuthorizationResult.Result; +import org.apache.nifi.registry.authorization.exception.AccessDeniedException; +import org.apache.nifi.registry.authorization.user.NiFiUser; +import org.apache.nifi.registry.authorization.AuthorizationAuditor; +import org.apache.nifi.registry.authorization.AuthorizationRequest; +import org.apache.nifi.registry.authorization.AuthorizationResult; +import org.apache.nifi.registry.authorization.Authorizer; +import org.apache.nifi.registry.authorization.RequestAction; +import org.apache.nifi.registry.authorization.Resource; +import org.apache.nifi.registry.authorization.UserContextKeys; + +import java.util.HashMap; +import java.util.Map; + +public interface Authorizable { + + /** + * The parent for this Authorizable. May be null. + * + * @return the parent authorizable or null + */ + Authorizable getParentAuthorizable(); + + /** + * The Resource for this Authorizable. + * + * @return the resource + */ + Resource getResource(); + + /** + * The originally requested resource for this Authorizable. Because policies are inherited, if a resource + * does not have a policy, this Authorizable may represent a parent resource and this method will return + * the originally requested resource. + * + * @return the originally requested resource + */ + default Resource getRequestedResource() { + return getResource(); + } + + /** + * Returns whether the current user is authorized for the specified action on the specified resource. This + * method does not imply the user is directly attempting to access the specified resource. If the user is + * attempting a direct access use Authorizable.authorize(). + * + * @param authorizer authorizer + * @param action action + * @return is authorized + */ + default boolean isAuthorized(Authorizer authorizer, RequestAction action, NiFiUser user) { + return Result.Approved.equals(checkAuthorization(authorizer, action, user).getResult()); + } + + /** + * Returns the result of an authorization request for the specified user for the specified action on the specified + * resource. This method does not imply the user is directly attempting to access the specified resource. If the user is + * attempting a direct access use Authorizable.authorize(). + * + * @param authorizer authorizer + * @param action action + * @param user user + * @return is authorized + */ + default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) { + if (user == null) { + return AuthorizationResult.denied("Unknown user."); + } + + final Map<String,String> userContext; + if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) { + userContext = new HashMap<>(); + userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress()); + } else { + userContext = null; + } + + final Resource resource = getResource(); + final Resource requestedResource = getRequestedResource(); + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(user.getIdentity()) + .groups(user.getGroups()) + .anonymous(user.isAnonymous()) + .accessAttempt(false) + .action(action) + .resource(resource) + .requestedResource(requestedResource) + .resourceContext(resourceContext) + .userContext(userContext) + .explanationSupplier(() -> { + // build the safe explanation + final StringBuilder safeDescription = new StringBuilder("Unable to "); + + if (RequestAction.READ.equals(action)) { + safeDescription.append("view "); + } else { + safeDescription.append("modify "); + } + safeDescription.append(resource.getSafeDescription()).append("."); + + return safeDescription.toString(); + }) + .build(); + + // perform the authorization + final AuthorizationResult result = authorizer.authorize(request); + + // verify the results + if (Result.ResourceNotFound.equals(result.getResult())) { + final Authorizable parent = getParentAuthorizable(); + if (parent == null) { + return AuthorizationResult.denied("No applicable policies could be found."); + } else { + // create a custom authorizable to override the safe description but still defer to the parent authorizable + final Authorizable parentProxy = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return parent.getParentAuthorizable(); + } + + @Override + public Resource getRequestedResource() { + return requestedResource; + } + + @Override + public Resource getResource() { + final Resource parentResource = parent.getResource(); + return new Resource() { + @Override + public String getIdentifier() { + return parentResource.getIdentifier(); + } + + @Override + public String getName() { + return parentResource.getName(); + } + + @Override + public String getSafeDescription() { + return resource.getSafeDescription(); + } + }; + } + }; + return parentProxy.checkAuthorization(authorizer, action, user, resourceContext); + } + } else { + return result; + } + } + + /** + * Returns the result of an authorization request for the specified user for the specified action on the specified + * resource. This method does not imply the user is directly attempting to access the specified resource. If the user is + * attempting a direct access use Authorizable.authorize(). + * + * @param authorizer authorizer + * @param action action + * @param user user + * @return is authorized + */ + default AuthorizationResult checkAuthorization(Authorizer authorizer, RequestAction action, NiFiUser user) { + return checkAuthorization(authorizer, action, user, null); + } + + /** + * Authorizes the current user for the specified action on the specified resource. This method does imply the user is + * directly accessing the specified resource. + * + * @param authorizer authorizer + * @param action action + * @param user user + * @param resourceContext resource context + */ + default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user, Map<String, String> resourceContext) throws AccessDeniedException { + if (user == null) { + throw new AccessDeniedException("Unknown user."); + } + + final Map<String,String> userContext; + if (user.getClientAddress() != null && !user.getClientAddress().trim().isEmpty()) { + userContext = new HashMap<>(); + userContext.put(UserContextKeys.CLIENT_ADDRESS.name(), user.getClientAddress()); + } else { + userContext = null; + } + + final Resource resource = getResource(); + final Resource requestedResource = getRequestedResource(); + final AuthorizationRequest request = new AuthorizationRequest.Builder() + .identity(user.getIdentity()) + .groups(user.getGroups()) + .anonymous(user.isAnonymous()) + .accessAttempt(true) + .action(action) + .resource(resource) + .requestedResource(requestedResource) + .resourceContext(resourceContext) + .userContext(userContext) + .explanationSupplier(() -> { + // build the safe explanation + final StringBuilder safeDescription = new StringBuilder("Unable to "); + + if (RequestAction.READ.equals(action)) { + safeDescription.append("view "); + } else { + safeDescription.append("modify "); + } + safeDescription.append(resource.getSafeDescription()).append("."); + + return safeDescription.toString(); + }) + .build(); + + final AuthorizationResult result = authorizer.authorize(request); + if (Result.ResourceNotFound.equals(result.getResult())) { + final Authorizable parent = getParentAuthorizable(); + if (parent == null) { + final AuthorizationResult failure = AuthorizationResult.denied("No applicable policies could be found."); + + // audit authorization request + if (authorizer instanceof AuthorizationAuditor) { + ((AuthorizationAuditor) authorizer).auditAccessAttempt(request, failure); + } + + // denied + throw new AccessDeniedException(failure.getExplanation()); + } else { + // create a custom authorizable to override the safe description but still defer to the parent authorizable + final Authorizable parentProxy = new Authorizable() { + @Override + public Authorizable getParentAuthorizable() { + return parent.getParentAuthorizable(); + } + + @Override + public Resource getRequestedResource() { + return requestedResource; + } + + @Override + public Resource getResource() { + final Resource parentResource = parent.getResource(); + return new Resource() { + @Override + public String getIdentifier() { + return parentResource.getIdentifier(); + } + + @Override + public String getName() { + return parentResource.getName(); + } + + @Override + public String getSafeDescription() { + return resource.getSafeDescription(); + } + }; + } + }; + parentProxy.authorize(authorizer, action, user, resourceContext); + } + } else if (Result.Denied.equals(result.getResult())) { + throw new AccessDeniedException(result.getExplanation()); + } + } + + /** + * Authorizes the current user for the specified action on the specified resource. This method does imply the user is + * directly accessing the specified resource. + * + * @param authorizer authorizer + * @param action action + * @param user user + */ + default void authorize(Authorizer authorizer, RequestAction action, NiFiUser user) throws AccessDeniedException { + authorize(authorizer, action, user, null); + } +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java new file mode 100644 index 0000000..2d6b1a8 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/EnforcePolicyPermissionsThroughBaseResource.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.resource; + +/** + * Defers permissions on policies to the policies of the base authorizable. Required because we don't + * want to change the enforcement of the policies on the authorizable. For example... + * + * if a user has permissions to /policies/input-ports/1234 then they have permissions to the following + * + * - the policy for /buckets/1234 -> /policies/buckets/1234 + * - the policy for /policies/buckets/1234 -> /policies/policies/buckets/1234 + */ +public interface EnforcePolicyPermissionsThroughBaseResource { + + /** + * Returns the base authorizable. Cannot be null. + * + * @return base authorizable + */ + Authorizable getBaseAuthorizable(); +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceFactory.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceFactory.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceFactory.java new file mode 100644 index 0000000..1d0b023 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceFactory.java @@ -0,0 +1,261 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.resource; + +import org.apache.nifi.registry.authorization.Resource; + +import java.util.Objects; + +public final class ResourceFactory { + + private final static Resource BUCKETS_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Bucket.getValue(); + } + + @Override + public String getName() { + return "Buckets"; + } + + @Override + public String getSafeDescription() { + return "buckets"; + } + }; + + + private final static Resource POLICY_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Policy.getValue(); + } + + @Override + public String getName() { + return "Policies for "; + } + + @Override + public String getSafeDescription() { + return "the policies for "; + } + }; + + private final static Resource PROXY_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Proxy.getValue(); + } + + @Override + public String getName() { + return "Proxy User Requests"; + } + + @Override + public String getSafeDescription() { + return "proxy requests on behalf of users"; + } + }; + + private final static Resource RESOURCE_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Resource.getValue(); + } + + @Override + public String getName() { + return "NiFi Resources"; + } + + @Override + public String getSafeDescription() { + return "resources"; + } + }; + + private final static Resource TENANT_RESOURCE = new Resource() { + @Override + public String getIdentifier() { + return ResourceType.Tenant.getValue(); + } + + @Override + public String getName() { + return "Tenant"; + } + + @Override + public String getSafeDescription() { + return "users/user groups"; + } + }; + + private final static Resource POLICIES_RESOURCE = new Resource() { + + @Override + public String getIdentifier() { + return "/policies"; + } + + @Override + public String getName() { + return "Access Policies"; + } + + @Override + public String getSafeDescription() { + return "policies"; + } + }; + + + /** + * Gets the Resource for proxying a user request. + * + * @return The resource for proxying a user request + */ + public static Resource getProxyResource() { + return PROXY_RESOURCE; + } + + /** + * Gets the Resource for detailing all available NiFi Resources. + * + * @return The Resource resource + */ + public static Resource getResourceResource() { + return RESOURCE_RESOURCE; + } + + /** + * Gets the Resource for accessing Tenants which includes creating, modifying, and deleting Users and UserGroups. + * + * @return The Resource for accessing Tenants + */ + public static Resource getTenantResource() { + return TENANT_RESOURCE; + } + + /** + * Gets the {@link Resource} for accessing access policies. + * @return The policies resource + */ + public static Resource getPoliciesResource() { + return POLICIES_RESOURCE; + } + + /** + * Gets the {@link Resource} for accessing buckets. + * @return The buckets resource + */ + public static Resource getBucketsResource() { + return BUCKETS_RESOURCE; + } + + /** + * Gets the {@link Resource} for accessing buckets. + * @return The buckets resource + */ + public static Resource getBucketResource(String bucketIdentifier, String bucketName) { + return getChildResource(ResourceType.Bucket, bucketIdentifier, bucketName); + } + + /** + * Gets a Resource for accessing a resources's policies. + * + * @param resource The resource being accessed + * @return The resource + */ + public static Resource getPolicyResource(final Resource resource) { + Objects.requireNonNull(resource, "The resource type must be specified."); + + return new Resource() { + @Override + public String getIdentifier() { + return String.format("%s%s", POLICY_RESOURCE.getIdentifier(), resource.getIdentifier()); + } + + @Override + public String getName() { + return POLICY_RESOURCE.getName() + resource.getName(); + } + + @Override + public String getSafeDescription() { + return POLICY_RESOURCE.getSafeDescription() + resource.getSafeDescription(); + } + }; + } + + /** + * Get a Resource object for any object that has a base type and an identifier, ie: + * /buckets/{uuid} + * + * @param parentResourceType - Required, the base resource type + * @param childIdentifier - Required, the identity of this sub resource + * @param name - Optional, the name of the subresource + * @return A resource for this object + */ + public static Resource getChildResource(final ResourceType parentResourceType, final String childIdentifier, final String name) { + Objects.requireNonNull(parentResourceType, "The base resource type must be specified."); + Objects.requireNonNull(childIdentifier, "The child identifier identifier must be specified."); + + return new Resource() { + @Override + public String getIdentifier() { + return String.format("%s/%s", parentResourceType.getValue(), childIdentifier); + } + + @Override + public String getName() { + return name; + } + + @Override + public String getSafeDescription() { + final StringBuilder safeDescription = new StringBuilder(); + switch (parentResourceType) { + case Bucket: + safeDescription.append("Bucket"); + break; + case Policy: + safeDescription.append("Policy"); + break; + case Tenant: + safeDescription.append("Tenant"); + break; + default: + safeDescription.append("Unknown resource type"); + break; + } + safeDescription.append(" with ID "); + safeDescription.append(childIdentifier); + return safeDescription.toString(); + } + }; + + } + + /** + * Prevent outside instantiation. + */ + private ResourceFactory() {} +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceType.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceType.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceType.java new file mode 100644 index 0000000..4987a38 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/resource/ResourceType.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.resource; + +public enum ResourceType { + Bucket("/buckets"), + Policy("/policies"), + Proxy("/proxy"), + Resource("/resources"), + Tenant("/tenants"); + + final String value; + + private ResourceType(final String value) { + this.value = value; + } + + public String getValue() { + return value; + } + + public static ResourceType valueOfValue(final String rawValue) { + ResourceType type = null; + + for (final ResourceType rt : values()) { + if (rt.getValue().equals(rawValue)) { + type = rt; + break; + } + } + + if (type == null) { + throw new IllegalArgumentException("Unknown resource type value " + rawValue); + } + + return type; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUser.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUser.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUser.java new file mode 100644 index 0000000..5f49241 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUser.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.nifi.registry.authorization.user; + +import java.util.Set; + +/** + * A representation of a NiFi user that has logged into the application + */ +public interface NiFiUser { + + /** + * @return the unique identity of this user + */ + String getIdentity(); + + /** + * @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise. + */ + Set<String> getGroups(); + + /** + * @return the next user in the proxied entities chain, or <code>null</code> if no more users exist in the chain. + */ + NiFiUser getChain(); + + /** + * @return <code>true</code> if the user is the unauthenticated Anonymous user + */ + boolean isAnonymous(); + + /** + * @return the address of the client that made the request which created this user + */ + String getClientAddress(); + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserDetails.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserDetails.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserDetails.java new file mode 100644 index 0000000..7b0da47 --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserDetails.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.user; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +/** + * User details for a NiFi user. + */ +public class NiFiUserDetails implements UserDetails { + + private final NiFiUser user; + + /** + * Creates a new NiFiUserDetails. + * + * @param user user + */ + public NiFiUserDetails(NiFiUser user) { + this.user = user; + } + + /** + * Get the user for this UserDetails. + * + * @return user + */ + public NiFiUser getNiFiUser() { + return user; + } + + /** + * Returns the authorities that this NiFi user has. + * + * @return authorities + */ + @Override + public Collection<? extends GrantedAuthority> getAuthorities() { + return Collections.EMPTY_SET; + } + + @Override + public String getPassword() { + return StringUtils.EMPTY; + } + + @Override + public String getUsername() { + return user.getIdentity(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + +} http://git-wip-us.apache.org/repos/asf/nifi-registry/blob/785cb81f/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserUtils.java ---------------------------------------------------------------------- diff --git a/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserUtils.java b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserUtils.java new file mode 100644 index 0000000..2ad508a --- /dev/null +++ b/nifi-registry-framework/src/main/java/org/apache/nifi/registry/authorization/user/NiFiUserUtils.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.registry.authorization.user; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility methods for retrieving information about the current application user. + * + */ +public final class NiFiUserUtils { + + /** + * Returns the current NiFiUser or null if the current user is not a NiFiUser. + * + * @return user + */ + public static NiFiUser getNiFiUser() { + NiFiUser user = null; + + // obtain the principal in the current authentication + final SecurityContext context = SecurityContextHolder.getContext(); + final Authentication authentication = context.getAuthentication(); + if (authentication != null) { + Object principal = authentication.getPrincipal(); + if (principal instanceof NiFiUserDetails) { + user = ((NiFiUserDetails) principal).getNiFiUser(); + } + } + + return user; + } + + public static String getNiFiUserIdentity() { + // get the nifi user to extract the username + NiFiUser user = NiFiUserUtils.getNiFiUser(); + if (user == null) { + return "unknown"; + } else { + return user.getIdentity(); + } + } + + /** + * Builds the proxy chain for the specified user. + * + * @param user The current user + * @return The proxy chain for that user in List form + */ + public static List<String> buildProxiedEntitiesChain(final NiFiUser user) { + // calculate the dn chain + final List<String> proxyChain = new ArrayList<>(); + + // build the dn chain + NiFiUser chainedUser = user; + while (chainedUser != null) { + // add the entry for this user + if (chainedUser.isAnonymous()) { + // use an empty string to represent an anonymous user in the proxy entities chain + proxyChain.add(StringUtils.EMPTY); + } else { + proxyChain.add(chainedUser.getIdentity()); + } + + // go to the next user in the chain + chainedUser = chainedUser.getChain(); + } + + return proxyChain; + } +}
