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

This allows HTML escaping to be disabled if required.
This commit is contained in:
Luke Taylor 2010-03-04 00:47:22 +00:00
parent 43f3568b16
commit 0551dd89ac
7 changed files with 242 additions and 9 deletions

View File

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

View File

@ -12,6 +12,7 @@
<user name="miles" password="milespassword" authorities="ROLE_USER,ROLE_JAZZ,ROLE_TRUMPETER"/> <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="johnc" password="johncspassword" authorities="ROLE_USER,ROLE_JAZZ,ROLE_SAXOPHONIST"/>
<user name="jimi" password="jimispassword" authorities="ROLE_USER,ROLE_ROCK,ROLE_GUITARIST"/> <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> </user-service>
</authentication-provider> </authentication-provider>
</authentication-manager> </authentication-manager>

View File

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

View File

@ -95,4 +95,16 @@ public class InMemoryProviderWebAppTests extends AbstractWebServerIntegrationTes
tester.assertTextPresent("This session has been expired"); 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;"));
}
} }

View File

@ -23,6 +23,7 @@ import org.springframework.security.web.util.TextEscapeUtils;
import org.springframework.beans.BeanWrapperImpl; import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.web.util.ExpressionEvaluationUtils;
import org.springframework.web.util.TagUtils; import org.springframework.web.util.TagUtils;
import java.io.IOException; import java.io.IOException;
@ -48,6 +49,7 @@ public class AuthenticationTag extends TagSupport {
private String property; private String property;
private int scope; private int scope;
private boolean scopeSpecified; private boolean scopeSpecified;
private boolean htmlEscape = true;
//~ Methods ======================================================================================================== //~ Methods ========================================================================================================
@ -120,7 +122,11 @@ public class AuthenticationTag extends TagSupport {
} }
} }
} else { } else {
if (htmlEscape) {
writeMessage(TextEscapeUtils.escapeEntities(String.valueOf(result))); writeMessage(TextEscapeUtils.escapeEntities(String.valueOf(result)));
} else {
writeMessage(String.valueOf(result));
}
} }
return EVAL_PAGE; return EVAL_PAGE;
} }
@ -132,4 +138,21 @@ public class AuthenticationTag extends TagSupport {
throw new JspException(ioe); 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;
}
} }

View File

@ -110,6 +110,12 @@
evaluated property of the Authentication object. evaluated property of the Authentication object.
</description> </description>
</attribute> </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> <attribute>
<name>scope</name> <name>scope</name>
<required>false</required> <required>false</required>

View File

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