From c5900cab9c26f86416e7c1ddb4900124ed67a1a7 Mon Sep 17 00:00:00 2001 From: Ben Alex Date: Sun, 5 Dec 2004 06:11:18 +0000 Subject: [PATCH] Added a ContextHolderAwareRequestWrapper to integrate with getRemoteUser() and isUserInRole(String). Thanks to Orlando Garcia Carmona ("paramosyermos" on Spring forums). --- .../ContextHolderAwareRequestFilter.java | 54 +++++++ .../ContextHolderAwareRequestWrapper.java | 117 +++++++++++++++ .../org/acegisecurity/ui/wrapper/package.html | 9 ++ .../ContextHolderAwareRequestFilterTests.java | 94 ++++++++++++ ...ContextHolderAwareRequestWrapperTests.java | 139 ++++++++++++++++++ doc/xdocs/changes.xml | 1 + project.xml | 3 + 7 files changed, 417 insertions(+) create mode 100644 core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilter.java create mode 100644 core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapper.java create mode 100644 core/src/main/java/org/acegisecurity/ui/wrapper/package.html create mode 100644 core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilterTests.java create mode 100644 core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapperTests.java diff --git a/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilter.java b/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilter.java new file mode 100644 index 0000000000..6001adfd2b --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilter.java @@ -0,0 +1,54 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.ui.wrapper; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + + +/** + * A Filter which populates the ServletRequest with + * an {@link ContextHolderAwareRequestWrapper}. + * + * @author Orlando Garcia Carmona + * @version $Id$ + */ +public class ContextHolderAwareRequestFilter implements Filter { + //~ Methods ================================================================ + + public void destroy() {} + + public void doFilter(ServletRequest servletRequest, + ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + + if (!(request instanceof ContextHolderAwareRequestWrapper)) { + request = new ContextHolderAwareRequestWrapper(request); + } + + filterChain.doFilter(request, servletResponse); + } + + public void init(FilterConfig filterConfig) throws ServletException {} +} diff --git a/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapper.java b/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapper.java new file mode 100644 index 0000000000..8d81849a8e --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapper.java @@ -0,0 +1,117 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.ui.wrapper; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.SecureContext; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + + +/** + * An Acegi Security-aware HttpServletRequestWrapper, which uses + * the ContextHolder-defined Authentication object + * for {@link ContextHolderAwareRequestWrapper#isUserInRole(java.lang.String)} + * and {@link javax.servlet.http.HttpServletRequestWrapper#getRemoteUser()} + * responses. + * + * @author Orlando Garcia Carmona + * @author Ben Alex + * @version $Id$ + */ +public class ContextHolderAwareRequestWrapper extends HttpServletRequestWrapper { + //~ Constructors =========================================================== + + public ContextHolderAwareRequestWrapper(HttpServletRequest request) { + super(request); + } + + //~ Methods ================================================================ + + /** + * Returns the principal's name, as obtained from the + * ContextHolder. Properly handles both + * String-based and UserDetails-based + * principals. + * + * @return the username or null if unavailable + */ + public String getRemoteUser() { + Authentication auth = getAuthentication(); + + if ((auth == null) || (auth.getPrincipal() == null)) { + return null; + } + + if (auth.getPrincipal() instanceof UserDetails) { + return ((UserDetails) auth.getPrincipal()).getUsername(); + } + + return auth.getPrincipal().toString(); + } + + /** + * Simple searches for an exactly matching {@link + * GrantedAuthority#getAuthority()}. + * + *

+ * Will always return false if the ContextHolder + * contains an Authentication with + * nullprincipal and/or + * GrantedAuthority[] objects. + *

+ * + * @param role the GrantedAuthorityString + * representation to check for + * + * @return true if an exact (case sensitive) matching + * granted authority is located, false otherwise + */ + public boolean isUserInRole(String role) { + return isGranted(role); + } + + private Authentication getAuthentication() { + if ((ContextHolder.getContext() != null) + && ContextHolder.getContext() instanceof SecureContext) { + return ((SecureContext) ContextHolder.getContext()) + .getAuthentication(); + } + + return null; + } + + private boolean isGranted(String role) { + Authentication auth = getAuthentication(); + + if ((auth == null) || (auth.getPrincipal() == null) + || (auth.getAuthorities() == null)) { + return false; + } + + for (int i = 0; i < auth.getAuthorities().length; i++) { + if (role.equals(auth.getAuthorities()[i].getAuthority())) { + return true; + } + } + + return false; + } +} diff --git a/core/src/main/java/org/acegisecurity/ui/wrapper/package.html b/core/src/main/java/org/acegisecurity/ui/wrapper/package.html new file mode 100644 index 0000000000..913908a2e0 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/ui/wrapper/package.html @@ -0,0 +1,9 @@ + + +Populates a Servlet request with a new Acegi Security compliant +HttpServletRequestWrapper. + +

To use, simply add the ContextHolderAwareRequestFilter +to web.xml. + + diff --git a/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilterTests.java b/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilterTests.java new file mode 100644 index 0000000000..eb185435d3 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestFilterTests.java @@ -0,0 +1,94 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.ui.wrapper; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.MockFilterConfig; +import net.sf.acegisecurity.MockHttpServletRequest; + +import java.io.IOException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; + + +/** + * Tests {@link ContextHolderAwareRequestFilter}. + * + * @author Ben Alex + * @version $Id$ + */ +public class ContextHolderAwareRequestFilterTests extends TestCase { + //~ Constructors =========================================================== + + public ContextHolderAwareRequestFilterTests() { + super(); + } + + public ContextHolderAwareRequestFilterTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(ContextHolderAwareRequestFilterTests.class); + } + + public void testCorrectOperation() throws Exception { + ContextHolderAwareRequestFilter filter = new ContextHolderAwareRequestFilter(); + filter.init(new MockFilterConfig()); + filter.doFilter(new MockHttpServletRequest(null, null), null, + new MockFilterChain(ContextHolderAwareRequestWrapper.class)); + + // Now re-execute the filter, ensuring our replacement wrapper is still used + filter.doFilter(new MockHttpServletRequest(null, null), null, + new MockFilterChain(ContextHolderAwareRequestWrapper.class)); + + filter.destroy(); + } + + //~ Inner Classes ========================================================== + + private class MockFilterChain implements FilterChain { + private Class expectedServletRequest; + + public MockFilterChain(Class expectedServletRequest) { + this.expectedServletRequest = expectedServletRequest; + } + + private MockFilterChain() { + super(); + } + + public void doFilter(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + if (request.getClass().isAssignableFrom(expectedServletRequest)) { + assertTrue(true); + } else { + fail("Expected class to be of type " + expectedServletRequest + + " but was: " + request.getClass()); + } + } + } +} diff --git a/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapperTests.java b/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapperTests.java new file mode 100644 index 0000000000..13aa9a8505 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/ui/wrapper/ContextHolderAwareRequestWrapperTests.java @@ -0,0 +1,139 @@ +/* Copyright 2004 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.sf.acegisecurity.ui.wrapper; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.MockHttpServletRequest; +import net.sf.acegisecurity.context.ContextHolder; +import net.sf.acegisecurity.context.SecureContext; +import net.sf.acegisecurity.context.SecureContextImpl; +import net.sf.acegisecurity.providers.TestingAuthenticationToken; +import net.sf.acegisecurity.providers.dao.User; + + +/** + * Tests {@link ContextHolderAwareRequestWrapper}. + * + * @author Ben Alex + * @version $Id$ + */ +public class ContextHolderAwareRequestWrapperTests extends TestCase { + //~ Constructors =========================================================== + + public ContextHolderAwareRequestWrapperTests() { + super(); + } + + public ContextHolderAwareRequestWrapperTests(String arg0) { + super(arg0); + } + + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(ContextHolderAwareRequestWrapperTests.class); + } + + public void testCorrectOperationWithStringBasedPrincipal() + throws Exception { + SecureContext sc = new SecureContextImpl(); + Authentication auth = new TestingAuthenticationToken("marissa", + "koala", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_FOO")}); + sc.setAuthentication(auth); + ContextHolder.setContext(sc); + + ContextHolderAwareRequestWrapper wrapper = new ContextHolderAwareRequestWrapper(new MockHttpServletRequest( + "/")); + + assertEquals("marissa", wrapper.getRemoteUser()); + assertTrue(wrapper.isUserInRole("ROLE_FOO")); + assertFalse(wrapper.isUserInRole("ROLE_NOT_GRANTED")); + + ContextHolder.setContext(null); + } + + public void testCorrectOperationWithUserDetailsBasedPrincipal() + throws Exception { + SecureContext sc = new SecureContextImpl(); + Authentication auth = new TestingAuthenticationToken(new User( + "marissaAsUserDetails", "koala", true, + new GrantedAuthority[] {}), "koala", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_HELLO"), new GrantedAuthorityImpl( + "ROLE_FOOBAR")}); + sc.setAuthentication(auth); + ContextHolder.setContext(sc); + + ContextHolderAwareRequestWrapper wrapper = new ContextHolderAwareRequestWrapper(new MockHttpServletRequest( + "/")); + + assertEquals("marissaAsUserDetails", wrapper.getRemoteUser()); + assertFalse(wrapper.isUserInRole("ROLE_FOO")); + assertFalse(wrapper.isUserInRole("ROLE_NOT_GRANTED")); + assertTrue(wrapper.isUserInRole("ROLE_FOOBAR")); + assertTrue(wrapper.isUserInRole("ROLE_HELLO")); + + ContextHolder.setContext(null); + } + + public void testNullAuthenticationHandling() throws Exception { + SecureContext sc = new SecureContextImpl(); + sc.setAuthentication(null); + ContextHolder.setContext(sc); + + ContextHolderAwareRequestWrapper wrapper = new ContextHolderAwareRequestWrapper(new MockHttpServletRequest( + "/")); + assertNull(wrapper.getRemoteUser()); + assertFalse(wrapper.isUserInRole("ROLE_ANY")); + + ContextHolder.setContext(null); + } + + public void testNullContextHolderHandling() throws Exception { + ContextHolder.setContext(null); + + ContextHolderAwareRequestWrapper wrapper = new ContextHolderAwareRequestWrapper(new MockHttpServletRequest( + "/")); + assertNull(wrapper.getRemoteUser()); + assertFalse(wrapper.isUserInRole("ROLE_ANY")); + } + + public void testNullPrincipalHandling() throws Exception { + SecureContext sc = new SecureContextImpl(); + Authentication auth = new TestingAuthenticationToken(null, "koala", + new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_HELLO"), new GrantedAuthorityImpl( + "ROLE_FOOBAR")}); + sc.setAuthentication(auth); + ContextHolder.setContext(sc); + + ContextHolderAwareRequestWrapper wrapper = new ContextHolderAwareRequestWrapper(new MockHttpServletRequest( + "/")); + + assertNull(wrapper.getRemoteUser()); + assertFalse(wrapper.isUserInRole("ROLE_HELLO")); // principal is null, so reject + assertFalse(wrapper.isUserInRole("ROLE_FOOBAR")); // principal is null, so reject + + ContextHolder.setContext(null); + } +} diff --git a/doc/xdocs/changes.xml b/doc/xdocs/changes.xml index 8645324d7d..fd34408a12 100644 --- a/doc/xdocs/changes.xml +++ b/doc/xdocs/changes.xml @@ -43,6 +43,7 @@ Added BasicAclExtendedDao interface and JdbcExtendedDaoImpl for ACL CRUD Added additional remoting protocol demonstrations to Contacts sample Added AbstractProcessingFilter property to always use defaultTargetUrl + Added ContextHolderAwareRequestWrapper to integrate with getRemoteUser() Improved BasicAclProvider to only respond to specified ACL object requests Refactored MethodDefinitionSource to work with Method, not MethodInvocation Refactored AbstractFilterInvocationDefinitionSource to work with URL Strings alone diff --git a/project.xml b/project.xml index 9535692d49..93f36fba5e 100644 --- a/project.xml +++ b/project.xml @@ -126,6 +126,9 @@ Aaron Tang + + Orlando Garcia Carmona +