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.
This commit is contained in:
Luke Taylor 2005-07-16 21:49:31 +00:00
parent 74588c8e53
commit a95964461d
5 changed files with 378 additions and 21 deletions

View File

@ -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.
*
* <p>
* This class wraps the XSL transform which actually does most of the work.
* </p>
*
* @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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Contacts Sample Application</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext-common-business.xml
/WEB-INF/applicationContext-common-authorization.xml
</param-value>
</context-param>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.properties</param-value>
</context-param>
<servlet>
<servlet-name>contacts</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<!--
The HttpSessionEventPublisher will publish
HttpSessionCreatedEvent and HttpSessionDestroyedEvent
to the WebApplicationContext
-->
<listener>
<listener-class>net.sf.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
<!--
- Provides web services endpoint. See remoting-servlet.xml.
-->
<servlet>
<servlet-name>remoting</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>contacts</servlet-name>
<url-pattern>*.htm</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>remoting</servlet-name>
<url-pattern>/remoting/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<security-constraint>
<web-resource-collection>
<url-pattern>/index.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<url-pattern>/hello.htm</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<url-pattern>/logoff.jsp</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<url-pattern>/acegilogin.jsp*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
<security-constraint>
<web-resource-collection>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>form</auth-method>
<form-login-config>
<form-login-page>/acegilogin.jsp</form-login-page>
<form-error-page>/acegilogin.jsp?login_error=1</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>user</role-name>
</security-role>
<security-role>
<role-name>dummy</role-name>
</security-role>
</web-app>

View File

@ -19,6 +19,10 @@
</context-param>
-->
<welcome-file-list>
<welcome-file>/convert.htm</welcome-file>
</welcome-file-list>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>