浏览代码

SEC-1420: Add htmlEscape attribute to authentication JSP tag.

This allows HTML escaping to be disabled if required.
Luke Taylor 15 年之前
父节点
当前提交
0551dd89ac

+ 8 - 4
core/src/test/java/org/springframework/security/core/userdetails/memory/UserMapTests.java

@@ -15,8 +15,10 @@
 
 package org.springframework.security.core.userdetails.memory;
 
+import static org.junit.Assert.*;
 import junit.framework.TestCase;
 
+import org.junit.Test;
 import org.springframework.security.core.authority.AuthorityUtils;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetails;
@@ -29,9 +31,9 @@ import org.springframework.security.core.userdetails.memory.UserMap;
  *
  * @author Ben Alex
  */
-public class UserMapTests extends TestCase {
+public class UserMapTests {
 
-    //~ Methods ========================================================================================================
+    @Test
     public void testAddAndRetrieveUser() {
         UserDetails rod = new User("rod", "koala", true, true, true, true,
                 AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"));
@@ -50,7 +52,8 @@ public class UserMapTests extends TestCase {
         assertEquals(peter, map.getUser("peter"));
     }
 
-    public void testNullUserCannotBeAdded() {
+    @Test
+    public void nullUserCannotBeAdded() {
         UserMap map = new UserMap();
         assertEquals(0, map.getUserCount());
 
@@ -62,7 +65,8 @@ public class UserMapTests extends TestCase {
         }
     }
 
-    public void testUnknownUserIsNotRetrieved() {
+    @Test
+    public void unknownUserIsNotRetrieved() {
         UserDetails rod = new User("rod", "koala", true, true, true, true,
                 AuthorityUtils.createAuthorityList("ROLE_ONE","ROLE_TWO"));
         UserMap map = new UserMap();

+ 1 - 0
itest/web/src/main/webapp/WEB-INF/in-memory-provider.xml

@@ -12,6 +12,7 @@
               <user name="miles" password="milespassword" authorities="ROLE_USER,ROLE_JAZZ,ROLE_TRUMPETER"/>
               <user name="johnc" password="johncspassword" authorities="ROLE_USER,ROLE_JAZZ,ROLE_SAXOPHONIST"/>
               <user name="jimi" password="jimispassword" authorities="ROLE_USER,ROLE_ROCK,ROLE_GUITARIST"/>
+              <user name="theescapist&lt;&gt;&amp;." password="theescapistspassword" authorities="ROLE_USER"/>
             </user-service>
         </authentication-provider>
     </authentication-manager>

+ 158 - 0
itest/web/src/main/webapp/WEB-INF/security.tld

@@ -0,0 +1,158 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE taglib
+        PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.2//EN"
+        "http://java.sun.com/dtd/web-jsptaglibrary_1_2.dtd">
+<taglib>
+    <tlib-version>1.0</tlib-version>
+    <jsp-version>1.2</jsp-version>
+    <short-name>security</short-name>
+    <uri>http://www.springframework.org/security/tags</uri>
+    <description>
+        Spring Security Authorization Tag Library
+        $Id$
+    </description>
+
+    <tag>
+        <name>authorize</name>
+        <tag-class>org.springframework.security.taglibs.authz.AuthorizeTag</tag-class>
+        <description>
+            A tag which outputs the body of the tag if the configured access expression
+            evaluates to true for the currently authenticated principal.
+        </description>
+
+        <attribute>
+            <name>access</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+            <description>
+                A Spring-EL expression which is supported by the WebSecurityExpressionHandler
+                in the application context. The latter will be used to evaluate the expression.
+            </description>
+        </attribute>
+
+        <attribute>
+            <name>url</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+            <description>
+                A URL within the application. If the user has access to this URL (as determined by
+                the AccessDecisionManager), the tag body will be evaluated. If not, it will
+                be skipped.
+            </description>
+        </attribute>
+
+        <attribute>
+            <name>method</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+            <description>
+                Can optionally be used to narrow down the HTTP method (typically GET or POST) to which the URL
+                applies to. Only has any meaning when used in combination with the "url" attribute.
+            </description>
+        </attribute>
+
+
+        <attribute>
+            <name>ifNotGranted</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                A comma separated list of roles which the user must not have
+                for the body to be output. Deprecated in favour of the access expression.
+            </description>
+        </attribute>
+
+        <attribute>
+            <name>ifAllGranted</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                A comma separated list of roles which the user must all
+                possess for the body to be output. Deprecated in favour of the access expression.
+            </description>
+        </attribute>
+
+        <attribute>
+            <name>ifAnyGranted</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                A comma separated list of roles, one of which the user must
+                possess for the body to be output. Deprecated in favour of the access expression.
+            </description>
+        </attribute>
+    </tag>
+
+    <tag>
+        <name>authentication</name>
+        <tag-class>org.springframework.security.taglibs.authz.AuthenticationTag</tag-class>
+        <description>
+            Allows access to the current Authentication object.
+        </description>
+
+        <attribute>
+            <name>property</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                Property of the Authentication object which should be output. Supports nested
+                properties. For example if the principal object is an instance of UserDetails,
+                the property "principal.username" will return the username. Alternatively, using
+                "name" will call getName method on the Authentication object directly.
+            </description>
+        </attribute>
+        <attribute>
+            <name>var</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+            <description>
+                Name of the exported scoped variable which will contain the
+                evaluated property of the Authentication object.
+            </description>
+        </attribute>
+        <attribute>
+            <description>Set HTML escaping for this tag, as a boolean value.</description>
+            <name>htmlEscape</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>scope</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+            <description>
+                Scope for var.
+            </description>
+        </attribute>
+    </tag>
+
+    <tag>
+        <name>accesscontrollist</name>
+        <tag-class>org.springframework.security.taglibs.authz.AccessControlListTag</tag-class>
+        <description>
+            Allows inclusion of a tag body if the current Authentication
+            has one of the specified permissions to the presented
+            domain object instance.
+        </description>
+
+        <attribute>
+            <name>hasPermission</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                A comma separated list of permissions, which will be converted to
+                Permission instances by the configured PermissionFactory.
+            </description>
+        </attribute>
+        <attribute>
+            <name>domainObject</name>
+            <required>true</required>
+            <rtexprvalue>true</rtexprvalue>
+            <description>
+                The actual domain object instance for which permissions
+                are being evaluated.
+            </description>
+        </attribute>
+    </tag>
+
+</taglib>

+ 12 - 0
itest/web/src/test/java/org/springframework/security/integration/InMemoryProviderWebAppTests.java

@@ -95,4 +95,16 @@ public class InMemoryProviderWebAppTests extends AbstractWebServerIntegrationTes
         tester.assertTextPresent("This session has been expired");
     }
 
+    @Test
+    public void authenticationTagEscapingWorksCorrectly() {
+        beginAt("secure/authenticationTagTestPage.jsp");
+        login("theescapist<>&.", "theescapistspassword");
+        String response = tester.getServerResponse();
+        assertTrue(response.contains("This is the unescaped authentication name: theescapist<>&."));
+        assertTrue(response.contains("This is the unescaped principal.username: theescapist<>&."));
+        assertTrue(response.contains("This is the authentication name: theescapist&lt;&gt;&amp;&#46;"));
+        assertTrue(response.contains("This is the principal.username: theescapist&lt;&gt;&amp;&#46;"));
+    }
+
+
 }

+ 24 - 1
taglibs/src/main/java/org/springframework/security/taglibs/authz/AuthenticationTag.java

@@ -23,6 +23,7 @@ import org.springframework.security.web.util.TextEscapeUtils;
 
 import org.springframework.beans.BeanWrapperImpl;
 import org.springframework.beans.BeansException;
+import org.springframework.web.util.ExpressionEvaluationUtils;
 import org.springframework.web.util.TagUtils;
 
 import java.io.IOException;
@@ -48,6 +49,7 @@ public class AuthenticationTag extends TagSupport {
     private String property;
     private int scope;
     private boolean scopeSpecified;
+    private boolean htmlEscape = true;
 
 
     //~ Methods ========================================================================================================
@@ -120,7 +122,11 @@ public class AuthenticationTag extends TagSupport {
                 }
             }
         } else {
-            writeMessage(TextEscapeUtils.escapeEntities(String.valueOf(result)));
+            if (htmlEscape) {
+                writeMessage(TextEscapeUtils.escapeEntities(String.valueOf(result)));
+            } else {
+                writeMessage(String.valueOf(result));
+            }
         }
         return EVAL_PAGE;
     }
@@ -132,4 +138,21 @@ public class AuthenticationTag extends TagSupport {
             throw new JspException(ioe);
         }
     }
+
+
+    /**
+     * Set HTML escaping for this tag, as boolean value.
+     */
+    public void setHtmlEscape(String htmlEscape) throws JspException {
+        this.htmlEscape = ExpressionEvaluationUtils.evaluateBoolean("htmlEscape", htmlEscape, pageContext);
+    }
+
+    /**
+     * Return the HTML escaping setting for this tag,
+     * or the default setting if not overridden.
+     * @see #isDefaultHtmlEscape()
+     */
+    protected boolean isHtmlEscape() {
+        return htmlEscape;
+    }
 }

+ 6 - 0
taglibs/src/main/resources/META-INF/security.tld

@@ -110,6 +110,12 @@
                 evaluated property of the Authentication object.
             </description>
         </attribute>
+        <attribute>
+            <description>Set HTML escaping for this tag, as a boolean value.</description>
+            <name>htmlEscape</name>
+            <required>false</required>
+            <rtexprvalue>true</rtexprvalue>
+        </attribute>
         <attribute>
             <name>scope</name>
             <required>false</required>

+ 33 - 4
taglibs/src/test/java/org/springframework/security/taglibs/authz/AuthenticationTagTests.java

@@ -15,11 +15,13 @@
 
 package org.springframework.security.taglibs.authz;
 
+import static org.junit.Assert.*;
+
 import javax.servlet.jsp.JspException;
 import javax.servlet.jsp.tagext.Tag;
 
-import junit.framework.TestCase;
-
+import org.junit.After;
+import org.junit.Test;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.authority.AuthorityUtils;
@@ -32,7 +34,7 @@ import org.springframework.security.core.userdetails.User;
  *
  * @author Ben Alex
  */
-public class AuthenticationTagTests extends TestCase {
+public class AuthenticationTagTests {
     //~ Instance fields ================================================================================================
 
     private final MyAuthenticationTag authenticationTag = new MyAuthenticationTag();
@@ -41,10 +43,12 @@ public class AuthenticationTagTests extends TestCase {
 
     //~ Methods ========================================================================================================
 
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() {
         SecurityContextHolder.clearContext();
     }
 
+    @Test
     public void testOperationWhenPrincipalIsAUserDetailsInstance()throws JspException {
         SecurityContextHolder.getContext().setAuthentication(auth);
 
@@ -54,6 +58,7 @@ public class AuthenticationTagTests extends TestCase {
         assertEquals("rodUserDetails", authenticationTag.getLastMessage());
     }
 
+    @Test
     public void testOperationWhenPrincipalIsAString() throws JspException {
         SecurityContextHolder.getContext().setAuthentication(
                 new TestingAuthenticationToken("rodAsString", "koala", AuthorityUtils.NO_AUTHORITIES ));
@@ -64,6 +69,7 @@ public class AuthenticationTagTests extends TestCase {
         assertEquals("rodAsString", authenticationTag.getLastMessage());
     }
 
+    @Test
     public void testNestedPropertyIsReadCorrectly() throws JspException {
         SecurityContextHolder.getContext().setAuthentication(auth);
 
@@ -73,6 +79,7 @@ public class AuthenticationTagTests extends TestCase {
         assertEquals("rodUserDetails", authenticationTag.getLastMessage());
     }
 
+    @Test
     public void testOperationWhenPrincipalIsNull() throws JspException {
         SecurityContextHolder.getContext().setAuthentication(
                 new TestingAuthenticationToken(null, "koala", AuthorityUtils.NO_AUTHORITIES ));
@@ -82,6 +89,7 @@ public class AuthenticationTagTests extends TestCase {
         assertEquals(Tag.EVAL_PAGE, authenticationTag.doEndTag());
     }
 
+    @Test
     public void testOperationWhenSecurityContextIsNull() throws Exception {
         SecurityContextHolder.getContext().setAuthentication(null);
 
@@ -91,12 +99,14 @@ public class AuthenticationTagTests extends TestCase {
         assertEquals(null, authenticationTag.getLastMessage());
     }
 
+    @Test
     public void testSkipsBodyIfNullOrEmptyOperation() throws Exception {
         authenticationTag.setProperty("");
         assertEquals(Tag.SKIP_BODY, authenticationTag.doStartTag());
         assertEquals(Tag.EVAL_PAGE, authenticationTag.doEndTag());
     }
 
+    @Test
     public void testThrowsExceptionForUnrecognisedProperty() {
         SecurityContextHolder.getContext().setAuthentication(auth);
         authenticationTag.setProperty("qsq");
@@ -109,6 +119,25 @@ public class AuthenticationTagTests extends TestCase {
         }
     }
 
+    @Test
+    public void htmlEscapingIsUsedByDefault() throws Exception {
+        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("<>& ", ""));
+        authenticationTag.setProperty("name");
+        authenticationTag.doStartTag();
+        authenticationTag.doEndTag();
+        assertEquals("&lt;&gt;&amp;&#32;", authenticationTag.getLastMessage());
+    }
+
+    @Test
+    public void settingHtmlEscapeToFalsePreventsEscaping() throws Exception {
+        SecurityContextHolder.getContext().setAuthentication(new TestingAuthenticationToken("<>& ", ""));
+        authenticationTag.setProperty("name");
+        authenticationTag.setHtmlEscape("false");
+        authenticationTag.doStartTag();
+        authenticationTag.doEndTag();
+        assertEquals("<>& ", authenticationTag.getLastMessage());
+    }
+
     //~ Inner Classes ==================================================================================================
 
     private class MyAuthenticationTag extends AuthenticationTag {