From a95964461d5732a96a15aa89e1ebffdcea68a828 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Sat, 16 Jul 2005 21:49:31 +0000 Subject: [PATCH] o Move the web.xml converter code, tests and xsl files from core into the acegifier sample app. o Switched to dom4j for more flexible xml handling and easier formatting of the XML output. o Modified the test web.xml to match the contacts-filter app to allow easy testing in an acegi application. --- .../src/java/acegifier/WebXmlConverter.java | 116 ++++++++++++++++ .../acegifier/web/AcegifierController.java | 71 +++++++--- .../test/acegifier/WebXmlConverterTests.java | 82 ++++++++++++ samples/acegifier/src/test/test-web.xml | 126 ++++++++++++++++++ samples/acegifier/src/webapp/WEB-INF/web.xml | 4 + 5 files changed, 378 insertions(+), 21 deletions(-) create mode 100644 samples/acegifier/src/java/acegifier/WebXmlConverter.java create mode 100644 samples/acegifier/src/test/acegifier/WebXmlConverterTests.java create mode 100644 samples/acegifier/src/test/test-web.xml diff --git a/samples/acegifier/src/java/acegifier/WebXmlConverter.java b/samples/acegifier/src/java/acegifier/WebXmlConverter.java new file mode 100644 index 0000000000..5676f5b23b --- /dev/null +++ b/samples/acegifier/src/java/acegifier/WebXmlConverter.java @@ -0,0 +1,116 @@ +package acegifier; + +import org.springframework.core.io.ClassPathResource; +import org.springframework.util.Assert; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.DocumentException; +import org.dom4j.io.SAXReader; +import org.dom4j.io.DocumentSource; +import org.dom4j.io.DocumentResult; + +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamSource; +import java.io.IOException; +import java.io.InputStream; + +/** + * A utility to translate a web.xml file into a set of acegi security spring beans. + * + * Also produces a new "acegified" web.xml file with the necessary filters installed + * and the security elements defined by the servlet DTD removed. + * + *

+ * This class wraps the XSL transform which actually does most of the work. + *

+ * + * @author Luke Taylor + * @version $Id$ + */ +public class WebXmlConverter { + private static final String WEB_TO_SPRING_XSL_FILE = "web-to-spring.xsl"; + private static final String NEW_WEB_XSLT_FILE = "acegi-web.xsl"; + + private Transformer acegiSecurityTransformer, newWebXmlTransformer; + + /** + * The name of the spring-beans file which the beans will be stored in. + * This is required when writing the new web.xml content. + */ + private String acegiOutputFileName = "applicationContext-acegi-security.xml"; + + /** The web.xml content to be converted */ + private Source xmlSource; + /** The results of the conversion */ + private Document newWebXml, acegiBeansXml; + + public WebXmlConverter() throws Exception { + TransformerFactory tf = TransformerFactory.newInstance(); + + acegiSecurityTransformer = tf.newTransformer(createTransformerSource(WEB_TO_SPRING_XSL_FILE)); + newWebXmlTransformer = tf.newTransformer(createTransformerSource(NEW_WEB_XSLT_FILE)); + } + + private Source createTransformerSource(String fileName) throws IOException { + ClassPathResource resource = new ClassPathResource(fileName); + return new StreamSource(resource.getInputStream()); + } + + /** + * Performs the transformations on the input source. + * Creates new web.xml content and a set of acegi-security Spring beans which can be + * accessed through the appropriate getter methods. + */ + public void doConversion() throws IOException, TransformerException { + Assert.notNull(xmlSource, "The XML input must be set"); + + // Create the modified web.xml file + newWebXmlTransformer.setParameter("acegi-security-context-file", acegiOutputFileName); +// newWebXmlTransformer.setParameter("cas-proxy-url", "http://localhost:8433/cas/proxy"); + DocumentResult result = new DocumentResult(); + newWebXmlTransformer.transform(xmlSource, result); + newWebXml = result.getDocument(); + + result = new DocumentResult(); + acegiSecurityTransformer.transform(xmlSource, result); + acegiBeansXml = result.getDocument(); + } + + /** Set the input as an xml string */ + public void setInput(String xml) throws DocumentException { + Document document = DocumentHelper.parseText(xml); + xmlSource = new DocumentSource(document); + } + + /** set the input as an InputStream */ + public void setInput(InputStream xmlIn) throws Exception { + SAXReader reader = new SAXReader(); + Document document = reader.read(xmlIn); + xmlSource = new DocumentSource(document); + } + + public String getAcegiOutputFileName() { + return acegiOutputFileName; + } + + public void setAcegiOutputFileName(String acegiOutputFileName) { + this.acegiOutputFileName = acegiOutputFileName; + } + + /** Returns the converted web.xml content */ + public Document getNewWebXml() { + return newWebXml; + } + + /** + * Returns the created spring-beans xml content which should be used in + * the application context file. + */ + public Document getAcegiBeans() { + return acegiBeansXml; + } + +} diff --git a/samples/acegifier/src/java/acegifier/web/AcegifierController.java b/samples/acegifier/src/java/acegifier/web/AcegifierController.java index 47b7d12f34..2b5db165b9 100644 --- a/samples/acegifier/src/java/acegifier/web/AcegifierController.java +++ b/samples/acegifier/src/java/acegifier/web/AcegifierController.java @@ -3,22 +3,26 @@ package acegifier.web; import org.springframework.web.servlet.mvc.SimpleFormController; import org.springframework.web.servlet.ModelAndView; import org.springframework.validation.BindException; +import org.springframework.validation.Errors; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.beans.BeansException; import net.sf.acegisecurity.util.InMemoryResource; -import org.w3c.dom.Document; import org.xml.sax.SAXParseException; +import org.dom4j.Document; +import org.dom4j.io.XMLWriter; +import org.dom4j.io.OutputFormat; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.DocumentBuilder; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.util.HashMap; import java.util.Map; -import net.sf.acegisecurity.util.WebXmlToAcegiSecurityConverter; +import net.sf.acegisecurity.util.FilterChainProxy; +import acegifier.WebXmlConverter; /** * Takes a submitted web.xml, applies the transformer to it and returns the resulting @@ -28,10 +32,8 @@ import net.sf.acegisecurity.util.WebXmlToAcegiSecurityConverter; * @version $Id$ */ public class AcegifierController extends SimpleFormController { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); public AcegifierController() { - dbf.setValidating(false); } public ModelAndView onSubmit( @@ -39,23 +41,20 @@ public class AcegifierController extends SimpleFormController { throws Exception { AcegifierForm conversion = (AcegifierForm)command; - ByteArrayInputStream in = new ByteArrayInputStream(conversion.getWebXml().getBytes()); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = null; - WebXmlToAcegiSecurityConverter converter = null; + WebXmlConverter converter = null; int nBeans = 0; + Document newWebXml = null, acegiBeans = null; try { - doc = db.parse(in); - converter = new WebXmlToAcegiSecurityConverter(); - converter.setInput(doc); + converter = new WebXmlConverter(); + converter.setInput(in); converter.doConversion(); - nBeans = createBeanFactory(converter.getAcegiBeansXml()); + newWebXml = converter.getNewWebXml(); + acegiBeans = converter.getAcegiBeans(); + nBeans = validateAcegiBeans(conversion, acegiBeans, errors); } catch (SAXParseException spe) { errors.rejectValue("webXml","parseFailure","Your Web XML Document failed to parse: " + spe.getMessage()); - } catch (BeansException be) { - errors.rejectValue("webXml","invalidBeans","There was a problem validating the Spring beans: " + be.getMessage()); } if(errors.hasErrors()) { @@ -63,19 +62,49 @@ public class AcegifierController extends SimpleFormController { } Map model = new HashMap(); - model.put("webXml", converter.getNewWebXml()); - model.put("acegiBeansXml", converter.getAcegiBeansXml()); + model.put("webXml", prettyPrint(newWebXml)); + model.put("acegiBeansXml", prettyPrint(acegiBeans)); model.put("nBeans", new Integer(nBeans)); return new ModelAndView("acegificationResults", model); } - /** Creates a BeanFactory from the transformed XML to make sure the results are valid */ - private int createBeanFactory(String beansXml) { + /** Creates a formatted XML string from the supplied document */ + private String prettyPrint(Document document) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setTrimText(false); + XMLWriter writer = new XMLWriter(output, format); + writer.write(document); + writer.flush(); + writer.close(); + return output.toString(); + } + + /** + * Validates the acegi beans, based on the input form data, and returns the number + * of spring beans defined in the document. + */ + private int validateAcegiBeans(AcegifierForm conversion, Document beans, Errors errors) throws IOException { + DefaultListableBeanFactory bf = createBeanFactory(beans); + + //TODO: actually do some proper validation! + + try { + bf.getBean("filterChainProxy", FilterChainProxy.class); + } catch (BeansException be) { + errors.rejectValue("webXml","beansInvalid","There was an error creating or accessing the bean factory " + be.getMessage()); + } + return bf.getBeanDefinitionCount(); + } + + /** Creates a BeanFactory from the spring beans XML document */ + private DefaultListableBeanFactory createBeanFactory(Document beans) { DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(bf); + beanReader.loadBeanDefinitions(new InMemoryResource(beans.asXML().getBytes())); - return beanReader.loadBeanDefinitions(new InMemoryResource(beansXml)); + return bf; } } diff --git a/samples/acegifier/src/test/acegifier/WebXmlConverterTests.java b/samples/acegifier/src/test/acegifier/WebXmlConverterTests.java new file mode 100644 index 0000000000..00438fea2b --- /dev/null +++ b/samples/acegifier/src/test/acegifier/WebXmlConverterTests.java @@ -0,0 +1,82 @@ +package acegifier; + +import junit.framework.TestCase; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor; +import net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter; +import net.sf.acegisecurity.providers.ProviderManager; +import net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider; +import net.sf.acegisecurity.providers.dao.memory.InMemoryDaoImpl; +import net.sf.acegisecurity.util.InMemoryResource; +import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.dom4j.Document; +import org.dom4j.io.OutputFormat; +import org.dom4j.io.XMLWriter; + +import java.io.IOException; +import java.io.ByteArrayOutputStream; + +/** + * Tests the WebXmlConverter by applying it to a sample web.xml file. + * + * @author Luke Taylor + * @version $Id$ + */ +public class WebXmlConverterTests extends TestCase { + + public void testFileConversion() throws Exception { + WebXmlConverter converter = new WebXmlConverter(); + + Resource r = new ClassPathResource("test-web.xml"); + converter.setInput(r.getInputStream()); + converter.doConversion(); + + DefaultListableBeanFactory bf = new DefaultListableBeanFactory(); + XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(bf); + + beanReader.loadBeanDefinitions( + new InMemoryResource(converter.getAcegiBeans().asXML().getBytes())); + assertNotNull(bf.getBean("filterChainProxy")); + + ProviderManager pm = (ProviderManager) bf.getBean("authenticationManager"); + assertNotNull(pm); + assertEquals(3, pm.getProviders().size()); + + DaoAuthenticationProvider dap = + (DaoAuthenticationProvider) bf.getBean("daoAuthenticationProvider"); + assertNotNull(dap); + + InMemoryDaoImpl dao = (InMemoryDaoImpl) dap.getAuthenticationDao(); + UserDetails user = dao.loadUserByUsername("superuser"); + assertEquals("password",user.getPassword()); + assertEquals(2, user.getAuthorities().length); + assertNotNull(bf.getBean("anonymousProcessingFilter")); + assertNotNull(bf.getBean("anonymousAuthenticationProvider")); + assertNotNull(bf.getBean("httpSessionContextIntegrationFilter")); + assertNotNull(bf.getBean("rememberMeProcessingFilter")); + assertNotNull(bf.getBean("rememberMeAuthenticationProvider")); + + SecurityEnforcementFilter sef = + (SecurityEnforcementFilter) bf.getBean("securityEnforcementFilter"); + assertNotNull(sef); + assertNotNull(sef.getAuthenticationEntryPoint()); + FilterSecurityInterceptor fsi = sef.getFilterSecurityInterceptor(); + System.out.println(prettyPrint(converter.getAcegiBeans())); + + } + + private String prettyPrint(Document document) throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + OutputFormat format = OutputFormat.createPrettyPrint(); + format.setNewlines(true); + format.setTrimText(false); + XMLWriter writer = new XMLWriter(output, format); + writer.write(document); + writer.flush(); + writer.close(); + return output.toString(); + } +} diff --git a/samples/acegifier/src/test/test-web.xml b/samples/acegifier/src/test/test-web.xml new file mode 100644 index 0000000000..a3968585a0 --- /dev/null +++ b/samples/acegifier/src/test/test-web.xml @@ -0,0 +1,126 @@ + + + + + Contacts Sample Application + + + contextConfigLocation + + /WEB-INF/applicationContext-common-business.xml + /WEB-INF/applicationContext-common-authorization.xml + + + + + log4jConfigLocation + /WEB-INF/classes/log4j.properties + + + + contacts + org.springframework.web.servlet.DispatcherServlet + 1 + + + + org.springframework.web.context.ContextLoaderListener + + + + org.springframework.web.util.Log4jConfigListener + + + + net.sf.acegisecurity.ui.session.HttpSessionEventPublisher + + + + remoting + org.springframework.web.servlet.DispatcherServlet + 2 + + + + contacts + *.htm + + + + remoting + /remoting/* + + + + index.jsp + + + + + /index.jsp + + + * + + + + + + /hello.htm + + + * + + + + + + /logoff.jsp + + + * + + + + + + /acegilogin.jsp* + + + * + + + + + + /* + + + user + + + + + form + + /acegilogin.jsp + /acegilogin.jsp?login_error=1 + + + + + + user + + + + dummy + + + \ No newline at end of file diff --git a/samples/acegifier/src/webapp/WEB-INF/web.xml b/samples/acegifier/src/webapp/WEB-INF/web.xml index 98c395fc9b..ee9694fdfe 100644 --- a/samples/acegifier/src/webapp/WEB-INF/web.xml +++ b/samples/acegifier/src/webapp/WEB-INF/web.xml @@ -19,6 +19,10 @@ --> + + /convert.htm + + org.springframework.web.context.ContextLoaderListener