org.apache.nifi.authorization.AuthorityProviderFactoryBean.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.nifi.authorization.AuthorityProviderFactoryBean.java

Source

/*
 * 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 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.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 org.apache.nifi.authorization.annotation.AuthorityProviderContext;
import org.apache.nifi.authorization.exception.AuthorityAccessException;
import org.apache.nifi.authorization.exception.IdentityAlreadyExistsException;
import org.apache.nifi.authorization.exception.ProviderCreationException;
import org.apache.nifi.authorization.exception.ProviderDestructionException;
import org.apache.nifi.authorization.exception.UnknownIdentityException;
import org.apache.nifi.authorization.generated.AuthorityProviders;
import org.apache.nifi.authorization.generated.Property;
import org.apache.nifi.authorization.generated.Provider;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.util.NiFiProperties;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.xml.sax.SAXException;

/**
 * Factory bean for loading the configured authority provider.
 */
public class AuthorityProviderFactoryBean
        implements FactoryBean, ApplicationContextAware, DisposableBean, AuthorityProviderLookup {

    private static final Logger logger = LoggerFactory.getLogger(AuthorityProviderFactoryBean.class);
    private static final String AUTHORITY_PROVIDERS_XSD = "/authority-providers.xsd";
    private static final String JAXB_GENERATED_PATH = "org.apache.nifi.authorization.generated";
    private static final JAXBContext JAXB_CONTEXT = initializeJaxbContext();

    /**
     * Load the JAXBContext.
     */
    private static JAXBContext initializeJaxbContext() {
        try {
            return JAXBContext.newInstance(JAXB_GENERATED_PATH,
                    AuthorityProviderFactoryBean.class.getClassLoader());
        } catch (JAXBException e) {
            throw new RuntimeException("Unable to create JAXBContext.");
        }
    }

    private ApplicationContext applicationContext;
    private AuthorityProvider authorityProvider;
    private NiFiProperties properties;
    private final Map<String, AuthorityProvider> authorityProviders = new HashMap<>();

    @Override
    public AuthorityProvider getAuthorityProvider(String identifier) {
        return authorityProviders.get(identifier);
    }

    @Override
    public Object getObject() throws Exception {
        if (authorityProvider == null) {
            // look up the authority provider to use
            final String authorityProviderIdentifier = properties
                    .getProperty(NiFiProperties.SECURITY_USER_AUTHORITY_PROVIDER);

            // ensure the authority provider class name was specified
            if (StringUtils.isBlank(authorityProviderIdentifier)) {
                // if configured for ssl, the authority provider must be specified
                if (properties.getSslPort() != null) {
                    throw new Exception(
                            "When running securely, the authority provider identifier must be specified in the nifi properties file.");
                }

                // use a default provider... only allowable when running not securely
                authorityProvider = createDefaultProvider();
            } else {
                final AuthorityProviders authorityProviderConfiguration = loadAuthorityProvidersConfiguration();

                // create each authority provider
                for (final Provider provider : authorityProviderConfiguration.getProvider()) {
                    authorityProviders.put(provider.getIdentifier(),
                            createAuthorityProvider(provider.getIdentifier(), provider.getClazz()));
                }

                // configure each authority provider
                for (final Provider provider : authorityProviderConfiguration.getProvider()) {
                    final AuthorityProvider instance = authorityProviders.get(provider.getIdentifier());
                    instance.onConfigured(loadAuthorityProviderConfiguration(provider));
                }

                // get the authority provider instance
                authorityProvider = getAuthorityProvider(authorityProviderIdentifier);

                // ensure it was found
                if (authorityProvider == null) {
                    throw new Exception(String.format("The specified authority provider '%s' could not be found.",
                            authorityProviderIdentifier));
                }
            }
        }

        return authorityProvider;
    }

    private AuthorityProviders loadAuthorityProvidersConfiguration() throws Exception {
        final File authorityProvidersConfigurationFile = properties.getAuthorityProviderConfiguraitonFile();

        // load the users from the specified file
        if (authorityProvidersConfigurationFile.exists()) {
            try {
                // find the schema
                final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
                final Schema schema = schemaFactory
                        .newSchema(AuthorityProviders.class.getResource(AUTHORITY_PROVIDERS_XSD));

                // attempt to unmarshal
                final Unmarshaller unmarshaller = JAXB_CONTEXT.createUnmarshaller();
                unmarshaller.setSchema(schema);
                final JAXBElement<AuthorityProviders> element = unmarshaller
                        .unmarshal(new StreamSource(authorityProvidersConfigurationFile), AuthorityProviders.class);
                return element.getValue();
            } catch (SAXException | JAXBException e) {
                throw new Exception("Unable to load the authority provider configuration file at: "
                        + authorityProvidersConfigurationFile.getAbsolutePath());
            }
        } else {
            throw new Exception("Unable to find the authority provider configuration file at "
                    + authorityProvidersConfigurationFile.getAbsolutePath());
        }
    }

    private AuthorityProvider createAuthorityProvider(final String identifier,
            final String authorityProviderClassName) throws Exception {
        // get the classloader for the specified authority provider
        final ClassLoader authorityProviderClassLoader = ExtensionManager
                .getClassLoader(authorityProviderClassName);
        if (authorityProviderClassLoader == null) {
            throw new Exception(
                    String.format("The specified authority provider class '%s' is not known to this nifi.",
                            authorityProviderClassName));
        }

        // get the current context classloader
        final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

        final AuthorityProvider instance;
        try {
            // set the appropriate class loader
            Thread.currentThread().setContextClassLoader(authorityProviderClassLoader);

            // attempt to load the class
            Class<?> rawAuthorityProviderClass = Class.forName(authorityProviderClassName, true,
                    authorityProviderClassLoader);
            Class<? extends AuthorityProvider> authorityProviderClass = rawAuthorityProviderClass
                    .asSubclass(AuthorityProvider.class);

            // otherwise create a new instance
            Constructor constructor = authorityProviderClass.getConstructor();
            instance = (AuthorityProvider) constructor.newInstance();

            // method injection
            performMethodInjection(instance, authorityProviderClass);

            // field injection
            performFieldInjection(instance, authorityProviderClass);

            // call post construction lifecycle event
            instance.initialize(new StandardAuthorityProviderInitializationContext(identifier, this));
        } finally {
            if (currentClassLoader != null) {
                Thread.currentThread().setContextClassLoader(currentClassLoader);
            }
        }

        return withNarLoader(instance);
    }

    private AuthorityProviderConfigurationContext loadAuthorityProviderConfiguration(final Provider provider) {
        final Map<String, String> providerProperties = new HashMap<>();

        for (final Property property : provider.getProperty()) {
            providerProperties.put(property.getName(), property.getValue());
        }

        return new StandardAuthorityProviderConfigurationContext(provider.getIdentifier(), providerProperties);
    }

    private void performMethodInjection(final AuthorityProvider instance, final Class authorityProviderClass)
            throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        for (final Method method : authorityProviderClass.getMethods()) {
            if (method.isAnnotationPresent(AuthorityProviderContext.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);
                        } else if (ApplicationContext.class.isAssignableFrom(argumentType)) {
                            // spring application context injection
                            method.invoke(instance, applicationContext);
                        }
                    }
                } finally {
                    method.setAccessible(isAccessible);
                }
            }
        }

        final Class parentClass = authorityProviderClass.getSuperclass();
        if (parentClass != null && AuthorityProvider.class.isAssignableFrom(parentClass)) {
            performMethodInjection(instance, parentClass);
        }
    }

    private void performFieldInjection(final AuthorityProvider instance, final Class authorityProviderClass)
            throws IllegalArgumentException, IllegalAccessException {
        for (final Field field : authorityProviderClass.getDeclaredFields()) {
            if (field.isAnnotationPresent(AuthorityProviderContext.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);
                        } else if (ApplicationContext.class.isAssignableFrom(fieldType)) {
                            // spring application context injection
                            field.set(instance, applicationContext);
                        }
                    }

                } finally {
                    field.setAccessible(isAccessible);
                }
            }
        }

        final Class parentClass = authorityProviderClass.getSuperclass();
        if (parentClass != null && AuthorityProvider.class.isAssignableFrom(parentClass)) {
            performFieldInjection(instance, parentClass);
        }
    }

    /**
     * @return a default provider to use when running unsecurely with no
     * provider configured
     */
    private AuthorityProvider createDefaultProvider() {
        return new AuthorityProvider() {
            @Override
            public boolean doesDnExist(String dn) throws AuthorityAccessException {
                return false;
            }

            @Override
            public Set<Authority> getAuthorities(String dn)
                    throws UnknownIdentityException, AuthorityAccessException {
                return EnumSet.noneOf(Authority.class);
            }

            @Override
            public void setAuthorities(String dn, Set<Authority> authorities)
                    throws UnknownIdentityException, AuthorityAccessException {
            }

            @Override
            public Set<String> getUsers(Authority authority) throws AuthorityAccessException {
                return new HashSet<>();
            }

            @Override
            public void revokeUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
            }

            @Override
            public void addUser(String dn, String group)
                    throws IdentityAlreadyExistsException, AuthorityAccessException {
            }

            @Override
            public String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
                return null;
            }

            @Override
            public void revokeGroup(String group) throws UnknownIdentityException, AuthorityAccessException {
            }

            @Override
            public void setUsersGroup(Set<String> dn, String group)
                    throws UnknownIdentityException, AuthorityAccessException {
            }

            @Override
            public void ungroupUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
            }

            @Override
            public void ungroup(String group) throws AuthorityAccessException {
            }

            @Override
            public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes)
                    throws UnknownIdentityException, AuthorityAccessException {
                return DownloadAuthorization.approved();
            }

            @Override
            public void initialize(AuthorityProviderInitializationContext initializationContext)
                    throws ProviderCreationException {
            }

            @Override
            public void onConfigured(AuthorityProviderConfigurationContext configurationContext)
                    throws ProviderCreationException {
            }

            @Override
            public void preDestruction() throws ProviderDestructionException {
            }
        };
    }

    /**
     * Decorates the base provider to ensure the nar context classloader is used
     * when invoking the underlying methods.
     *
     * @param baseProvider base provider
     * @return provider
     */
    public AuthorityProvider withNarLoader(final AuthorityProvider baseProvider) {
        return new AuthorityProvider() {
            @Override
            public boolean doesDnExist(String dn) throws AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    return baseProvider.doesDnExist(dn);
                }
            }

            @Override
            public Set<Authority> getAuthorities(String dn)
                    throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    return baseProvider.getAuthorities(dn);
                }
            }

            @Override
            public void setAuthorities(String dn, Set<Authority> authorities)
                    throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.setAuthorities(dn, authorities);
                }
            }

            @Override
            public Set<String> getUsers(Authority authority) throws AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    return baseProvider.getUsers(authority);
                }
            }

            @Override
            public void revokeUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.revokeUser(dn);
                }
            }

            @Override
            public void addUser(String dn, String group)
                    throws IdentityAlreadyExistsException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.addUser(dn, group);
                }
            }

            @Override
            public String getGroupForUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    return baseProvider.getGroupForUser(dn);
                }
            }

            @Override
            public void revokeGroup(String group) throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.revokeGroup(group);
                }
            }

            @Override
            public void setUsersGroup(Set<String> dns, String group)
                    throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.setUsersGroup(dns, group);
                }
            }

            @Override
            public void ungroupUser(String dn) throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.ungroupUser(dn);
                }
            }

            @Override
            public void ungroup(String group) throws AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.ungroup(group);
                }
            }

            @Override
            public DownloadAuthorization authorizeDownload(List<String> dnChain, Map<String, String> attributes)
                    throws UnknownIdentityException, AuthorityAccessException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    return baseProvider.authorizeDownload(dnChain, attributes);
                }
            }

            @Override
            public void initialize(AuthorityProviderInitializationContext initializationContext)
                    throws ProviderCreationException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.initialize(initializationContext);
                }
            }

            @Override
            public void onConfigured(AuthorityProviderConfigurationContext configurationContext)
                    throws ProviderCreationException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.onConfigured(configurationContext);
                }
            }

            @Override
            public void preDestruction() throws ProviderDestructionException {
                try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
                    baseProvider.preDestruction();
                }
            }
        };
    }

    @Override
    public Class getObjectType() {
        return AuthorityProvider.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void destroy() throws Exception {
        if (authorityProvider != null) {
            authorityProvider.preDestruction();
        }
    }

    public void setProperties(NiFiProperties properties) {
        this.properties = properties;
    }
}