1
0
mirror of synced 2026-05-22 21:33:16 +00:00

SEC-525: [PATCH] Add AccessCheckerTag based on URL resource access permissions. Added functionality to "authorize" tag to allow evaluation of whether a particual url is accessible to the user. Uses a WebInvocationPrivilegeEvaluator registered in the application context.

This commit is contained in:
Luke Taylor
2009-09-16 00:23:13 +00:00
parent 1c4a809e09
commit 731402e9f5
10 changed files with 719 additions and 121 deletions
@@ -8,6 +8,7 @@ import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import org.springframework.context.ApplicationContext;
@@ -17,31 +18,43 @@ import org.springframework.security.access.expression.ExpressionUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.WebSecurityExpressionHandler;
import org.springframework.web.context.support.WebApplicationContextUtils;
/**
* Expression-based access control tag.
*
* Access control tag which evaluates its body based either on
* <ul>
* <li>an access expression (the "access" attribute), or</li>
* <li>by evaluating the current user's right to access a particular URL (set using the "url" attribute).</li>
* </ul>
* @author Luke Taylor
* @version $Id$
* @since 3.0
*/
public class AuthorizeTag extends LegacyAuthorizeTag {
private String access;
private String url;
private String method;
// If access expression evaluates to "true" return
public int doStartTag() throws JspException {
if (access == null || access.length() == 0) {
return super.doStartTag();
}
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
if (currentUser == null) {
return SKIP_BODY;
}
if (access != null && access.length() > 0) {
return authorizeUsingAccessExpression(currentUser);
} else if (url != null && url.length() > 0) {
return authorizeUsingUrlCheck(currentUser);
}
return super.doStartTag();
}
private int authorizeUsingAccessExpression(Authentication currentUser) throws JspException {
// Get web expression
WebSecurityExpressionHandler handler = getExpressionHandler();
@@ -62,10 +75,23 @@ public class AuthorizeTag extends LegacyAuthorizeTag {
return SKIP_BODY;
}
private int authorizeUsingUrlCheck(Authentication currentUser) throws JspException {
return getPrivilegeEvaluator().isAllowed(((HttpServletRequest)pageContext.getRequest()).getContextPath(),
url, method, currentUser) ? EVAL_BODY_INCLUDE : SKIP_BODY;
}
public void setAccess(String access) {
this.access = access;
}
public void setUrl(String url) {
this.url = url;
}
public void setMethod(String method) {
this.method = method;
}
WebSecurityExpressionHandler getExpressionHandler() throws JspException {
ServletContext servletContext = pageContext.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
@@ -73,12 +99,25 @@ public class AuthorizeTag extends LegacyAuthorizeTag {
if (expressionHdlrs.size() == 0) {
throw new JspException("No visible WebSecurityExpressionHandler instance could be found in the application " +
"context. There must be at least one in order to use expressions with taglib support.");
"context. There must be at least one in order to support expressions in JSP 'authorize' tags.");
}
return (WebSecurityExpressionHandler) expressionHdlrs.values().toArray()[0];
}
WebInvocationPrivilegeEvaluator getPrivilegeEvaluator() throws JspException {
ServletContext servletContext = pageContext.getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);
Map<String, WebInvocationPrivilegeEvaluator> wipes = ctx.getBeansOfType(WebInvocationPrivilegeEvaluator.class);
if (wipes.size() == 0) {
throw new JspException("No visible WebInvocationPrivilegeEvaluator instance could be found in the application " +
"context. There must be at least one in order to support the use of URL access checks in 'authorize' tags.");
}
return (WebInvocationPrivilegeEvaluator) wipes.values().toArray()[0];
}
private static final FilterChain DUMMY_CHAIN = new FilterChain() {
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
throw new UnsupportedOperationException();
@@ -30,6 +30,28 @@
</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>
@@ -28,7 +28,9 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockPageContext;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
@@ -36,6 +38,7 @@ import org.springframework.web.context.support.StaticWebApplicationContext;
/**
* @author Francois Beausoleil
* @author Luke Taylor
* @version $Id$
*/
public class AuthorizeTagTests {
@@ -51,6 +54,7 @@ public class AuthorizeTagTests {
SecurityContextHolder.getContext().setAuthentication(currentUser);
StaticWebApplicationContext ctx = new StaticWebApplicationContext();
ctx.registerSingleton("expressionHandler", DefaultWebSecurityExpressionHandler.class);
ctx.registerSingleton("wipe", MockWebInvocationPrivilegeEvaluator.class);
MockServletContext servletCtx = new MockServletContext();
servletCtx.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ctx);
authorizeTag = new AuthorizeTag();
@@ -82,6 +86,35 @@ public class AuthorizeTagTests {
authorizeTag.setAccess("permitAll");
assertEquals(Tag.EVAL_BODY_INCLUDE, authorizeTag.doStartTag());
}
// url attribute tests
@Test
public void skipsBodyWithUrlSetIfNoAuthenticationPresent() throws Exception {
SecurityContextHolder.clearContext();
authorizeTag.setUrl("/something");
assertEquals(Tag.SKIP_BODY, authorizeTag.doStartTag());
}
@Test
public void skipsBodyIfUrlIsNotAllowed() throws Exception {
authorizeTag.setUrl("/notallowed");
assertEquals(Tag.SKIP_BODY, authorizeTag.doStartTag());
}
@Test
public void evaluatesBodyIfUrlIsAllowed() throws Exception {
authorizeTag.setUrl("/allowed");
authorizeTag.setMethod("GET");
assertEquals(Tag.EVAL_BODY_INCLUDE, authorizeTag.doStartTag());
}
@Test
public void skipsBodyIfMethodIsNotAllowed() throws Exception {
authorizeTag.setUrl("/allowed");
authorizeTag.setMethod("POST");
assertEquals(Tag.SKIP_BODY, authorizeTag.doStartTag());
}
// Legacy attribute tests
@Test
@@ -144,4 +177,15 @@ public class AuthorizeTagTests {
authorizeTag.setIfNotGranted("ROLE_TELLER");
assertEquals("prevents request - principal has ROLE_TELLER", Tag.SKIP_BODY, authorizeTag.doStartTag());
}
public static class MockWebInvocationPrivilegeEvaluator implements WebInvocationPrivilegeEvaluator {
public boolean isAllowed(String uri, Authentication authentication) {
return "/allowed".equals(uri);
}
public boolean isAllowed(String contextPath, String uri, String method, Authentication authentication) {
return "/allowed".equals(uri) && (method == null || "GET".equals(method));
}
}
}