All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.wildfly.security.auth.jaspi.ElytronAuthConfigFactory Maven / Gradle / Ivy

There is a newer version: 2.4.1.Final
Show newest version
/*
 * Copyright 2018 Red Hat, Inc.
 *
 * Licensed 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.wildfly.security.auth.jaspi;

import static java.lang.System.getSecurityManager;
import static org.wildfly.common.Assert.checkNotNullParam;
import static org.wildfly.security.auth.jaspi._private.ElytronMessages.log;

import java.lang.reflect.Constructor;
import java.security.AccessController;
import java.security.SecurityPermission;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;

import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;

import org.wildfly.security.manager.action.GetContextClassLoaderAction;

/**
 * The WildFly Elytron implementation of {@link AuthConfigFactory}.
 *
 * @author Darran Lofthouse
 */
public class ElytronAuthConfigFactory extends AuthConfigFactory {

    private final Map layerContextRegistration = new HashMap<>();

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#getConfigProvider(java.lang.String, java.lang.String, javax.security.auth.message.config.RegistrationListener)
     */
    @Override
    public AuthConfigProvider getConfigProvider(String layer, String appContext, RegistrationListener listener) {
        // TODO - This is the most time sensitive method, it is called per request.
        // We may want to look at replacing the entire Map on updates so this method can query the
        // current version without locking.

        synchronized(layerContextRegistration) {
            LayerContextKey fullKey = new LayerContextKey(layer, appContext);
            Registration registration = layerContextRegistration.get(fullKey);
            // Step 1 - Exact Match
            if (registration != null) {
                if (listener != null) {
                    registration.addListener(listener);
                }
                if (registration.activeRegistration()) {
                    return registration.authConfigProvider;
                }
            } else if (listener != null) {
                // null registration but we have a listener.
                Registration listenerRegistration = new Registration(layer, appContext);
                listenerRegistration.addListener(listener);
                layerContextRegistration.put(fullKey, listenerRegistration);
            }

            // Step 2 - appContext only
            if (layer != null) {
                registration = layerContextRegistration.get(new LayerContextKey(null, appContext));
                if (registration != null && registration.activeRegistration()) {
                    return registration.authConfigProvider;
                }
            }

            // Step 3 - layer only
            if (appContext != null) {
                registration = layerContextRegistration.get(new LayerContextKey(layer, null));
                if (registration != null && registration.activeRegistration()) {
                    return registration.authConfigProvider;
                }
            }

            // Step 4 - No appContext or layer
            if (layer != null && appContext != null) {
                registration = layerContextRegistration.get(new LayerContextKey(null, null));
                if (registration != null && registration.activeRegistration()) {
                    return registration.authConfigProvider;
                }
            }
        }

        return null;
    }

    boolean matchesRegistration(final String layer, final String appContext) {
        synchronized (layerContextRegistration) {
            // Step 1 - Exact Match
            Registration registration = layerContextRegistration.get(new LayerContextKey(layer, appContext));
            if (registration != null && registration.activeRegistration()) {
                return true;
            }
            // Step 2 - appContext only
            registration = layerContextRegistration.get(new LayerContextKey(null, appContext));
            if (registration != null && registration.activeRegistration()) {
                return true;
            }
            // Step 3 - layer only
            registration = layerContextRegistration.get(new LayerContextKey(layer, null));
            if (registration != null && registration.activeRegistration()) {
                return true;
            }
            // Step 4 - No appContext or layer
            registration = layerContextRegistration.get(new LayerContextKey(null, null));
            if (registration != null && registration.activeRegistration()) {
                return true;
            }
        }

        return false;
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#registerConfigProvider(javax.security.auth.message.config.AuthConfigProvider, java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description) {
        checkPermission(providerRegistrationSecurityPermission);

        return registerConfigProvider(provider, layer, appContext, description, false);
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#registerConfigProvider(java.lang.String, java.util.Map, java.lang.String, java.lang.String, java.lang.String)
     */
    @Override
    public String registerConfigProvider(String className, Map properties, String layer, String appContext, String description) {
        // TODO [ELY-1548] We should support persisting to configuration changes made by calling this method.
        checkPermission(providerRegistrationSecurityPermission);

        AuthConfigProvider authConfigProvider = null;
        if (className != null) {
            ClassLoader classLoader = identifyClassLoader();
            try {
                Class providerClass = (Class) classLoader.loadClass(className);
                Constructor constructor = providerClass.getConstructor(Map.class, AuthConfigFactory.class);
                authConfigProvider = constructor.newInstance(properties, null);
            } catch (Exception e) {
                throw log.unableToConstructProvider(className, e);
            }
        }

        return registerConfigProvider(authConfigProvider, layer, appContext, description, true);
    }

    /*
     * We may actually want to be calling this method when in the application server environment as we would want to say the
     * managed configuration is persistent but at the same time want to pass in a real instance.
     */

    String registerConfigProvider(AuthConfigProvider provider, String layer, String appContext, String description, boolean persistent) {
        Registration registration = null;
        List existingListeners;
        synchronized(layerContextRegistration) {
            LayerContextKey key = new LayerContextKey(layer, appContext);
            registration = layerContextRegistration.get(key);
            if (registration == null) {
                registration = new Registration(layer, appContext);
                layerContextRegistration.put(key, registration);
                existingListeners = Collections.emptyList();
            } else {
                existingListeners = registration.clearListeners();
            }

            registration.setDescription(description);
            registration.setPersistent(persistent);
            registration.setAuthConfigProvider(provider, provider == null);
        }

        // Handle notify outside the synchronized block in case they want to re-register.
        for (RegistrationListener current : existingListeners) {
            current.notify(layer, appContext);
        }

        return registration.getRegistrationId();
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#removeRegistration(java.lang.String)
     */
    @Override
    public boolean removeRegistration(String registrationId) {
        checkPermission(providerRegistrationSecurityPermission);

        String layer = null;
        String appContext = null;
        boolean removed = false;
        List existingListeners = null;
        synchronized(layerContextRegistration) {
            Iterator> registrationIterator = layerContextRegistration.entrySet().iterator();
            while (registrationIterator.hasNext()) {
                Entry entry = registrationIterator.next();
                if (entry.getValue().getRegistrationId().equals(registrationId)) {
                    existingListeners = entry.getValue().clearListeners();
                    layer = entry.getKey().messageLayer;
                    appContext = entry.getKey().appContext;
                    registrationIterator.remove();
                    removed = true;
                    break;
                }
            }
        }

        // Handle notify outside the synchronized block in case they want to re-register.
        if (existingListeners != null && !existingListeners.isEmpty()) {
            for (RegistrationListener current : existingListeners) {
                current.notify(layer, appContext);
            }
        }

        return removed;
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#detachListener(javax.security.auth.message.config.RegistrationListener, java.lang.String, java.lang.String)
     */
    @Override
    public String[] detachListener(RegistrationListener listener, String layer, String appContext) {
        checkNotNullParam("listener", listener);
        checkPermission(providerRegistrationSecurityPermission);
        List registrationIDs = new ArrayList<>();
        synchronized (layerContextRegistration) {
            for (Registration current : layerContextRegistration.values()) {
                if ((layer == null || layer.equals(current.messageLayer)) && (appContext == null || appContext.equals(current.appContext))) {
                    if (current.removeListener(listener)) {
                        registrationIDs.add(current.getRegistrationId());
                    }
                }
            }
        }

        return registrationIDs.toArray(new String[registrationIDs.size()]);
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#getRegistrationContext(java.lang.String)
     */
    @Override
    public RegistrationContext getRegistrationContext(String registrationID) {
        synchronized (layerContextRegistration) {
            for (Registration current : layerContextRegistration.values()) {
                if (current.getRegistrationId().equals(registrationID)) {
                    return current.toRegistrationContext();
                }
            }
        }

        return null;
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#getRegistrationIDs(javax.security.auth.message.config.AuthConfigProvider)
     */
    @Override
    public String[] getRegistrationIDs(AuthConfigProvider provider) {
        List registrationIDs = new ArrayList<>();
        synchronized (layerContextRegistration) {
            if (provider != null) {
                for (Registration current : layerContextRegistration.values()) {
                    if (provider.equals(current.authConfigProvider)) {
                        registrationIDs.add(current.registrationId);
                    }
                }
            } else {
                for (Registration current : layerContextRegistration.values()) {
                    if (current.activeRegistration()) {
                        // The registration may exist just to hold listeners.
                        registrationIDs.add(current.registrationId);
                    }
                }
            }

        }

        return registrationIDs.toArray(new String[registrationIDs.size()]);
    }

    /**
     * @see javax.security.auth.message.config.AuthConfigFactory#refresh()
     */
    @Override
    public void refresh() {
        // [ELY-1538] Dynamic loading not presently supported, once supported refresh will reload the configuration.
        checkPermission(providerRegistrationSecurityPermission);
    }

    private static void checkPermission(final SecurityPermission securityPermission) {
        SecurityManager securityManager = getSecurityManager();
        if (securityManager != null) {
            securityManager.checkPermission(securityPermission);
        }
    }

    private static ClassLoader identifyClassLoader() {
        ClassLoader classLoader = getSecurityManager() != null
                ? AccessController.doPrivileged(GetContextClassLoaderAction.getInstance())
                : GetContextClassLoaderAction.getInstance().run();

        return classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
    }

    static class Registration {
        final String registrationId = UUID.randomUUID().toString();

        private boolean nullProvider;
        private AuthConfigProvider authConfigProvider;
        private List registrationListeners = new ArrayList<>();

        private final String appContext;
        private final String messageLayer;
        private String description;
        private boolean persistent;

        Registration(final String messageLayer, final String appContext) {
            this.messageLayer = messageLayer;
            this.appContext = appContext;
        }

        String getRegistrationId() {
            return registrationId;
        }

        void setDescription(final String description) {
            this.description = description;
        }

        void setPersistent(final boolean persistent) {
            this.persistent = persistent;
        }

        void setAuthConfigProvider(final AuthConfigProvider authConfigProvider, final boolean nullProvider) {
            this.authConfigProvider = authConfigProvider;
            this.nullProvider = authConfigProvider == null ? nullProvider : false;
        }

        AuthConfigProvider getAuthConfigProvider() {
            return authConfigProvider;
        }

        boolean isNullProvider() {
            return nullProvider;
        }

        /*
         * RegistrationListener Manipulation
         */

        void addListener(final RegistrationListener registrationListener) {
            this.registrationListeners.add(registrationListener);
        }

        boolean removeListener(final RegistrationListener registrationListener) {
            return registrationListeners.remove(registrationListener);
        }

        List clearListeners() {
            List currentListeners = this.registrationListeners;
            this.registrationListeners = new ArrayList<>();

            return currentListeners;
        }

        boolean activeRegistration() {
            return authConfigProvider != null || nullProvider;
        }

        RegistrationContext toRegistrationContext() {
            // We return a new instance to avoid state changes being detected by stale references.
            return activeRegistration() ? new ElytronRegistrationContext(messageLayer, appContext, description, persistent) : null;
        }

    }

    static class ElytronRegistrationContext implements RegistrationContext {

        private final String appContext;
        private final String messageLayer;
        private final  String description;
        private final boolean persistent;

        ElytronRegistrationContext(final String messageLayer, final String appContext, final String description, final boolean persistent) {
            this.messageLayer = messageLayer;
            this.appContext = appContext;
            this.description = description;
            this.persistent = persistent;
        }

        @Override
        public String getMessageLayer() {
            return messageLayer;
        }

        @Override
        public String getAppContext() {
            return appContext;
        }

        @Override
        public String getDescription() {
            return description;
        }

        @Override
        public boolean isPersistent() {
            return persistent;
        }

    }

    /*
     * The down side of a key like this is we end up with per-request object allocation to be garbage collected, the up side
     * however is we can accurately differentiate null values from the String 'null'.
     */

    static final class LayerContextKey {
        private final String messageLayer;
        private final String appContext;
        private final int hash;

        LayerContextKey(final String messageLayer, final String appContext) {
            this.messageLayer = messageLayer;
            this.appContext = appContext;

            this.hash = (messageLayer != null ? messageLayer.hashCode() : 7)
                    * (appContext != null ? appContext.hashCode() : 13);
        }

        @Override
        public boolean equals(Object other) {
            return other instanceof LayerContextKey ? equals((LayerContextKey) other) : false;
        }

        boolean equals(LayerContextKey other) {
            return (messageLayer != null ? messageLayer.equals(other.messageLayer) : other.messageLayer == null) &&
                    (appContext != null ? appContext.equals(other.appContext) : other.appContext == null);
        }

        @Override
        public int hashCode() {
            return hash;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy