| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.nifi.authorization; |
| |
| import org.apache.commons.lang3.StringUtils; |
| import org.apache.nifi.authorization.annotation.AuthorizerContext; |
| import org.apache.nifi.authorization.exception.AuthorizationAccessException; |
| import org.apache.nifi.authorization.exception.AuthorizerCreationException; |
| import org.apache.nifi.authorization.exception.AuthorizerDestructionException; |
| import org.apache.nifi.authorization.generated.Authorizers; |
| import org.apache.nifi.authorization.generated.Property; |
| import org.apache.nifi.bundle.Bundle; |
| import org.apache.nifi.nar.ExtensionManager; |
| import org.apache.nifi.properties.SensitivePropertyProviderFactoryAware; |
| import org.apache.nifi.security.xml.XmlUtils; |
| import org.apache.nifi.util.NiFiProperties; |
| import org.apache.nifi.util.file.classloader.ClassLoaderUtils; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.beans.factory.DisposableBean; |
| import org.springframework.beans.factory.FactoryBean; |
| 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.stream.XMLStreamException; |
| import javax.xml.stream.XMLStreamReader; |
| 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.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Factory bean for loading the configured authorizer. |
| */ |
| public class AuthorizerFactoryBean extends SensitivePropertyProviderFactoryAware |
| implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup { |
| |
| private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactoryBean.class); |
| private static final String AUTHORIZERS_XSD = "/authorizers.xsd"; |
| private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authorization.generated"; |
| private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext(); |
| |
| private NiFiProperties properties; |
| |
| /** |
| * Load the JAXBContext. |
| */ |
| private static JAXBContext initializeJaxbContext() { |
| try { |
| return JAXBContext.newInstance(JAXB_GENERATED_PATH, AuthorizerFactoryBean.class.getClassLoader()); |
| } catch (JAXBException e) { |
| throw new RuntimeException("Unable to create JAXBContext."); |
| } |
| } |
| |
| private Authorizer authorizer; |
| private ExtensionManager extensionManager; |
| 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 void setProperties(final NiFiProperties properties) { |
| this.properties = properties; |
| } |
| |
| @Override |
| public UserGroupProvider getUserGroupProvider(String identifier) { |
| return userGroupProviders.get(identifier); |
| } |
| |
| @Override |
| public AccessPolicyProvider getAccessPolicyProvider(String identifier) { |
| return accessPolicyProviders.get(identifier); |
| } |
| |
| @Override |
| public Authorizer getAuthorizer(String identifier) { |
| return authorizers.get(identifier); |
| } |
| |
| @Override |
| public Object getObject() throws Exception { |
| 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(NiFiProperties.SECURITY_USER_AUTHORIZER); |
| |
| // ensure the authorizer class name was specified |
| if (StringUtils.isBlank(authorizerIdentifier)) { |
| throw new Exception("When running securely, the authorizer identifier must be specified in the nifi properties file."); |
| } else { |
| final Authorizers authorizerConfiguration = loadAuthorizersConfiguration(); |
| |
| // create each user group provider |
| for (final org.apache.nifi.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) { |
| if (userGroupProviders.containsKey(userGroupProvider.getIdentifier())) { |
| throw new Exception("Duplicate User Group Provider identifier in Authorizers configuration: " + userGroupProvider.getIdentifier()); |
| } |
| userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz())); |
| } |
| |
| // configure each user group provider |
| for (final org.apache.nifi.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.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) { |
| if (accessPolicyProviders.containsKey(accessPolicyProvider.getIdentifier())) { |
| throw new Exception("Duplicate Access Policy Provider identifier in Authorizers configuration: " + accessPolicyProvider.getIdentifier()); |
| } |
| accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz())); |
| } |
| |
| // configure each access policy provider |
| for (final org.apache.nifi.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.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) { |
| if (authorizers.containsKey(authorizer.getIdentifier())) { |
| throw new Exception("Duplicate Authorizer identifier in Authorizers configuration: " + authorizer.getIdentifier()); |
| } |
| authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz(), authorizer.getClasspath())); |
| } |
| |
| // configure each authorizer, except the authorizer that is selected in nifi.properties |
| for (final org.apache.nifi.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) { |
| if (provider.getIdentifier().equals(authorizerIdentifier)) { |
| continue; |
| } |
| 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 Exception(String.format("The specified authorizer '%s' could not be found.", authorizerIdentifier)); |
| } else { |
| // install integrity checks |
| authorizer = AuthorizerFactory.installIntegrityChecks(authorizer); |
| |
| // configure authorizer after integrity checks are installed |
| AuthorizerConfigurationContext authorizerConfigurationContext = null; |
| for (final org.apache.nifi.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) { |
| if (provider.getIdentifier().equals(authorizerIdentifier)) { |
| authorizerConfigurationContext = loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()); |
| break; |
| } |
| } |
| |
| if (authorizerConfigurationContext == null) { |
| throw new IllegalStateException("Unable to load configuration for authorizer with id: " + authorizerIdentifier); |
| } |
| |
| authorizer.onConfigured(authorizerConfigurationContext); |
| } |
| } |
| } |
| } |
| |
| return authorizer; |
| } |
| |
| private Authorizers loadAuthorizersConfiguration() throws Exception { |
| final File authorizersConfigurationFile = properties.getAuthorizerConfigurationFile(); |
| |
| // 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 XMLStreamReader xsr = XmlUtils.createSafeReader(new StreamSource(authorizersConfigurationFile)); |
| final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller(); |
| unmarshaller.setSchema(schema); |
| final JAXBElement<Authorizers> element = unmarshaller.unmarshal(xsr, Authorizers.class); |
| return element.getValue(); |
| } catch (XMLStreamException | 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 UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception { |
| // get the classloader for the specified user group provider |
| final List<Bundle> userGroupProviderBundles = extensionManager.getBundles(userGroupProviderClassName); |
| |
| if (userGroupProviderBundles.size() == 0) { |
| throw new Exception(String.format("The specified user group provider class '%s' is not known to this nifi.", userGroupProviderClassName)); |
| } |
| |
| if (userGroupProviderBundles.size() > 1) { |
| throw new Exception(String.format("Multiple bundles found for the specified user group provider class '%s', only one is allowed.", userGroupProviderClassName)); |
| } |
| |
| final Bundle userGroupProviderBundle = userGroupProviderBundles.get(0); |
| final ClassLoader userGroupProviderClassLoader = userGroupProviderBundle.getClassLoader(); |
| |
| // get the current context classloader |
| final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); |
| |
| final UserGroupProvider instance; |
| try { |
| // set the appropriate class loader |
| Thread.currentThread().setContextClassLoader(userGroupProviderClassLoader); |
| |
| // attempt to load the class |
| Class<?> rawUserGroupProviderClass = Class.forName(userGroupProviderClassName, true, userGroupProviderClassLoader); |
| 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)); |
| } finally { |
| if (currentClassLoader != null) { |
| Thread.currentThread().setContextClassLoader(currentClassLoader); |
| } |
| } |
| |
| return UserGroupProviderFactory.withNarLoader(instance, userGroupProviderClassLoader); |
| } |
| |
| private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception { |
| // get the classloader for the specified access policy provider |
| final List<Bundle> accessPolicyProviderBundles = extensionManager.getBundles(accessPolicyProviderClassName); |
| |
| if (accessPolicyProviderBundles.size() == 0) { |
| throw new Exception(String.format("The specified access policy provider class '%s' is not known to this nifi.", accessPolicyProviderClassName)); |
| } |
| |
| if (accessPolicyProviderBundles.size() > 1) { |
| throw new Exception(String.format("Multiple bundles found for the specified access policy provider class '%s', only one is allowed.", accessPolicyProviderClassName)); |
| } |
| |
| final Bundle accessPolicyProviderBundle = accessPolicyProviderBundles.get(0); |
| final ClassLoader accessPolicyProviderClassLoader = accessPolicyProviderBundle.getClassLoader(); |
| |
| // get the current context classloader |
| final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); |
| |
| final AccessPolicyProvider instance; |
| try { |
| // set the appropriate class loader |
| Thread.currentThread().setContextClassLoader(accessPolicyProviderClassLoader); |
| |
| // attempt to load the class |
| Class<?> rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName, true, accessPolicyProviderClassLoader); |
| 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)); |
| } finally { |
| if (currentClassLoader != null) { |
| Thread.currentThread().setContextClassLoader(currentClassLoader); |
| } |
| } |
| |
| return AccessPolicyProviderFactory.withNarLoader(instance, accessPolicyProviderClassLoader); |
| } |
| |
| private Authorizer createAuthorizer(final String identifier, final String authorizerClassName, final String classpathResources) throws Exception { |
| // get the classloader for the specified authorizer |
| final List<Bundle> authorizerBundles = extensionManager.getBundles(authorizerClassName); |
| |
| if (authorizerBundles.size() == 0) { |
| throw new Exception(String.format("The specified authorizer class '%s' is not known to this nifi.", authorizerClassName)); |
| } |
| |
| if (authorizerBundles.size() > 1) { |
| throw new Exception(String.format("Multiple bundles found for the specified authorizer class '%s', only one is allowed.", authorizerClassName)); |
| } |
| |
| // start with ClassLoad from authorizer's bundle |
| final Bundle authorizerBundle = authorizerBundles.get(0); |
| ClassLoader authorizerClassLoader = authorizerBundle.getClassLoader(); |
| |
| // if additional classpath resources were specified, replace with a new ClassLoader that wraps the original one |
| if (StringUtils.isNotEmpty(classpathResources)) { |
| logger.info(String.format("Replacing Authorizer ClassLoader for '%s' to include additional resources: %s", identifier, classpathResources)); |
| URL[] urls = ClassLoaderUtils.getURLsForClasspath(classpathResources, null, true); |
| authorizerClassLoader = new URLClassLoader(urls, authorizerClassLoader); |
| } |
| |
| // get the current context classloader |
| final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader(); |
| |
| final Authorizer instance; |
| try { |
| // set the appropriate class loader |
| Thread.currentThread().setContextClassLoader(authorizerClassLoader); |
| |
| // attempt to load the class |
| Class<?> rawAuthorizerClass = Class.forName(authorizerClassName, true, authorizerClassLoader); |
| 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)); |
| } finally { |
| if (currentClassLoader != null) { |
| Thread.currentThread().setContextClassLoader(currentClassLoader); |
| } |
| } |
| |
| return AuthorizerFactory.withNarLoader(instance, authorizerClassLoader); |
| } |
| |
| private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Property> properties) { |
| final Map<String, String> authorizerProperties = new HashMap<>(); |
| |
| for (final Property property : properties) { |
| if (!StringUtils.isBlank(property.getEncryption())) { |
| String decryptedValue = decryptValue(property.getValue(), property.getEncryption(), property.getName(), identifier); |
| authorizerProperties.put(property.getName(), decryptedValue); |
| } else { |
| authorizerProperties.put(property.getName(), property.getValue()); |
| } |
| } |
| |
| return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties); |
| } |
| |
| 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 (NiFiProperties.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 (NiFiProperties.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 { |
| } |
| }; |
| } |
| |
| @Override |
| public Class getObjectType() { |
| return Authorizer.class; |
| } |
| |
| @Override |
| public boolean isSingleton() { |
| return true; |
| } |
| |
| @Override |
| public void destroy() throws Exception { |
| List<Exception> errors = new ArrayList<>(); |
| |
| if (authorizers != null) { |
| authorizers.forEach((identifier, object) -> { |
| try { |
| object.preDestruction(); |
| } catch (Exception e) { |
| errors.add(e); |
| logger.error("Error pre-destructing {}: {}", identifier, e); |
| } |
| }); |
| } |
| |
| if (accessPolicyProviders != null) { |
| accessPolicyProviders.forEach((identifier, object) -> { |
| try { |
| object.preDestruction(); |
| } catch (Exception e) { |
| errors.add(e); |
| logger.error("Error pre-destructing {}: {}", identifier, e); |
| } |
| }); |
| } |
| |
| if (userGroupProviders != null) { |
| userGroupProviders.forEach((identifier, object) -> { |
| try { |
| object.preDestruction(); |
| } catch (Exception e) { |
| errors.add(e); |
| logger.error("Error pre-destructing {}: {}", identifier, e); |
| } |
| }); |
| } |
| |
| if (!errors.isEmpty()) { |
| List<String> errorMessages = errors.stream().map(Throwable::toString).collect(Collectors.toList()); |
| throw new AuthorizerDestructionException("One or more providers encountered a pre-destruction error: " + StringUtils.join(errorMessages, "; "), errors.get(0)); |
| } |
| } |
| |
| public void setExtensionManager(ExtensionManager extensionManager) { |
| this.extensionManager = extensionManager; |
| } |
| |
| } |