Bladeren bron

SEC-1094: Simplified WebXml attribute mapping. Removed generic jaxen-based implementation on which it was based in favour of simple DOM model traversal. Updated sample.

Luke Taylor 16 jaren geleden
bovenliggende
commit
5808da12ff

+ 2 - 8
core/pom.xml

@@ -13,7 +13,7 @@
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-expression</artifactId>
-        </dependency>        
+        </dependency>
         <dependency>
             <groupId>org.springframework</groupId>
             <artifactId>spring-core</artifactId>
@@ -81,12 +81,6 @@
             <artifactId>hsqldb</artifactId>
             <scope>test</scope>
         </dependency>
-        <dependency>
-            <groupId>jaxen</groupId>
-            <artifactId>jaxen</artifactId>
-            <version>1.1.1</version>
-            <optional>true</optional>
-        </dependency>
         <dependency>
             <groupId>org.apache.tomcat</groupId>
             <artifactId>annotations-api</artifactId>
@@ -98,7 +92,7 @@
             <optional>true</optional>
         </dependency>
     </dependencies>
-    
+
     <build>
         <plugins>
             <plugin>

+ 0 - 103
core/src/test/java/org/springframework/security/core/authority/mapping/XmlMappableRolesRetrieverTests.java

@@ -1,103 +0,0 @@
-package org.springframework.security.core.authority.mapping;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Set;
-
-import org.springframework.security.core.authority.mapping.XmlMappableAttributesRetriever;
-
-import junit.framework.TestCase;
-
-/**
- *
- * @author TSARDD
- * @since 18-okt-2007
- */
-@SuppressWarnings("unchecked")
-public class XmlMappableRolesRetrieverTests extends TestCase {
-    private static final String DEFAULT_XML = "<roles><role>Role1</role><role>Role2</role></roles>";
-
-    private static final String DEFAULT_XPATH = "/roles/role/text()";
-
-    private static final String[] DEFAULT_EXPECTED_ROLES = new String[] { "Role1", "Role2" };
-
-    public final void testAfterPropertiesSetException() {
-        TestXmlMappableAttributesRetriever t = new TestXmlMappableAttributesRetriever();
-        try {
-            t.afterPropertiesSet();
-            fail("AfterPropertiesSet didn't throw expected exception");
-        } catch (IllegalArgumentException expected) {
-        } catch (Exception unexpected) {
-            fail("AfterPropertiesSet throws unexpected exception");
-        }
-    }
-
-    public void testGetMappableRoles() {
-        XmlMappableAttributesRetriever r = getXmlMappableRolesRetriever(true, getDefaultInputStream(), DEFAULT_XPATH);
-        Set<String> resultRoles = r.getMappableAttributes();
-        assertNotNull("Result roles should not be null", resultRoles);
-        assertEquals("Number of result roles doesn't match expected number of roles", DEFAULT_EXPECTED_ROLES.length, resultRoles.size());
-        Collection expectedRolesColl = Arrays.asList(DEFAULT_EXPECTED_ROLES);
-        assertTrue("Role collections do not match", expectedRolesColl.containsAll(resultRoles)
-                && resultRoles.containsAll(expectedRolesColl));
-    }
-
-    public void testCloseInputStream() {
-        testCloseInputStream(true);
-    }
-
-    public void testDontCloseInputStream() {
-        testCloseInputStream(false);
-    }
-
-    private void testCloseInputStream(boolean closeAfterRead) {
-        CloseableByteArrayInputStream is = getDefaultInputStream();
-        XmlMappableAttributesRetriever r = getXmlMappableRolesRetriever(closeAfterRead, is, DEFAULT_XPATH);
-        r.getMappableAttributes();
-        assertEquals(is.isClosed(), closeAfterRead);
-    }
-
-    private XmlMappableAttributesRetriever getXmlMappableRolesRetriever(boolean closeInputStream, InputStream is, String xpath) {
-        XmlMappableAttributesRetriever result = new TestXmlMappableAttributesRetriever();
-        result.setCloseInputStream(closeInputStream);
-        result.setXmlInputStream(is);
-        result.setXpathExpression(xpath);
-        try {
-            result.afterPropertiesSet();
-        } catch (Exception e) {
-            fail("Unexpected exception" + e.toString());
-        }
-        return result;
-    }
-
-    private CloseableByteArrayInputStream getDefaultInputStream() {
-        return getInputStream(DEFAULT_XML);
-    }
-
-    private CloseableByteArrayInputStream getInputStream(String data) {
-        return new CloseableByteArrayInputStream(data.getBytes());
-    }
-
-    private static final class TestXmlMappableAttributesRetriever extends XmlMappableAttributesRetriever {
-    }
-
-    private static final class CloseableByteArrayInputStream extends ByteArrayInputStream {
-        private boolean closed = false;
-
-        public CloseableByteArrayInputStream(byte[] buf) {
-            super(buf);
-        }
-
-        public void close() throws IOException {
-            super.close();
-            closed = true;
-        }
-
-        public boolean isClosed() {
-            return closed;
-        }
-    }
-}

+ 0 - 3
core/template.mf

@@ -23,10 +23,7 @@ Import-Template:
  org.springframework.transaction.*;version="[3.0.0, 3.1.0)";resolution:=optional,
  org.springframework.util;version="[3.0.0, 3.1.0)",
  net.sf.ehcache.*;version="[1.4.1, 2.0.0)";resolution:=optional, 
- org.w3c.dom;version="0";resolution:=optional,
- org.xml.sax;version="0";resolution:=optional, 
  javax.annotation.security.*;version="0";resolution:=optional,
  javax.crypto.*;version="0";resolution:=optional,
- javax.xml.*;version="0";resolution:=optional,
  javax.security.auth.*;version="0";resolution:=optional
  

+ 5 - 0
docs/manual/src/docbook/preauth.xml

@@ -187,6 +187,11 @@ class="org.springframework.security.web.authentication.preauth.PreAuthenticatedA
         <literal>userPrincipal</literal> property of the <interfacename>HttpServletRequest</interfacename>. use of this
         filter would usually be combined with the use of J2EE roles as described above in <xref linkend="j2ee-preauth-details"/>. 
       </para>
+      <para>
+        There is a sample application in the codebase which uses this approach, so get hold of the code from subversion and 
+        have a look at the application context file if you are interested. The code is in the <filename>samples/preauth</filename>
+        directory.
+      </para>
       
     </section>
   </section>

+ 14 - 23
samples/preauth/src/main/webapp/WEB-INF/applicationContext-security.xml

@@ -9,8 +9,8 @@
 <beans xmlns="http://www.springframework.org/schema/beans"
     xmlns:sec="http://www.springframework.org/schema/security"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
-                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">
+    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">
 
     <bean id="filterChainProxy" class="org.springframework.security.web.FilterChainProxy">
         <sec:filter-chain-map path-type="ant">
@@ -32,7 +32,18 @@
 
     <bean id="j2eePreAuthFilter" class="org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter">
         <property name="authenticationManager" ref="authenticationManager"/>
-        <property name="authenticationDetailsSource" ref="authenticationDetailsSource"/>
+        <property name="authenticationDetailsSource">
+            <bean class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
+                <property name="mappableRolesRetriever">
+                    <bean class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever" />
+                </property>
+                <property name="userRoles2GrantedAuthoritiesMapper">
+                    <bean class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
+                        <property name="convertAttributeToUpperCase" value="true"/>
+                    </bean>
+                </property>
+            </bean>
+        </property>
     </bean>
 
     <bean id="preAuthenticatedProcessingFilterEntryPoint"
@@ -47,26 +58,6 @@
         </constructor-arg>
     </bean>
 
-    <bean id="authenticationDetailsSource" class="org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource">
-        <property name="mappableRolesRetriever" ref="j2eeMappableRolesRetriever"/>
-        <property name="userRoles2GrantedAuthoritiesMapper" ref="j2eeUserRoles2GrantedAuthoritiesMapper"/>
-    </bean>
-
-    <bean id="j2eeUserRoles2GrantedAuthoritiesMapper" class="org.springframework.security.core.authority.mapping.SimpleAttributes2GrantedAuthoritiesMapper">
-        <property name="convertAttributeToUpperCase" value="true"/>
-    </bean>
-
-    <bean id="j2eeMappableRolesRetriever" class="org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever">
-
-    <property name="webXmlInputStream"><bean factory-bean="webXmlResource" factory-method="getInputStream"/>
-    </property>
-    </bean>
-
-    <bean id="webXmlResource" class="org.springframework.web.context.support.ServletContextResource">
-        <constructor-arg ref="servletContext"/>
-        <constructor-arg value="/WEB-INF/web.xml"/>
-    </bean>
-
     <bean id="servletContext" class="org.springframework.web.context.support.ServletContextFactoryBean"/>
 
     <bean id="etf" class="org.springframework.security.web.access.ExceptionTranslationFilter">

+ 1 - 0
web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/J2eePreAuthenticatedProcessingFilter.java

@@ -13,6 +13,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen
  * @since 2.0
  */
 public class J2eePreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
+
     /**
      * Return the J2EE user name.
      */

+ 104 - 31
web/src/main/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlMappableAttributesRetriever.java

@@ -1,50 +1,123 @@
 package org.springframework.security.web.authentication.preauth.j2ee;
 
+import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
-import org.springframework.security.core.authority.mapping.XmlMappableAttributesRetriever;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.context.ResourceLoaderAware;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
+import org.springframework.security.core.authority.mapping.MappableAttributesRetriever;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
 
 /**
- * <p>
- * This MappableAttributesRetriever implementation reads the list of defined J2EE
- * roles from a web.xml file. It's functionality is based on the
- * XmlMappableAttributesRetriever base class.
- * <p>
- * Example on how to configure this MappableAttributesRetriever in the Spring
- * configuration file:
- *
- * <pre>
- *
- *
- * &lt;bean id=&quot;j2eeMappableRolesRetriever&quot; class=&quot;org.springframework.security.ui.preauth.j2ee.WebXmlMappableAttributesRetriever&quot;&gt;
- *     &lt;property name=&quot;webXmlInputStream&quot;&gt;&lt;bean factory-bean=&quot;webXmlResource&quot; factory-method=&quot;getInputStream&quot;/&gt;&lt;/property&gt;
- * &lt;/bean&gt;
- * &lt;bean id=&quot;webXmlResource&quot; class=&quot;org.springframework.web.context.support.ServletContextResource&quot;&gt;
- *     &lt;constructor-arg&gt;&lt;ref local=&quot;servletContext&quot;/&gt;&lt;/constructor-arg&gt;
- *     &lt;constructor-arg&gt;&lt;value&gt;/WEB-INF/web.xml&lt;/value&gt;&lt;/constructor-arg&gt;
- * &lt;/bean&gt;
- * &lt;bean id=&quot;servletContext&quot; class=&quot;org.springframework.web.context.support.ServletContextFactoryBean&quot;/&gt;
- *
- * </pre>
+ * This <tt>MappableAttributesRetriever</tt> implementation reads the list of defined J2EE
+ * roles from a <tt>web.xml</tt> file and returns these from {{@link #getMappableAttributes()}.
  *
  * @author Ruud Senden
+ * @author Luke Taylor
  * @since 2.0
  */
-public class WebXmlMappableAttributesRetriever extends XmlMappableAttributesRetriever {
-    private static final String XPATH_EXPR = "/web-app/security-role/role-name/text()";
+public class WebXmlMappableAttributesRetriever implements ResourceLoaderAware, MappableAttributesRetriever, InitializingBean {
+    protected final Log logger = LogFactory.getLog(getClass());
+
+    private ResourceLoader resourceLoader;
+    private Set<String> mappableAttributes;
+
+    public void setResourceLoader(ResourceLoader resourceLoader) {
+        this.resourceLoader = resourceLoader;
+    }
+
+
+    public Set<String> getMappableAttributes() {
+        return mappableAttributes;
+    }
+
+    /**
+     * Loads the web.xml file using the configured <tt>ResourceLoader</tt> and
+     * parses the role-name elements from it, using these as the set of <tt>mappableAttributes</tt>.
+     */
+
+    public void afterPropertiesSet() throws Exception {
+        Resource webXml = resourceLoader.getResource("/WEB-INF/web.xml");
+        Document doc = getDocument(webXml.getInputStream());
+        NodeList webApp = doc.getElementsByTagName("web-app");
+        if (webApp.getLength() != 1) {
+            throw new IllegalArgumentException("Failed to find 'web-app' element in resource" + webXml);
+        }
+        NodeList securityRoles = ((Element)webApp.item(0)).getElementsByTagName("security-role");
+
+        ArrayList<String> roleNames = new ArrayList<String>();
+
+        for (int i=0; i < securityRoles.getLength(); i++) {
+            Element secRoleElt = (Element) securityRoles.item(i);
+            NodeList roles = secRoleElt.getElementsByTagName("role-name");
+
+            if (roles.getLength() > 0) {
+                String roleName = ((Element)roles.item(0)).getTextContent().trim();
+                roleNames.add(roleName);
+                logger.info("Retrieved role-name '" + roleName + "' from web.xml");
+            } else {
+                logger.info("No security-role elements found in " + webXml);
+            }
+        }
+
+        mappableAttributes = Collections.unmodifiableSet(new HashSet<String>(roleNames));
+    }
 
     /**
-     * Constructor setting the XPath expression to use
+     * @return Document for the specified InputStream
      */
-    public WebXmlMappableAttributesRetriever() {
-        super.setXpathExpression(XPATH_EXPR);
+    private Document getDocument(InputStream aStream) {
+        Document doc;
+        try {
+            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+            factory.setValidating(false);
+            DocumentBuilder db = factory.newDocumentBuilder();
+            db.setEntityResolver(new MyEntityResolver());
+            doc = db.parse(aStream);
+            return doc;
+        } catch (FactoryConfigurationError e) {
+            throw new RuntimeException("Unable to parse document object", e);
+        } catch (ParserConfigurationException e) {
+            throw new RuntimeException("Unable to parse document object", e);
+        } catch (SAXException e) {
+            throw new RuntimeException("Unable to parse document object", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Unable to parse document object", e);
+        } finally {
+            try {
+                aStream.close();
+            } catch (IOException e) {
+                logger.warn("Failed to close input stream for web.xml", e);
+            }
+        }
     }
 
     /**
-     * @param anInputStream
-     *            The InputStream to read the XML data from
+     * We do not need to resolve external entities, so just return an empty
+     * String.
      */
-    public void setWebXmlInputStream(InputStream anInputStream) {
-        super.setXmlInputStream(anInputStream);
+    private static final class MyEntityResolver implements EntityResolver {
+        public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+            return new InputSource(new StringReader(""));
+        }
     }
 }

+ 34 - 12
web/src/test/java/org/springframework/security/web/authentication/preauth/j2ee/WebXmlJ2eeDefinedRolesRetrieverTests.java

@@ -1,21 +1,34 @@
 package org.springframework.security.web.authentication.preauth.j2ee;
 
-import java.io.InputStream;
+import static org.junit.Assert.*;
+
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
-import org.springframework.security.web.authentication.preauth.j2ee.WebXmlMappableAttributesRetriever;
-
-import junit.framework.TestCase;
+import org.junit.Test;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.ResourceLoader;
 
-public class WebXmlJ2eeDefinedRolesRetrieverTests extends TestCase {
+public class WebXmlJ2eeDefinedRolesRetrieverTests {
 
-    public final void testRole1To4Roles() throws Exception {
-        final List<String> ROLE1TO4_EXPECTED_ROLES = Arrays.asList(new String[] { "Role1", "Role2", "Role3", "Role4" });
-        InputStream role1to4InputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("webxml/Role1-4.web.xml");
+    @Test
+    public void testRole1To4Roles() throws Exception {
+        List<String> ROLE1TO4_EXPECTED_ROLES = Arrays.asList(new String[] { "Role1", "Role2", "Role3", "Role4" });
+        final Resource webXml = new ClassPathResource("webxml/Role1-4.web.xml");
         WebXmlMappableAttributesRetriever rolesRetriever = new WebXmlMappableAttributesRetriever();
-        rolesRetriever.setWebXmlInputStream(role1to4InputStream);
+
+        rolesRetriever.setResourceLoader(new ResourceLoader() {
+            public ClassLoader getClassLoader() {
+                return Thread.currentThread().getContextClassLoader();
+            }
+
+            public Resource getResource(String location) {
+                return webXml;
+            }
+        });
+
         rolesRetriever.afterPropertiesSet();
         Set<String> j2eeRoles = rolesRetriever.getMappableAttributes();
         assertNotNull(j2eeRoles);
@@ -25,10 +38,19 @@ public class WebXmlJ2eeDefinedRolesRetrieverTests extends TestCase {
                 j2eeRoles.containsAll(ROLE1TO4_EXPECTED_ROLES));
     }
 
-    public final void testGetZeroJ2eeRoles() throws Exception {
-        InputStream noRolesInputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("webxml/NoRoles.web.xml");
+    @Test
+    public void testGetZeroJ2eeRoles() throws Exception {
+        final Resource webXml = new ClassPathResource("webxml/NoRoles.web.xml");
         WebXmlMappableAttributesRetriever rolesRetriever = new WebXmlMappableAttributesRetriever();
-        rolesRetriever.setWebXmlInputStream(noRolesInputStream);
+        rolesRetriever.setResourceLoader(new ResourceLoader() {
+            public ClassLoader getClassLoader() {
+                return Thread.currentThread().getContextClassLoader();
+            }
+
+            public Resource getResource(String location) {
+                return webXml;
+            }
+        });
         rolesRetriever.afterPropertiesSet();
         Set<String> j2eeRoles = rolesRetriever.getMappableAttributes();
         assertEquals("J2eeRoles expected size: 0, actual size: " + j2eeRoles.size(), 0, j2eeRoles.size());

+ 5 - 1
web/template.mf

@@ -24,6 +24,7 @@ Import-Template:
  org.springframework.beans.*;version="[3.0.0, 3.1.0)",
  org.springframework.context.*;version="[3.0.0, 3.1.0)",
  org.springframework.core;version="[3.0.0, 3.1.0)",
+ org.springframework.core.io;version="[3.0.0, 3.1.0)",
  org.springframework.dao;version="[3.0.0, 3.1.0)";resolution:=optional,
  org.springframework.expression;version="[3.0.0, 3.1.0)";resolution:=optional,
  org.springframework.expression.spel.*;version="[3.0.0, 3.1.0)";resolution:=optional,
@@ -31,4 +32,7 @@ Import-Template:
  org.springframework.mock.web;version="[3.0.0, 3.1.0)";resolution:=optional,
  org.springframework.web.context.*;version="[3.0.0, 3.1.0)";resolution:=optional,
  org.springframework.util;version="[3.0.0, 3.1.0)";resolution:=optional,
- javax.servlet.*;version="0"
+ org.w3c.dom;version="0";resolution:=optional,
+ org.xml.sax;version="0";resolution:=optional,
+ javax.servlet.*;version="0",
+ javax.xml.parsers.*;version="0";resolution:=optional