/**
* 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.catalina.authenticator.jaspic;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.RegistrationListener;
import org.apache.catalina.Globals;
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Provider;
import org.apache.catalina.authenticator.jaspic.PersistentProviderRegistrations.Providers;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
public class AuthConfigFactoryImpl extends AuthConfigFactory {
private static final Log log = LogFactory.getLog(AuthConfigFactoryImpl.class);
private static final StringManager sm = StringManager.getManager(AuthConfigFactoryImpl.class);
private static final String CONFIG_PATH = "conf/jaspic-providers.xml";
private static final File CONFIG_FILE =
new File(System.getProperty(Globals.CATALINA_BASE_PROP), CONFIG_PATH);
private static final Object CONFIG_FILE_LOCK = new Object();
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static String DEFAULT_REGISTRATION_ID = getRegistrationID(null, null);
private final Map<String,RegistrationContextImpl> layerAppContextRegistrations =
new ConcurrentHashMap<>();
private final Map<String,RegistrationContextImpl> appContextRegistrations =
new ConcurrentHashMap<>();
private final Map<String,RegistrationContextImpl> layerRegistrations =
new ConcurrentHashMap<>();
// Note: Although there will only ever be a maximum of one entry in this
// Map, use a ConcurrentHashMap for consistency
private volatile Map<String,RegistrationContextImpl> defaultRegistration =
new ConcurrentHashMap<>(1);
public AuthConfigFactoryImpl() {
loadPersistentRegistrations();
}
@Override
public AuthConfigProvider getConfigProvider(String layer, String appContext,
RegistrationListener listener) {
RegistrationContextImpl registrationContext =
findRegistrationContextImpl(layer, appContext);
if (registrationContext != null) {
RegistrationListenerWrapper wrapper = new RegistrationListenerWrapper(
layer, appContext, listener);
registrationContext.addListener(wrapper);
return registrationContext.getProvider();
}
return null;
}
@Override
public String registerConfigProvider(String className,
@SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
String description) {
String registrationID =
doRegisterConfigProvider(className, properties, layer, appContext, description);
savePersistentRegistrations();
return registrationID;
}
@SuppressWarnings("unchecked")
private String doRegisterConfigProvider(String className,
@SuppressWarnings("rawtypes") Map properties, String layer, String appContext,
String description) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.registerClass",
className, layer, appContext));
}
Class<?> clazz;
AuthConfigProvider provider = null;
try {
clazz = Class.forName(className, true, Thread.currentThread().getContextClassLoader());
} catch (ClassNotFoundException e) {
// Ignore so the re-try below can proceed
}
try {
clazz = Class.forName(className);
Constructor<?> constructor = clazz.getConstructor(Map.class, AuthConfigFactory.class);
provider = (AuthConfigProvider) constructor.newInstance(properties, null);
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new SecurityException(e);
}
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
layer, appContext, description, true, provider, properties);
addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
return registrationID;
}
@Override
public String registerConfigProvider(AuthConfigProvider provider, String layer,
String appContext, String description) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.registerInstance",
provider.getClass().getName(), layer, appContext));
}
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContextImpl = new RegistrationContextImpl(
layer, appContext, description, false, provider, null);
addRegistrationContextImpl(layer, appContext, registrationID, registrationContextImpl);
return registrationID;
}
private void addRegistrationContextImpl(String layer, String appContext,
String registrationID, RegistrationContextImpl registrationContextImpl) {
RegistrationContextImpl previous = null;
// Add the registration, noting any registration it replaces
if (layer != null && appContext != null) {
previous = layerAppContextRegistrations.put(registrationID, registrationContextImpl);
} else if (layer == null && appContext != null) {
previous = appContextRegistrations.put(registrationID, registrationContextImpl);
} else if (layer != null && appContext == null) {
previous = layerRegistrations.put(registrationID, registrationContextImpl);
} else {
previous = defaultRegistration.put(registrationID, registrationContextImpl);
}
if (previous == null) {
// No match with previous registration so need to check listeners
// for all less specific registrations to see if they need to be
// notified of this new registration. That there is no exact match
// with a previous registration allows a few short-cuts to be taken
if (layer != null && appContext != null) {
// Need to check existing appContext registrations
// (and layer and default)
// appContext must match
RegistrationContextImpl registration =
appContextRegistrations.get(getRegistrationID(null, appContext));
if (registration != null) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (layer.equals(wrapper.getMessageLayer()) &&
appContext.equals(wrapper.getAppContext())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
if (appContext != null) {
// Need to check existing layer registrations
// (and default)
// Need to check registrations for all layers
for (RegistrationContextImpl registration : layerRegistrations.values()) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (appContext.equals(wrapper.getAppContext())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
if (layer != null || appContext != null) {
// Need to check default
for (RegistrationContextImpl registration : defaultRegistration.values()) {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
if (appContext != null && appContext.equals(wrapper.getAppContext()) ||
layer != null && layer.equals(wrapper.getMessageLayer())) {
registration.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
}
} else {
// Replaced an existing registration so need to notify those listeners
for (RegistrationListenerWrapper wrapper : previous.listeners) {
previous.listeners.remove(wrapper);
wrapper.listener.notify(wrapper.messageLayer, wrapper.appContext);
}
}
}
@Override
public boolean removeRegistration(String registrationID) {
RegistrationContextImpl registration = null;
if (DEFAULT_REGISTRATION_ID.equals(registrationID)) {
registration = defaultRegistration.remove(registrationID);
}
if (registration == null) {
registration = layerAppContextRegistrations.remove(registrationID);
}
if (registration == null) {
registration = appContextRegistrations.remove(registrationID);
}
if (registration == null) {
registration = layerRegistrations.remove(registrationID);
}
if (registration == null) {
return false;
} else {
for (RegistrationListenerWrapper wrapper : registration.listeners) {
wrapper.getListener().notify(wrapper.getMessageLayer(), wrapper.getAppContext());
}
return true;
}
}
@Override
public String[] detachListener(RegistrationListener listener, String layer, String appContext) {
String registrationID = getRegistrationID(layer, appContext);
RegistrationContextImpl registrationContext = findRegistrationContextImpl(layer, appContext);
if (registrationContext.removeListener(listener)) {
return new String[] { registrationID };
}
return EMPTY_STRING_ARRAY;
}
@Override
public String[] getRegistrationIDs(AuthConfigProvider provider) {
List<String> result = new ArrayList<>();
if (provider == null) {
result.addAll(layerAppContextRegistrations.keySet());
result.addAll(appContextRegistrations.keySet());
result.addAll(layerRegistrations.keySet());
if (defaultRegistration != null) {
result.add(DEFAULT_REGISTRATION_ID);
}
} else {
findProvider(provider, layerAppContextRegistrations, result);
findProvider(provider, appContextRegistrations, result);
findProvider(provider, layerRegistrations, result);
findProvider(provider, defaultRegistration, result);
}
return result.toArray(EMPTY_STRING_ARRAY);
}
private void findProvider(AuthConfigProvider provider,
Map<String,RegistrationContextImpl> registrations, List<String> result) {
for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
if (provider.equals(entry.getValue().getProvider())) {
result.add(entry.getKey());
}
}
}
@Override
public RegistrationContext getRegistrationContext(String registrationID) {
RegistrationContext result = defaultRegistration.get(registrationID);
if (result == null) {
result = layerAppContextRegistrations.get(registrationID);
}
if (result == null) {
result = appContextRegistrations.get(registrationID);
}
if (result == null) {
result = layerRegistrations.get(registrationID);
}
return result;
}
@Override
public void refresh() {
loadPersistentRegistrations();
}
private static String getRegistrationID(String layer, String appContext) {
if (layer != null && layer.length() == 0) {
throw new IllegalArgumentException(
sm.getString("authConfigFactoryImpl.zeroLengthMessageLayer"));
}
if (appContext != null && appContext.length() == 0) {
throw new IllegalArgumentException(
sm.getString("authConfigFactoryImpl.zeroLengthAppContext"));
}
return (layer == null ? "" : layer) + ":" + (appContext == null ? "" : appContext);
}
private void loadPersistentRegistrations() {
synchronized (CONFIG_FILE_LOCK) {
if (log.isDebugEnabled()) {
log.debug(sm.getString("authConfigFactoryImpl.load",
CONFIG_FILE.getAbsolutePath()));
}
if (!CONFIG_FILE.isFile()) {
return;
}
Providers providers = PersistentProviderRegistrations.loadProviders(CONFIG_FILE);
for (Provider provider : providers.getProviders()) {
doRegisterConfigProvider(provider.getClassName(), provider.getProperties(),
provider.getLayer(), provider.getAppContext(), provider.getDescription());
}
}
}
private void savePersistentRegistrations() {
synchronized (CONFIG_FILE_LOCK) {
Providers providers = new Providers();
savePersistentProviders(providers, layerAppContextRegistrations);
savePersistentProviders(providers, appContextRegistrations);
savePersistentProviders(providers, layerRegistrations);
savePersistentProviders(providers, defaultRegistration);
PersistentProviderRegistrations.writeProviders(providers, CONFIG_FILE);
}
}
private void savePersistentProviders(Providers providers,
Map<String,RegistrationContextImpl> registrations) {
for (Entry<String,RegistrationContextImpl> entry : registrations.entrySet()) {
savePersistentProvider(providers, entry.getValue());
}
}
private void savePersistentProvider(Providers providers,
RegistrationContextImpl registrationContextImpl) {
if (registrationContextImpl != null && registrationContextImpl.isPersistent()) {
Provider provider = new Provider();
provider.setAppContext(registrationContextImpl.getAppContext());
provider.setClassName(registrationContextImpl.getProvider().getClass().getName());
provider.setDescription(registrationContextImpl.getDescription());
provider.setLayer(registrationContextImpl.getMessageLayer());
for (Entry<String,String> property : registrationContextImpl.getProperties().entrySet()) {
provider.addProperty(property.getKey(), property.getValue());
}
providers.addProvider(provider);
}
}
private RegistrationContextImpl findRegistrationContextImpl(String layer, String appContext) {
RegistrationContextImpl result;
result = layerAppContextRegistrations.get(getRegistrationID(layer, appContext));
if (result == null) {
result = appContextRegistrations.get(getRegistrationID(null, appContext));
}
if (result == null) {
result = layerRegistrations.get(getRegistrationID(layer, null));
}
if (result == null) {
result = defaultRegistration.get(DEFAULT_REGISTRATION_ID);
}
return result;
}
private static class RegistrationContextImpl implements RegistrationContext {
private RegistrationContextImpl(String messageLayer, String appContext, String description,
boolean persistent, AuthConfigProvider provider, Map<String,String> properties) {
this.messageLayer = messageLayer;
this.appContext = appContext;
this.description = description;
this.persistent = persistent;
this.provider = provider;
Map<String,String> propertiesCopy = new HashMap<>();
if (properties != null) {
propertiesCopy.putAll(properties);
}
this.properties = Collections.unmodifiableMap(propertiesCopy);
}
private final String messageLayer;
private final String appContext;
private final String description;
private final boolean persistent;
private final AuthConfigProvider provider;
private final Map<String,String> properties;
private final List<RegistrationListenerWrapper> listeners = new CopyOnWriteArrayList<>();
@Override
public String getMessageLayer() {
return messageLayer;
}
@Override
public String getAppContext() {
return appContext;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean isPersistent() {
return persistent;
}
private AuthConfigProvider getProvider() {
return provider;
}
private void addListener(RegistrationListenerWrapper listener) {
if (listener != null) {
listeners.add(listener);
}
}
private Map<String,String> getProperties() {
return properties;
}
private boolean removeListener(RegistrationListener listener) {
boolean result = false;
for (RegistrationListenerWrapper wrapper : listeners) {
if (wrapper.getListener().equals(listener)) {
listeners.remove(wrapper);
}
}
return result;
}
}
private static class RegistrationListenerWrapper {
private final String messageLayer;
private final String appContext;
private final RegistrationListener listener;
public RegistrationListenerWrapper(String messageLayer, String appContext,
RegistrationListener listener) {
this.messageLayer = messageLayer;
this.appContext = appContext;
this.listener = listener;
}
public String getMessageLayer() {
return messageLayer;
}
public String getAppContext() {
return appContext;
}
public RegistrationListener getListener() {
return listener;
}
}
}