From 24687068da35cf73350145af9035cd761f60d501 Mon Sep 17 00:00:00 2001 From: Mark Thomas Date: Wed, 10 Feb 2016 21:26:46 +0000 Subject: [PATCH] Next stage of JASPIC implementation (excluding the changes to AuthenticatorBase to integrate JASPIC support). With the necessary integration, this code passes the javaee7-samples JASPIC unit tests as far as the lifecycle tests (which currently all fail). git-svn-id: https://svn.apache.org/repos/asf/tomcat/trunk@1729742 13f79535-47bb-0310-9956-ffa450edef68 --- .../jaspic/AuthConfigFactoryImpl.java | 298 ++++++++++++++++++ .../jaspic/CallbackHandlerImpl.java | 106 +++++++ .../jaspic/LocalStrings.properties | 26 ++ .../authenticator/jaspic/MessageInfoImpl.java | 80 +++++ .../PersistentProviderRegistrations.java | 106 ++++++- .../TestPersistentProviderRegistrations.java | 43 ++- .../catalina/startup/TestContextConfig.java | 21 ++ 7 files changed, 673 insertions(+), 7 deletions(-) create mode 100644 java/org/apache/catalina/authenticator/jaspic/AuthConfigFactoryImpl.java create mode 100644 java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java create mode 100644 java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties create mode 100644 java/org/apache/catalina/authenticator/jaspic/MessageInfoImpl.java diff --git a/java/org/apache/catalina/authenticator/jaspic/AuthConfigFactoryImpl.java b/java/org/apache/catalina/authenticator/jaspic/AuthConfigFactoryImpl.java new file mode 100644 index 000000000000..f402e902373f --- /dev/null +++ b/java/org/apache/catalina/authenticator/jaspic/AuthConfigFactoryImpl.java @@ -0,0 +1,298 @@ +/** + * 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.Iterator; +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 final Map registrations = new ConcurrentHashMap<>(); + + + // TODO: Handle parallel deployment. Can this be done if web application is + // not aware it is being deployed in parallel? + public AuthConfigFactoryImpl() { + loadPersistentRegistrations(); + } + + + @Override + public AuthConfigProvider getConfigProvider(String layer, String appContext, + RegistrationListener listener) { + String registrationID = getRegistrarionID(layer, appContext); + RegistrationContextImpl registrationContext = registrations.get(registrationID); + if (registrationContext != null) { + registrationContext.addListener(null); + 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, this); + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | + IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new SecurityException(e); + } + + String registrationID = getRegistrarionID(layer, appContext); + registrations.put(registrationID, + new RegistrationContextImpl(layer, appContext, description, true, provider, properties)); + 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 = getRegistrarionID(layer, appContext); + registrations.put(registrationID, + new RegistrationContextImpl(layer, appContext, description, false, provider, null)); + return registrationID; + } + + + @Override + public boolean removeRegistration(String registrationID) { + return registrations.remove(registrationID) != null; + } + + + @Override + public String[] detachListener(RegistrationListener listener, String layer, String appContext) { + String registrationID = getRegistrarionID(layer, appContext); + RegistrationContextImpl registrationContext = registrations.get(registrationID); + if (registrationContext.removeListener(listener)) { + return new String[] { registrationID }; + } + return EMPTY_STRING_ARRAY; + } + + + @Override + public String[] getRegistrationIDs(AuthConfigProvider provider) { + if (provider == null) { + return registrations.keySet().toArray(EMPTY_STRING_ARRAY); + } else { + List results = new ArrayList<>(); + for (Entry entry : registrations.entrySet()) { + if (provider.equals(entry.getValue().getProvider())) { + results.add(entry.getKey()); + } + } + return results.toArray(EMPTY_STRING_ARRAY); + } + } + + + @Override + public RegistrationContext getRegistrationContext(String registrationID) { + return registrations.get(registrationID); + } + + + @Override + public void refresh() { + loadPersistentRegistrations(); + } + + + private String getRegistrarionID(String layer, String appContext) { + return layer + ":" + 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(); + for (Entry entry : registrations.entrySet()) { + if (entry.getValue().isPersistent()) { + Provider provider = new Provider(); + provider.setAppContext(entry.getValue().getAppContext()); + provider.setClassName(entry.getValue().getProvider().getClass().getName()); + provider.setDescription(entry.getValue().getDescription()); + provider.setLayer(entry.getValue().getMessageLayer()); + for (Entry property : entry.getValue().getProperties().entrySet()) { + provider.addProperty(property.getKey(), property.getValue()); + } + providers.addProvider(provider); + } + } + PersistentProviderRegistrations.writeProviders(providers, CONFIG_FILE); + } + } + + + private static class RegistrationContextImpl implements RegistrationContext { + + private RegistrationContextImpl(String messageLayer, String appContext, String description, + boolean persistent, AuthConfigProvider provider, Map properties) { + this.messageLayer = messageLayer; + this.appContext = appContext; + this.description = description; + this.persistent = persistent; + this.provider = provider; + Map 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 properties; + private final List 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(RegistrationListener listener) { + if (listener != null) { + listeners.add(listener); + } + } + + + private Map getProperties() { + return properties; + } + + + private boolean removeListener(RegistrationListener listener) { + boolean result = false; + Iterator iter = listeners.iterator(); + while (iter.hasNext()) { + if (iter.next().equals(listener)) { + iter.remove(); + } + } + return result; + } + } +} diff --git a/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java b/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java new file mode 100644 index 000000000000..6687832390cd --- /dev/null +++ b/java/org/apache/catalina/authenticator/jaspic/CallbackHandlerImpl.java @@ -0,0 +1,106 @@ +/* + * 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.IOException; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.message.callback.CallerPrincipalCallback; +import javax.security.auth.message.callback.GroupPrincipalCallback; + +import org.apache.catalina.connector.Request; +import org.apache.catalina.realm.GenericPrincipal; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; + +public class CallbackHandlerImpl implements CallbackHandler { + + private static final Log log = LogFactory.getLog(CallbackHandlerImpl.class); + private static final StringManager sm = StringManager.getManager(CallbackHandlerImpl.class); + + private Request request; + private String name; + private Principal principal; + private Subject subject; + private String[] groups; + + + public CallbackHandlerImpl(Request request) { + this.request = request; + } + + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + if (callbacks != null) { + // Need to combine data from multiple callbacks so use this to hold + // the data + // Process the callbacks + for (Callback callback : callbacks) { + if (callback instanceof CallerPrincipalCallback) { + CallerPrincipalCallback cpc = (CallerPrincipalCallback) callback; + name = cpc.getName(); + principal = cpc.getPrincipal(); + subject = cpc.getSubject(); + } else if (callback instanceof GroupPrincipalCallback) { + GroupPrincipalCallback gpc = (GroupPrincipalCallback) callback; + groups = gpc.getGroups(); + } else { + log.error(sm.getString("callbackHandlerImpl.jaspicCallbackMissing", + callback.getClass().getName())); + } + } + + // Create the GenericPrincipal + GenericPrincipal gp = getGenericPrincipal(); + if (gp != null) { + request.setUserPrincipal(gp); + + if (subject != null) { + subject.getPrivateCredentials().add(gp); + } + } + } + } + + + public GenericPrincipal getGenericPrincipal() { + String name = this.name; + if (name == null && principal != null) { + name = principal.getName(); + } + if (name == null) { + return null; + } + List roles; + if (groups == null || groups.length == 0) { + roles = Collections.emptyList(); + } else { + roles = Arrays.asList(groups); + } + + return new GenericPrincipal(name, null, roles, principal); + } +} diff --git a/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties b/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties new file mode 100644 index 000000000000..368ed2c9a900 --- /dev/null +++ b/java/org/apache/catalina/authenticator/jaspic/LocalStrings.properties @@ -0,0 +1,26 @@ +# 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. + +authConfigFactoryImpl.load=Loading persistent provider registrations from [{0}] +authConfigFactoryImpl.registerClass=Registering class [{0}] for layer [{1}] and application context [{2}] +authConfigFactoryImpl.registerInstance=Registering instance of type[{0}] for layer [{1}] and application context [{2}] + +callbackHandlerImpl.jaspicCallbackMissing=Unsupported JASPIC callback of type [{0}] received which was ignored + +jaspicAuthenticator.authenticate=Authenticating request for [{0}] via JASPIC + +persistentProviderRegistrations.deleteFail=The temporary file [{0}] cannot be deleted +persistentProviderRegistrations.existsDeleteFail=The temporary file [{0}] already exists and cannot be deleted +persistentProviderRegistrations.moveFail=Failed to move [{0}] to [{1}] \ No newline at end of file diff --git a/java/org/apache/catalina/authenticator/jaspic/MessageInfoImpl.java b/java/org/apache/catalina/authenticator/jaspic/MessageInfoImpl.java new file mode 100644 index 000000000000..1c27a7976a1f --- /dev/null +++ b/java/org/apache/catalina/authenticator/jaspic/MessageInfoImpl.java @@ -0,0 +1,80 @@ +/* + * 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.util.HashMap; +import java.util.Map; + +import javax.security.auth.message.MessageInfo; +import javax.servlet.http.HttpServletResponse; + +import org.apache.catalina.connector.Request; +import org.apache.tomcat.util.res.StringManager; + +public class MessageInfoImpl implements MessageInfo { + protected static final StringManager sm = StringManager.getManager(MessageInfoImpl.class); + + public static final String IS_MANDATORY = "javax.security.auth.message.MessagePolicy.isMandatory"; + + private final Map map = new HashMap<>(); + private Request request; + private HttpServletResponse response; + + public MessageInfoImpl() { + } + + public MessageInfoImpl(Request request, HttpServletResponse response, boolean authMandatory) { + this.request = request; + this.response = response; + map.put(IS_MANDATORY, Boolean.toString(authMandatory)); + } + + @Override + @SuppressWarnings("rawtypes") + // JASPIC uses raw types + public Map getMap() { + return map; + } + + @Override + public Object getRequestMessage() { + return request; + } + + @Override + public Object getResponseMessage() { + return response; + } + + @Override + public void setRequestMessage(Object request) { + if (!(request instanceof Request)) { + throw new IllegalArgumentException(sm.getString("authenticator.jaspic.badRequestType", + request.getClass().getName())); + } + this.request = (Request) request; + } + + @Override + public void setResponseMessage(Object response) { + if (!(response instanceof HttpServletResponse)) { + throw new IllegalArgumentException(sm.getString("authenticator.jaspic.badResponseType", + response.getClass().getName())); + } + this.response = (HttpServletResponse) response; + } +} \ No newline at end of file diff --git a/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java b/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java index eec74c30605f..05eb1214d526 100644 --- a/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java +++ b/java/org/apache/catalina/authenticator/jaspic/PersistentProviderRegistrations.java @@ -18,32 +18,50 @@ import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.digester.Digester; +import org.apache.tomcat.util.res.StringManager; import org.xml.sax.SAXException; +/** + * Utility class for the loading and saving of JASPIC persistent provider + * registrations. + */ final class PersistentProviderRegistrations { + private static final Log log = LogFactory.getLog(PersistentProviderRegistrations.class); + private static final StringManager sm = + StringManager.getManager(PersistentProviderRegistrations.class); + + private PersistentProviderRegistrations() { // Utility class. Hide default constructor } - static Providers getProviders(File configFile) { + static Providers loadProviders(File configFile) { try (InputStream is = new FileInputStream(configFile)) { // Construct a digester to read the XML input file Digester digester = new Digester(); try { digester.setFeature("http://apache.org/xml/features/allow-java-encodings", true); - // TODO: Configure the digester to validate the input against - // the XSD + digester.setValidating(true); + digester.setNamespaceAware(true); } catch (Exception e) { throw new SecurityException(e); } @@ -72,6 +90,85 @@ static Providers getProviders(File configFile) { } + static void writeProviders(Providers providers, File configFile) { + File configFileOld = new File(configFile.getAbsolutePath() + ".old"); + File configFileNew = new File(configFile.getAbsolutePath() + ".new"); + + // Remove left over temporary files if present + if (configFileOld.exists()) { + if (configFileOld.delete()) { + throw new SecurityException(sm.getString( + "persistentProviderRegistrations.existsDeleteFail", + configFileOld.getAbsolutePath())); + } + } + if (configFileNew.exists()) { + if (configFileNew.delete()) { + throw new SecurityException(sm.getString( + "persistentProviderRegistrations.existsDeleteFail", + configFileNew.getAbsolutePath())); + } + } + + // Write out the providers to the temporary new file + try (OutputStream fos = new FileOutputStream(configFileNew); + Writer writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { + writer.write( + "\n" + + "\n"); + for (Provider provider : providers.providers) { + writer.write(" \n"); + for (Entry entry : provider.getProperties().entrySet()) { + writer.write(" \n"); + } + writer.write(" \n"); + } + writer.write("\n"); + } catch (IOException e) { + configFileNew.delete(); + throw new SecurityException(e); + } + + // Move the current file out of the way + if (configFile.isFile()) { + if (!configFile.renameTo(configFileOld)) { + throw new SecurityException(sm.getString("persistentProviderRegistrations.moveFail", + configFile.getAbsolutePath(), configFileOld.getAbsolutePath())); + } + } + + // Move the new file into place + if (!configFileNew.renameTo(configFile)) { + throw new SecurityException(sm.getString("persistentProviderRegistrations.moveFail", + configFileNew.getAbsolutePath(), configFile.getAbsolutePath())); + } + + // Remove the old file + if (configFileOld.exists() && !configFileOld.delete()) { + log.warn(sm.getString("persistentProviderRegistrations.deleteFail", + configFileOld.getAbsolutePath())); + } + } + + public static class Providers { private final List providers = new ArrayList<>(); @@ -128,6 +225,9 @@ public void setDescription(String description) { public void addProperty(Property property) { properties.put(property.getName(), property.getValue()); } + void addProperty(String name, String value) { + properties.put(name, value); + } public Map getProperties() { return properties; } diff --git a/test/org/apache/catalina/authenticator/jaspic/TestPersistentProviderRegistrations.java b/test/org/apache/catalina/authenticator/jaspic/TestPersistentProviderRegistrations.java index 85b1df2c83b0..5728e4b51740 100644 --- a/test/org/apache/catalina/authenticator/jaspic/TestPersistentProviderRegistrations.java +++ b/test/org/apache/catalina/authenticator/jaspic/TestPersistentProviderRegistrations.java @@ -29,7 +29,7 @@ public class TestPersistentProviderRegistrations { @Test public void testLoadEmpty() { File f = new File("test/conf/jaspic-test-01.xml"); - Providers result = PersistentProviderRegistrations.getProviders(f); + Providers result = PersistentProviderRegistrations.loadProviders(f); Assert.assertEquals(0, result.getProviders().size()); } @@ -37,9 +37,14 @@ public void testLoadEmpty() { @Test public void testLoadSimple() { File f = new File("test/conf/jaspic-test-02.xml"); - Providers result = PersistentProviderRegistrations.getProviders(f); - Assert.assertEquals(1, result.getProviders().size()); - Provider p = result.getProviders().get(0); + Providers result = PersistentProviderRegistrations.loadProviders(f); + validateSimple(result); + } + + + private void validateSimple(Providers providers) { + Assert.assertEquals(1, providers.getProviders().size()); + Provider p = providers.getProviders().get(0); Assert.assertEquals("a", p.getClassName()); Assert.assertEquals("b", p.getLayer()); Assert.assertEquals("c", p.getAppContext()); @@ -49,4 +54,34 @@ public void testLoadSimple() { Assert.assertEquals("f", p.getProperties().get("e")); Assert.assertEquals("h", p.getProperties().get("g")); } + + + @Test + public void testSaveSimple() { + File f = new File("test/conf/jaspic-test-03.xml"); + if (f.exists()) { + Assert.assertTrue(f.delete()); + } + + // Create a config and write it out + Providers start = new Providers(); + Provider p = new Provider(); + p.setClassName("a"); + p.setLayer("b"); + p.setAppContext("c"); + p.setDescription("d"); + p.addProperty("e", "f"); + p.addProperty("g", "h"); + start.addProvider(p); + PersistentProviderRegistrations.writeProviders(start, f); + + // Read it back + Providers end = PersistentProviderRegistrations.loadProviders(f); + + validateSimple(end); + + if (f.exists()) { + f.delete(); + } + } } diff --git a/test/org/apache/catalina/startup/TestContextConfig.java b/test/org/apache/catalina/startup/TestContextConfig.java index 92ac52a969bd..8dfb201fbd2d 100644 --- a/test/org/apache/catalina/startup/TestContextConfig.java +++ b/test/org/apache/catalina/startup/TestContextConfig.java @@ -34,6 +34,7 @@ import org.apache.catalina.Context; import org.apache.catalina.core.StandardContext; +import org.apache.catalina.core.StandardHost; import org.apache.tomcat.util.buf.ByteChunk; public class TestContextConfig extends TomcatBaseTest { @@ -201,4 +202,24 @@ private void assertPageContains(String pageUrl, String expectedBody, Assert.assertTrue(result, result.indexOf(expectedBody) > -1); } } + + + @Test + public void testUser01() throws Exception { + Tomcat tomcat = getTomcatInstance(); + StandardHost host = (StandardHost) tomcat.getHost(); + host.setUnpackWARs(true); + host.setAutoDeploy(true); + tomcat.start(); + + + StandardContext ctx = new StandardContext(); + ctx.setDocBase("D://test1"); + ctx.setPath("/test1"); + ctx.addLifecycleListener(new ContextConfig()); + + host.addChild(ctx); + + System.out.println("done"); + } }