diff --git a/core/src/main/java/org/springframework/security/authoritymapping/SimpleMappableAttributesRetriever.java b/core/src/main/java/org/springframework/security/authoritymapping/SimpleMappableAttributesRetriever.java index ec205ea36a..d1d6543c68 100755 --- a/core/src/main/java/org/springframework/security/authoritymapping/SimpleMappableAttributesRetriever.java +++ b/core/src/main/java/org/springframework/security/authoritymapping/SimpleMappableAttributesRetriever.java @@ -1,6 +1,5 @@ package org.springframework.security.authoritymapping; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -25,9 +24,10 @@ public class SimpleMappableAttributesRetriever implements MappableAttributesRetr return mappableAttributes; } - public void setMappableAttributes(String[] aMappableRoles) { - mappableAttributes = new HashSet(aMappableRoles.length); - mappableAttributes.addAll(Arrays.asList(aMappableRoles)); + @SuppressWarnings("unchecked") + public void setMappableAttributes(Set aMappableRoles) { + mappableAttributes = new HashSet(); + mappableAttributes.addAll(aMappableRoles); mappableAttributes = Collections.unmodifiableSet(mappableAttributes); } diff --git a/core/src/test/java/org/springframework/security/authoritymapping/SimpleMappableRolesRetrieverTests.java b/core/src/test/java/org/springframework/security/authoritymapping/SimpleMappableRolesRetrieverTests.java index 208e74eeb7..7f9a37b6a4 100755 --- a/core/src/test/java/org/springframework/security/authoritymapping/SimpleMappableRolesRetrieverTests.java +++ b/core/src/test/java/org/springframework/security/authoritymapping/SimpleMappableRolesRetrieverTests.java @@ -1,26 +1,27 @@ -package org.springframework.security.authoritymapping; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Set; - -import junit.framework.TestCase; - -/** - * - * @author TSARDD - * @since 18-okt-2007 - */ -public class SimpleMappableRolesRetrieverTests extends TestCase { - - public final void testGetSetMappableRoles() { - String[] roles = new String[] { "Role1", "Role2" }; - SimpleMappableAttributesRetriever r = new SimpleMappableAttributesRetriever(); - r.setMappableAttributes(roles); - Set result = r.getMappableAttributes(); - Collection rolesColl = Arrays.asList(roles); - assertTrue("Role collections do not match; result: " + result + ", expected: " + rolesColl, rolesColl.containsAll(result) - && result.containsAll(rolesColl)); - } - -} +package org.springframework.security.authoritymapping; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Set; +import java.util.HashSet; + +import junit.framework.TestCase; +import org.springframework.util.StringUtils; + +/** + * + * @author TSARDD + * @since 18-okt-2007 + */ +public class SimpleMappableRolesRetrieverTests extends TestCase { + + public final void testGetSetMappableRoles() { + Set roles = StringUtils.commaDelimitedListToSet("Role1,Role2"); + SimpleMappableAttributesRetriever r = new SimpleMappableAttributesRetriever(); + r.setMappableAttributes(roles); + Set result = r.getMappableAttributes(); + assertTrue("Role collections do not match; result: " + result + ", expected: " + roles, roles.containsAll(result) + && result.containsAll(roles)); + } + +} diff --git a/pom.xml b/pom.xml index ab2a294bba..ef468e3c04 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ samples taglibs itest - + portlet Spring Security diff --git a/portlet/pom.xml b/portlet/pom.xml index 8d587d1093..c0f0802bbd 100644 --- a/portlet/pom.xml +++ b/portlet/pom.xml @@ -12,9 +12,9 @@ org.springframework.security - spring-security-core + spring-security-web ${project.version} - + javax.servlet servlet-api @@ -22,13 +22,13 @@ javax.portlet portlet-api - 1.0 + 2.0 provided org.springframework - spring-portlet - 2.0.8 + org.springframework.web.portlet + ${spring.version} org.springframework @@ -42,4 +42,4 @@ - \ No newline at end of file + diff --git a/portlet/src/main/java/org/springframework/security/context/PortletSessionContextIntegrationInterceptor.java b/portlet/src/main/java/org/springframework/security/context/PortletSessionContextIntegrationInterceptor.java index 15008a1e11..247e7bd080 100644 --- a/portlet/src/main/java/org/springframework/security/context/PortletSessionContextIntegrationInterceptor.java +++ b/portlet/src/main/java/org/springframework/security/context/PortletSessionContextIntegrationInterceptor.java @@ -1,437 +1,468 @@ -/* - * Copyright 2005-2007 the original author or authors. - * - * 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 org.springframework.security.context; - -import java.lang.reflect.Method; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.PortletException; -import javax.portlet.PortletRequest; -import javax.portlet.PortletResponse; -import javax.portlet.PortletSession; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.util.ReflectionUtils; -import org.springframework.web.portlet.HandlerInterceptor; -import org.springframework.web.portlet.ModelAndView; - -/** - *

This interceptor populates the {@link SecurityContextHolder} with information obtained from the - * PortletSession. It is applied to both ActionRequests and - * RenderRequests

- * - *

The PortletSession will be queried to retrieve the SecurityContext that should - * be stored against the SecurityContextHolder for the duration of the portlet request. At the - * end of the request, any updates made to the SecurityContextHolder will be persisted back to the - * PortletSession by this interceptor.

- * - *

If a valid SecurityContext cannot be obtained from the PortletSession for - * whatever reason, a fresh SecurityContext will be created and used instead. The created object - * will be of the instance defined by the {@link #setContext(Class)} method (which defaults to - * {@link org.springframework.security.context.SecurityContextImpl}.

- * - *

A PortletSession may be created by this interceptor if one does not already exist. If at the - * end of the portlet request the PortletSession does not exist, one will only be created if - * the current contents of the SecurityContextHolder are not the {@link java.lang.Object#equals} - * to a new instance of {@link #context}. This avoids needless PortletSession creation, - * and automates the storage of changes made to the SecurityContextHolder. There is one exception to - * this rule, that is if the {@link #forceEagerSessionCreation} property is true, in which case - * sessions will always be created irrespective of normal session-minimization logic (the default is - * false, as this is resource intensive and not recommended).

- * - *

If for whatever reason no PortletSession should ever be created, the - * {@link #allowSessionCreation} property should be set to false. Only do this if you really need - * to conserve server memory and ensure all classes using the SecurityContextHolder are designed to - * have no persistence of the SecurityContext between web requests. Please note that if - * {@link #forceEagerSessionCreation} is true, the allowSessionCreation must also be - * true (setting it to false will cause a startup-time error).

- - *

This interceptor must be executed before

any authentication processing mechanisms. These - * mechanisms (specifically {@link org.springframework.security.ui.portlet.PortletProcessingInterceptor}) expect the - * SecurityContextHolder to contain a valid SecurityContext by the time they execute.

- * - *

An important nuance to this interceptor is that (by default) the SecurityContext is stored - * into the APPLICATION_SCOPE of the PortletSession. This doesn't just mean you will be - * sharing it with all the other portlets in your webapp (which is generally a good idea). It also means that (if - * you have done all the other appropriate magic), you will share this SecurityContext with servlets in - * your webapp. This is very useful if you have servlets serving images or processing AJAX calls from your portlets - * since they can now use the {@link HttpSessionContextIntegrationFilter} to access the same SecurityContext - * object from the session. This allows these calls to be secured as well as the portlet calls.

- * - * Much of the logic of this interceptor comes from the {@link HttpSessionContextIntegrationFilter} class which - * fills the same purpose on the servlet side. Ben Alex and Patrick Burlson are listed as authors here because they - * are the authors of that class and there are blocks of code that essentially identical between the two. (Making this - * a good candidate for refactoring someday.) - * - *

Unlike HttpSessionContextIntegrationFilter, this interceptor does not check to see if it is - * getting applied multiple times. This shouldn't be a problem since the application of interceptors is under the - * control of the Spring Portlet MVC framework and tends to be more explicit and more predictable than the application - * of filters. However, you should still be careful to only apply this inteceptor to your request once.

- * - * @author John A. Lewis - * @author Ben Alex - * @author Patrick Burleson - * @since 2.0 - * @version $Id$ - */ -public class PortletSessionContextIntegrationInterceptor - implements InitializingBean, HandlerInterceptor { - - //~ Static fields/initializers ===================================================================================== - - protected static final Log logger = LogFactory.getLog(PortletSessionContextIntegrationInterceptor.class); - - public static final String SPRING_SECURITY_CONTEXT_KEY = HttpSessionContextIntegrationFilter.SPRING_SECURITY_CONTEXT_KEY; - - private static final String SESSION_EXISTED = PortletSessionContextIntegrationInterceptor.class.getName() + ".SESSION_EXISTED"; - private static final String CONTEXT_HASHCODE = PortletSessionContextIntegrationInterceptor.class.getName() + ".CONTEXT_HASHCODE"; - - //~ Instance fields ================================================================================================ - - private Class context = SecurityContextImpl.class; - - private Object contextObject; - - /** - * Indicates if this interceptor can create a PortletSession if - * needed (sessions are always created sparingly, but setting this value to - * false will prohibit sessions from ever being created). - * Defaults to true. Do not set to false if - * you are have set {@link #forceEagerSessionCreation} to true, - * as the properties would be in conflict. - */ - private boolean allowSessionCreation = true; - - /** - * Indicates if this interceptor is required to create a PortletSession - * for every request before proceeding through the request process, even if the - * PortletSession would not ordinarily have been created. By - * default this is false, which is entirely appropriate for - * most circumstances as you do not want a PortletSession - * created unless the interceptor actually needs one. It is envisaged the main - * situation in which this property would be set to true is - * if using other interceptors that depend on a PortletSession - * already existing. This is only required in specialized cases, so leave it set to - * false unless you have an actual requirement and aware of the - * session creation overhead. - */ - private boolean forceEagerSessionCreation = false; - - /** - * Indicates whether the SecurityContext will be cloned from - * the PortletSession. The default is to simply reference - * (the default is false). The default may cause issues if - * concurrent threads need to have a different security identity from other - * threads being concurrently processed that share the same - * PortletSession. In most normal environments this does not - * represent an issue, as changes to the security identity in one thread is - * allowed to affect the security identity in other threads associated with - * the same PortletSession. For unusual cases where this is not - * permitted, change this value to true and ensure the - * {@link #context} is set to a SecurityContext that - * implements {@link Cloneable} and overrides the clone() - * method. - */ - private boolean cloneFromPortletSession = false; - - /** - * Indicates wether the APPLICATION_SCOPE mode of the - * PortletSession should be used for storing the - * SecurityContext. The default is
true
. - * This allows it to be shared between the portlets in the webapp and - * potentially with servlets in the webapp as well. If this is set to - * false, then the PORTLET_SCOPE will be used - * instead. - */ - private boolean useApplicationScopePortletSession = true; - - - //~ Constructors =================================================================================================== - - public PortletSessionContextIntegrationInterceptor() throws PortletException { - this.contextObject = generateNewContext(); - } - - //~ Methods ======================================================================================================== - - public void afterPropertiesSet() throws Exception { - - // check that the value of context is legal - if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) { - throw new IllegalArgumentException("context must be defined and implement SecurityContext " - + "(typically use org.springframework.security.context.SecurityContextImpl; existing class is " - + this.context + ")"); - } - - // check that session creation options make sense - if ((forceEagerSessionCreation == true) && (allowSessionCreation == false)) { - throw new IllegalArgumentException( - "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true"); - } - } - - public boolean preHandleAction(ActionRequest request, ActionResponse response, - Object handler) throws Exception { - // call to common preHandle method - return preHandle(request, response, handler); - } - - public boolean preHandleRender(RenderRequest request, RenderResponse response, - Object handler) throws Exception { - // call to common preHandle method - return preHandle(request, response, handler); - } - - public void postHandleRender(RenderRequest request, RenderResponse response, - Object handler, ModelAndView modelAndView) throws Exception { - // no-op - } - - public void afterActionCompletion(ActionRequest request, ActionResponse response, - Object handler, Exception ex) throws Exception { - // call to common afterCompletion method - afterCompletion(request, response, handler, ex); - } - - public void afterRenderCompletion(RenderRequest request, RenderResponse response, - Object handler, Exception ex) throws Exception { - // call to common afterCompletion method - afterCompletion(request, response, handler, ex); - } - - - private boolean preHandle(PortletRequest request, PortletResponse response, - Object handler) throws Exception { - - PortletSession portletSession = null; - boolean portletSessionExistedAtStartOfRequest = false; - - // see if the portlet session already exists (or should be eagerly created) - try { - portletSession = request.getPortletSession(forceEagerSessionCreation); - } catch (IllegalStateException ignored) {} - - // if there is a session, then see if there is a context to bring in - if (portletSession != null) { - - // remember that the session already existed - portletSessionExistedAtStartOfRequest = true; - - // attempt to retrieve the context from the session - Object contextFromSessionObject = portletSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY, portletSessionScope()); - - // if we got a context then place it into the holder - if (contextFromSessionObject != null) { - - // if we are supposed to clone it, then do so - if (cloneFromPortletSession) { - Assert.isInstanceOf(Cloneable.class, contextFromSessionObject, - "Context must implement Clonable and provide a Object.clone() method"); - try { - Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[] {}); - if (!m.isAccessible()) { - m.setAccessible(true); - } - contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[] {}); - } - catch (Exception ex) { - ReflectionUtils.handleReflectionException(ex); - } - } - - // if what we got is a valid context then place it into the holder, otherwise create a new one - if (contextFromSessionObject instanceof SecurityContext) { - if (logger.isDebugEnabled()) - logger.debug("Obtained from SPRING_SECURITY_CONTEXT a valid SecurityContext and " - + "set to SecurityContextHolder: '" + contextFromSessionObject + "'"); - SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject); - } else { - if (logger.isWarnEnabled()) - logger.warn("SPRING_SECURITY_CONTEXT did not contain a SecurityContext but contained: '" - + contextFromSessionObject - + "'; are you improperly modifying the PortletSession directly " - + "(you should always use SecurityContextHolder) or using the PortletSession attribute " - + "reserved for this class? - new SecurityContext instance associated with " - + "SecurityContextHolder"); - SecurityContextHolder.setContext(generateNewContext()); - } - - } else { - - // there was no context in the session, so create a new context and put it in the holder - if (logger.isDebugEnabled()) - logger.debug("PortletSession returned null object for SPRING_SECURITY_CONTEXT - new " - + "SecurityContext instance associated with SecurityContextHolder"); - SecurityContextHolder.setContext(generateNewContext()); - } - - } else { - - // there was no session, so create a new context and place it in the holder - if (logger.isDebugEnabled()) - logger.debug("No PortletSession currently exists - new SecurityContext instance " - + "associated with SecurityContextHolder"); - SecurityContextHolder.setContext(generateNewContext()); - - } - - // place attributes onto the request to remember if the session existed and the hashcode of the context - request.setAttribute(SESSION_EXISTED, new Boolean(portletSessionExistedAtStartOfRequest)); - request.setAttribute(CONTEXT_HASHCODE, new Integer(SecurityContextHolder.getContext().hashCode())); - - return true; - } - - private void afterCompletion(PortletRequest request, PortletResponse response, - Object handler, Exception ex) throws Exception { - - PortletSession portletSession = null; - - // retrieve the attributes that remember if the session existed and the hashcode of the context - boolean portletSessionExistedAtStartOfRequest = ((Boolean)request.getAttribute(SESSION_EXISTED)).booleanValue(); - int oldContextHashCode = ((Integer)request.getAttribute(CONTEXT_HASHCODE)).intValue(); - - // try to retrieve an existing portlet session - try { - portletSession = request.getPortletSession(false); - } catch (IllegalStateException ignored) {} - - // if there is now no session but there was one at the beginning then it must have been invalidated - if ((portletSession == null) && portletSessionExistedAtStartOfRequest) { - if (logger.isDebugEnabled()) - logger.debug("PortletSession is now null, but was not null at start of request; " - + "session was invalidated, so do not create a new session"); - } - - // create a new portlet session if we need to - if ((portletSession == null) && !portletSessionExistedAtStartOfRequest) { - - // if we're not allowed to create a new session, then report that - if (!allowSessionCreation) { - if (logger.isDebugEnabled()) - logger.debug("The PortletSession is currently null, and the " - + "PortletSessionContextIntegrationInterceptor is prohibited from creating a PortletSession " - + "(because the allowSessionCreation property is false) - SecurityContext thus not " - + "stored for next request"); - } - // if the context was changed during the request, then go ahead and create a session - else if (!contextObject.equals(SecurityContextHolder.getContext())) { - if (logger.isDebugEnabled()) - logger.debug("PortletSession being created as SecurityContextHolder contents are non-default"); - try { - portletSession = request.getPortletSession(true); - } catch (IllegalStateException ignored) {} - } - // if nothing in the context changed, then don't bother to create a session - else { - if (logger.isDebugEnabled()) - logger.debug("PortletSession is null, but SecurityContextHolder has not changed from default: ' " - + SecurityContextHolder.getContext() - + "'; not creating PortletSession or storing SecurityContextHolder contents"); - } - } - - // if the session exists and the context has changes, then store the context back into the session - if ((portletSession != null) - && (SecurityContextHolder.getContext().hashCode() != oldContextHashCode)) { - portletSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext(), portletSessionScope()); - if (logger.isDebugEnabled()) - logger.debug("SecurityContext stored to PortletSession: '" - + SecurityContextHolder.getContext() + "'"); - } - - // remove the contents of the holder - SecurityContextHolder.clearContext(); - if (logger.isDebugEnabled()) - logger.debug("SecurityContextHolder set to new context, as request processing completed"); - - } - - /** - * Creates a new SecurityContext object. The specific class is - * determined by the setting of the {@link #context} property. - * @return the new SecurityContext - * @throws PortletException if the creation throws an InstantiationException or - * an IllegalAccessException, then this method will wrap them in a - * PortletException - */ - public SecurityContext generateNewContext() throws PortletException { - try { - return (SecurityContext) this.context.newInstance(); - } catch (InstantiationException ie) { - throw new PortletException(ie); - } catch (IllegalAccessException iae) { - throw new PortletException(iae); - } - } - - - private int portletSessionScope() { - // return the appropriate scope setting based on our property value - return (this.useApplicationScopePortletSession ? - PortletSession.APPLICATION_SCOPE : PortletSession.PORTLET_SCOPE); - } - - - public Class getContext() { - return context; - } - - public void setContext(Class secureContext) { - this.context = secureContext; - } - - public boolean isAllowSessionCreation() { - return allowSessionCreation; - } - - public void setAllowSessionCreation(boolean allowSessionCreation) { - this.allowSessionCreation = allowSessionCreation; - } - - public boolean isForceEagerSessionCreation() { - return forceEagerSessionCreation; - } - - public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) { - this.forceEagerSessionCreation = forceEagerSessionCreation; - } - - public boolean isCloneFromPortletSession() { - return cloneFromPortletSession; - } - - public void setCloneFromPortletSession(boolean cloneFromPortletSession) { - this.cloneFromPortletSession = cloneFromPortletSession; - } - - public boolean isUseApplicationScopePortletSession() { - return useApplicationScopePortletSession; - } - - public void setUseApplicationScopePortletSession( - boolean useApplicationScopePortletSession) { - this.useApplicationScopePortletSession = useApplicationScopePortletSession; - } - -} +/* + * Copyright 2005-2007 the original author or authors. + * + * 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 org.springframework.security.context; + +import java.lang.reflect.Method; + +import javax.portlet.*; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.portlet.HandlerInterceptor; +import org.springframework.web.portlet.ModelAndView; +import org.springframework.security.web.context.SecurityContextPersistenceFilter; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; + +/** + *

This interceptor populates the {@link SecurityContextHolder} with information obtained from the + * PortletSession. It is applied to both ActionRequests and + * RenderRequests

+ * + *

The PortletSession will be queried to retrieve the SecurityContext that should + * be stored against the SecurityContextHolder for the duration of the portlet request. At the + * end of the request, any updates made to the SecurityContextHolder will be persisted back to the + * PortletSession by this interceptor.

+ * + *

If a valid SecurityContext cannot be obtained from the PortletSession for + * whatever reason, a fresh SecurityContext will be created and used instead. The created object + * will be of the instance defined by the {@link #setContext(Class)} method (which defaults to + * {@link org.springframework.security.context.SecurityContextImpl}.

+ * + *

A PortletSession may be created by this interceptor if one does not already exist. If at the + * end of the portlet request the PortletSession does not exist, one will only be created if + * the current contents of the SecurityContextHolder are not the {@link java.lang.Object#equals} + * to a new instance of {@link #context}. This avoids needless PortletSession creation, + * and automates the storage of changes made to the SecurityContextHolder. There is one exception to + * this rule, that is if the {@link #forceEagerSessionCreation} property is true, in which case + * sessions will always be created irrespective of normal session-minimization logic (the default is + * false, as this is resource intensive and not recommended).

+ * + *

If for whatever reason no PortletSession should ever be created, the + * {@link #allowSessionCreation} property should be set to false. Only do this if you really need + * to conserve server memory and ensure all classes using the SecurityContextHolder are designed to + * have no persistence of the SecurityContext between web requests. Please note that if + * {@link #forceEagerSessionCreation} is true, the allowSessionCreation must also be + * true (setting it to false will cause a startup-time error).

+ + *

This interceptor must be executed before

any authentication processing mechanisms. These + * mechanisms (specifically {@link org.springframework.security.ui.portlet.PortletProcessingInterceptor}) expect the + * SecurityContextHolder to contain a valid SecurityContext by the time they execute.

+ * + *

An important nuance to this interceptor is that (by default) the SecurityContext is stored + * into the APPLICATION_SCOPE of the PortletSession. This doesn't just mean you will be + * sharing it with all the other portlets in your webapp (which is generally a good idea). It also means that (if + * you have done all the other appropriate magic), you will share this SecurityContext with servlets in + * your webapp. This is very useful if you have servlets serving images or processing AJAX calls from your portlets + * since they can now use the {@link SecurityContextPersistenceFilter} to access the same SecurityContext + * object from the session. This allows these calls to be secured as well as the portlet calls.

+ * + * Much of the logic of this interceptor comes from the {@link SecurityContextPersistenceFilter} class which + * fills the same purpose on the servlet side. Ben Alex and Patrick Burlson are listed as authors here because they + * are the authors of that class and there are blocks of code that essentially identical between the two. (Making this + * a good candidate for refactoring someday.) + * + *

Unlike HttpSessionContextIntegrationFilter, this interceptor does not check to see if it is + * getting applied multiple times. This shouldn't be a problem since the application of interceptors is under the + * control of the Spring Portlet MVC framework and tends to be more explicit and more predictable than the application + * of filters. However, you should still be careful to only apply this inteceptor to your request once.

+ * + * @author John A. Lewis + * @author Ben Alex + * @author Patrick Burleson + * @since 2.0 + * @version $Id$ + */ +public class PortletSessionContextIntegrationInterceptor + implements InitializingBean, HandlerInterceptor { + + //~ Static fields/initializers ===================================================================================== + + protected static final Log logger = LogFactory.getLog(PortletSessionContextIntegrationInterceptor.class); + + public static final String SPRING_SECURITY_CONTEXT_KEY = HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY; + + private static final String SESSION_EXISTED = PortletSessionContextIntegrationInterceptor.class.getName() + ".SESSION_EXISTED"; + private static final String CONTEXT_HASHCODE = PortletSessionContextIntegrationInterceptor.class.getName() + ".CONTEXT_HASHCODE"; + + //~ Instance fields ================================================================================================ + + private Class context = SecurityContextImpl.class; + + private Object contextObject; + + /** + * Indicates if this interceptor can create a PortletSession if + * needed (sessions are always created sparingly, but setting this value to + * false will prohibit sessions from ever being created). + * Defaults to true. Do not set to false if + * you are have set {@link #forceEagerSessionCreation} to true, + * as the properties would be in conflict. + */ + private boolean allowSessionCreation = true; + + /** + * Indicates if this interceptor is required to create a PortletSession + * for every request before proceeding through the request process, even if the + * PortletSession would not ordinarily have been created. By + * default this is false, which is entirely appropriate for + * most circumstances as you do not want a PortletSession + * created unless the interceptor actually needs one. It is envisaged the main + * situation in which this property would be set to true is + * if using other interceptors that depend on a PortletSession + * already existing. This is only required in specialized cases, so leave it set to + * false unless you have an actual requirement and aware of the + * session creation overhead. + */ + private boolean forceEagerSessionCreation = false; + + /** + * Indicates whether the SecurityContext will be cloned from + * the PortletSession. The default is to simply reference + * (the default is false). The default may cause issues if + * concurrent threads need to have a different security identity from other + * threads being concurrently processed that share the same + * PortletSession. In most normal environments this does not + * represent an issue, as changes to the security identity in one thread is + * allowed to affect the security identity in other threads associated with + * the same PortletSession. For unusual cases where this is not + * permitted, change this value to true and ensure the + * {@link #context} is set to a SecurityContext that + * implements {@link Cloneable} and overrides the clone() + * method. + */ + private boolean cloneFromPortletSession = false; + + /** + * Indicates wether the APPLICATION_SCOPE mode of the + * PortletSession should be used for storing the + * SecurityContext. The default is
true
. + * This allows it to be shared between the portlets in the webapp and + * potentially with servlets in the webapp as well. If this is set to + * false, then the PORTLET_SCOPE will be used + * instead. + */ + private boolean useApplicationScopePortletSession = true; + + + //~ Constructors =================================================================================================== + + public PortletSessionContextIntegrationInterceptor() throws PortletException { + this.contextObject = generateNewContext(); + } + + //~ Methods ======================================================================================================== + + public void afterPropertiesSet() throws Exception { + + // check that the value of context is legal + if ((this.context == null) || (!SecurityContext.class.isAssignableFrom(this.context))) { + throw new IllegalArgumentException("context must be defined and implement SecurityContext " + + "(typically use org.springframework.security.context.SecurityContextImpl; existing class is " + + this.context + ")"); + } + + // check that session creation options make sense + if ((forceEagerSessionCreation == true) && (allowSessionCreation == false)) { + throw new IllegalArgumentException( + "If using forceEagerSessionCreation, you must set allowSessionCreation to also be true"); + } + } + + public boolean preHandleAction(ActionRequest request, ActionResponse response, + Object handler) throws Exception { + // call to common preHandle method + return preHandle(request, response, handler); + } + + public boolean preHandleRender(RenderRequest request, RenderResponse response, + Object handler) throws Exception { + // call to common preHandle method + return preHandle(request, response, handler); + } + + public void postHandleRender(RenderRequest request, RenderResponse response, + Object handler, ModelAndView modelAndView) throws Exception { + // no-op + } + + public void afterActionCompletion(ActionRequest request, ActionResponse response, + Object handler, Exception ex) throws Exception { + // call to common afterCompletion method + afterCompletion(request, response, handler, ex); + } + + public void afterRenderCompletion(RenderRequest request, RenderResponse response, + Object handler, Exception ex) throws Exception { + // call to common afterCompletion method + afterCompletion(request, response, handler, ex); + } + + /** + * {@inheritDoc} + */ + public boolean preHandleResource(ResourceRequest request, ResourceResponse response, Object handler) throws Exception { + return preHandle(request, response, handler); + } + + /** + * {@inheritDoc} + */ + public void postHandleResource(ResourceRequest request, ResourceResponse response, Object handler, ModelAndView modelAndView) throws Exception { + // no-op + } + + /** + * {@inheritDoc} + */ + public void afterResourceCompletion(ResourceRequest request, ResourceResponse response, Object handler, Exception ex) throws Exception { + // call to common afterCompletion method + afterCompletion(request, response, handler, ex); + } + + /** + * {@inheritDoc} + */ + public boolean preHandleEvent(EventRequest request, EventResponse response, Object handler) throws Exception { + return preHandle(request, response, handler); + } + + /** + * {@inheritDoc} + */ + public void afterEventCompletion(EventRequest request, EventResponse response, Object handler, Exception ex) throws Exception { + // call to common afterCompletion method + afterCompletion(request, response, handler, ex); + } + + private boolean preHandle(PortletRequest request, PortletResponse response, + Object handler) throws Exception { + + PortletSession portletSession = null; + boolean portletSessionExistedAtStartOfRequest = false; + + // see if the portlet session already exists (or should be eagerly created) + try { + portletSession = request.getPortletSession(forceEagerSessionCreation); + } catch (IllegalStateException ignored) {} + + // if there is a session, then see if there is a context to bring in + if (portletSession != null) { + + // remember that the session already existed + portletSessionExistedAtStartOfRequest = true; + + // attempt to retrieve the context from the session + Object contextFromSessionObject = portletSession.getAttribute(SPRING_SECURITY_CONTEXT_KEY, portletSessionScope()); + + // if we got a context then place it into the holder + if (contextFromSessionObject != null) { + + // if we are supposed to clone it, then do so + if (cloneFromPortletSession) { + Assert.isInstanceOf(Cloneable.class, contextFromSessionObject, + "Context must implement Clonable and provide a Object.clone() method"); + try { + Method m = contextFromSessionObject.getClass().getMethod("clone", new Class[] {}); + if (!m.isAccessible()) { + m.setAccessible(true); + } + contextFromSessionObject = m.invoke(contextFromSessionObject, new Object[] {}); + } + catch (Exception ex) { + ReflectionUtils.handleReflectionException(ex); + } + } + + // if what we got is a valid context then place it into the holder, otherwise create a new one + if (contextFromSessionObject instanceof SecurityContext) { + if (logger.isDebugEnabled()) + logger.debug("Obtained from SPRING_SECURITY_CONTEXT a valid SecurityContext and " + + "set to SecurityContextHolder: '" + contextFromSessionObject + "'"); + SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject); + } else { + if (logger.isWarnEnabled()) + logger.warn("SPRING_SECURITY_CONTEXT did not contain a SecurityContext but contained: '" + + contextFromSessionObject + + "'; are you improperly modifying the PortletSession directly " + + "(you should always use SecurityContextHolder) or using the PortletSession attribute " + + "reserved for this class? - new SecurityContext instance associated with " + + "SecurityContextHolder"); + SecurityContextHolder.setContext(generateNewContext()); + } + + } else { + + // there was no context in the session, so create a new context and put it in the holder + if (logger.isDebugEnabled()) + logger.debug("PortletSession returned null object for SPRING_SECURITY_CONTEXT - new " + + "SecurityContext instance associated with SecurityContextHolder"); + SecurityContextHolder.setContext(generateNewContext()); + } + + } else { + + // there was no session, so create a new context and place it in the holder + if (logger.isDebugEnabled()) + logger.debug("No PortletSession currently exists - new SecurityContext instance " + + "associated with SecurityContextHolder"); + SecurityContextHolder.setContext(generateNewContext()); + + } + + // place attributes onto the request to remember if the session existed and the hashcode of the context + request.setAttribute(SESSION_EXISTED, new Boolean(portletSessionExistedAtStartOfRequest)); + request.setAttribute(CONTEXT_HASHCODE, new Integer(SecurityContextHolder.getContext().hashCode())); + + return true; + } + + private void afterCompletion(PortletRequest request, PortletResponse response, + Object handler, Exception ex) throws Exception { + + PortletSession portletSession = null; + + // retrieve the attributes that remember if the session existed and the hashcode of the context + boolean portletSessionExistedAtStartOfRequest = ((Boolean)request.getAttribute(SESSION_EXISTED)).booleanValue(); + int oldContextHashCode = ((Integer)request.getAttribute(CONTEXT_HASHCODE)).intValue(); + + // try to retrieve an existing portlet session + try { + portletSession = request.getPortletSession(false); + } catch (IllegalStateException ignored) {} + + // if there is now no session but there was one at the beginning then it must have been invalidated + if ((portletSession == null) && portletSessionExistedAtStartOfRequest) { + if (logger.isDebugEnabled()) + logger.debug("PortletSession is now null, but was not null at start of request; " + + "session was invalidated, so do not create a new session"); + } + + // create a new portlet session if we need to + if ((portletSession == null) && !portletSessionExistedAtStartOfRequest) { + + // if we're not allowed to create a new session, then report that + if (!allowSessionCreation) { + if (logger.isDebugEnabled()) + logger.debug("The PortletSession is currently null, and the " + + "PortletSessionContextIntegrationInterceptor is prohibited from creating a PortletSession " + + "(because the allowSessionCreation property is false) - SecurityContext thus not " + + "stored for next request"); + } + // if the context was changed during the request, then go ahead and create a session + else if (!contextObject.equals(SecurityContextHolder.getContext())) { + if (logger.isDebugEnabled()) + logger.debug("PortletSession being created as SecurityContextHolder contents are non-default"); + try { + portletSession = request.getPortletSession(true); + } catch (IllegalStateException ignored) {} + } + // if nothing in the context changed, then don't bother to create a session + else { + if (logger.isDebugEnabled()) + logger.debug("PortletSession is null, but SecurityContextHolder has not changed from default: ' " + + SecurityContextHolder.getContext() + + "'; not creating PortletSession or storing SecurityContextHolder contents"); + } + } + + // if the session exists and the context has changes, then store the context back into the session + if ((portletSession != null) + && (SecurityContextHolder.getContext().hashCode() != oldContextHashCode)) { + portletSession.setAttribute(SPRING_SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext(), portletSessionScope()); + if (logger.isDebugEnabled()) + logger.debug("SecurityContext stored to PortletSession: '" + + SecurityContextHolder.getContext() + "'"); + } + + // remove the contents of the holder + SecurityContextHolder.clearContext(); + if (logger.isDebugEnabled()) + logger.debug("SecurityContextHolder set to new context, as request processing completed"); + + } + + /** + * Creates a new SecurityContext object. The specific class is + * determined by the setting of the {@link #context} property. + * @return the new SecurityContext + * @throws PortletException if the creation throws an InstantiationException or + * an IllegalAccessException, then this method will wrap them in a + * PortletException + */ + public SecurityContext generateNewContext() throws PortletException { + try { + return (SecurityContext) this.context.newInstance(); + } catch (InstantiationException ie) { + throw new PortletException(ie); + } catch (IllegalAccessException iae) { + throw new PortletException(iae); + } + } + + + private int portletSessionScope() { + // return the appropriate scope setting based on our property value + return (this.useApplicationScopePortletSession ? + PortletSession.APPLICATION_SCOPE : PortletSession.PORTLET_SCOPE); + } + + + public Class getContext() { + return context; + } + + public void setContext(Class secureContext) { + this.context = secureContext; + } + + public boolean isAllowSessionCreation() { + return allowSessionCreation; + } + + public void setAllowSessionCreation(boolean allowSessionCreation) { + this.allowSessionCreation = allowSessionCreation; + } + + public boolean isForceEagerSessionCreation() { + return forceEagerSessionCreation; + } + + public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) { + this.forceEagerSessionCreation = forceEagerSessionCreation; + } + + public boolean isCloneFromPortletSession() { + return cloneFromPortletSession; + } + + public void setCloneFromPortletSession(boolean cloneFromPortletSession) { + this.cloneFromPortletSession = cloneFromPortletSession; + } + + public boolean isUseApplicationScopePortletSession() { + return useApplicationScopePortletSession; + } + + public void setUseApplicationScopePortletSession( + boolean useApplicationScopePortletSession) { + this.useApplicationScopePortletSession = useApplicationScopePortletSession; + } + +} diff --git a/portlet/src/main/java/org/springframework/security/ui/portlet/PortletPreAuthenticatedAuthenticationDetailsSource.java b/portlet/src/main/java/org/springframework/security/ui/portlet/PortletPreAuthenticatedAuthenticationDetailsSource.java index 8989a70a3b..59a1ccac89 100644 --- a/portlet/src/main/java/org/springframework/security/ui/portlet/PortletPreAuthenticatedAuthenticationDetailsSource.java +++ b/portlet/src/main/java/org/springframework/security/ui/portlet/PortletPreAuthenticatedAuthenticationDetailsSource.java @@ -6,7 +6,7 @@ import java.util.Set; import javax.portlet.PortletRequest; -import org.springframework.security.ui.preauth.j2ee.AbstractPreAuthenticatedAuthenticationDetailsSource; +import org.springframework.security.web.authentication.preauth.j2ee.AbstractPreAuthenticatedAuthenticationDetailsSource; public class PortletPreAuthenticatedAuthenticationDetailsSource extends AbstractPreAuthenticatedAuthenticationDetailsSource { diff --git a/portlet/src/main/java/org/springframework/security/ui/portlet/PortletProcessingInterceptor.java b/portlet/src/main/java/org/springframework/security/ui/portlet/PortletProcessingInterceptor.java index 5916fc6c2b..3b82be693e 100644 --- a/portlet/src/main/java/org/springframework/security/ui/portlet/PortletProcessingInterceptor.java +++ b/portlet/src/main/java/org/springframework/security/ui/portlet/PortletProcessingInterceptor.java @@ -1,322 +1,342 @@ -/* - * Copyright 2005-2007 the original author or authors. - * - * 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 org.springframework.security.ui.portlet; - -import java.io.IOException; -import java.security.Principal; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.portlet.ActionRequest; -import javax.portlet.ActionResponse; -import javax.portlet.PortletRequest; -import javax.portlet.PortletResponse; -import javax.portlet.PortletSession; -import javax.portlet.RenderRequest; -import javax.portlet.RenderResponse; - -import org.springframework.security.Authentication; -import org.springframework.security.AuthenticationDetailsSource; -import org.springframework.security.AuthenticationDetailsSourceImpl; -import org.springframework.security.AuthenticationException; -import org.springframework.security.AuthenticationManager; -import org.springframework.security.context.SecurityContext; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationToken; -import org.springframework.security.ui.AbstractProcessingFilter; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.util.Assert; -import org.springframework.web.portlet.HandlerInterceptor; -import org.springframework.web.portlet.ModelAndView; - -/** - *

This interceptor is responsible for processing portlet authentication requests. This - * is the portlet equivalent of the AuthenticationProcessingFilter used for - * traditional servlet-based web applications. It is applied to both ActionRequests - * and RenderRequests alike. If authentication is successful, the resulting - * {@link Authentication} object will be placed into the SecurityContext, which - * is guaranteed to have already been created by an earlier interceptor. If authentication - * fails, the AuthenticationException will be placed into the - * APPLICATION_SCOPE of the PortletSession with the attribute defined - * by {@link AbstractProcessingFilter#SPRING_SECURITY_LAST_EXCEPTION_KEY}.

- * - *

Some portals do not properly provide the identity of the current user via the - * getRemoteUser() or getUserPrincipal() methods of the - * PortletRequest. In these cases they sometimes make it available in the - * USER_INFO map provided as one of the attributes of the request. If this is - * the case in your portal, you can specify a list of USER_INFO attributes - * to check for the username via the userNameAttributes property of this bean. - * You can also completely override the {@link #getPrincipalFromRequest(PortletRequest)} - * and {@link #getCredentialsFromRequest(PortletRequest)} methods to suit the particular - * behavior of your portal.

- * - *

This interceptor will put the PortletRequest object into the - * details property of the Authentication object that is sent - * as a request to the AuthenticationManager. This is done so that the request - * is available to classes like {@link ContainerPortletAuthoritiesPopulator} that need - * access to information from the portlet container. The {@link PortletAuthenticationProvider} - * will replace this with the USER_INFO map in the resulting Authentication - * object.

- * - * @see org.springframework.security.ui.AbstractProcessingFilter - * @see org.springframework.security.ui.webapp.AuthenticationProcessingFilter - * @author John A. Lewis - * @since 2.0 - * @version $Id$ - */ -public class PortletProcessingInterceptor implements HandlerInterceptor, InitializingBean { - - //~ Static fields/initializers ===================================================================================== - - private static final Log logger = LogFactory.getLog(PortletProcessingInterceptor.class); - - //~ Instance fields ================================================================================================ - - private AuthenticationManager authenticationManager; - - private List userNameAttributes; - - private AuthenticationDetailsSource authenticationDetailsSource; - - private boolean useAuthTypeAsCredentials = false; - - public PortletProcessingInterceptor() { - authenticationDetailsSource = new AuthenticationDetailsSourceImpl(); - ((AuthenticationDetailsSourceImpl)authenticationDetailsSource).setClazz(PortletAuthenticationDetails.class); - } - - //~ Methods ======================================================================================================== - - public void afterPropertiesSet() throws Exception { - Assert.notNull(authenticationManager, "An AuthenticationManager must be set"); - } - - public boolean preHandleAction(ActionRequest request, ActionResponse response, - Object handler) throws Exception { - return preHandle(request, response, handler); - } - - public boolean preHandleRender(RenderRequest request, - RenderResponse response, Object handler) throws Exception { - return preHandle(request, response, handler); - } - - public void postHandleRender(RenderRequest request, RenderResponse response, - Object handler, ModelAndView modelAndView) throws Exception { - } - - public void afterActionCompletion(ActionRequest request, ActionResponse response, - Object handler, Exception ex) throws Exception { - } - - public void afterRenderCompletion(RenderRequest request, RenderResponse response, - Object handler, Exception ex) throws Exception { - } - - /** - * Common preHandle method for both the action and render phases of the interceptor. - */ - private boolean preHandle(PortletRequest request, PortletResponse response, - Object handler) throws Exception { - - // get the SecurityContext - SecurityContext ctx = SecurityContextHolder.getContext(); - - if (logger.isDebugEnabled()) - logger.debug("Checking secure context token: " + ctx.getAuthentication()); - - // if there is no existing Authentication object, then lets create one - if (ctx.getAuthentication() == null) { - - try { - - // build the authentication request from the PortletRequest - PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken( - getPrincipalFromRequest(request), - getCredentialsFromRequest(request)); - - // put the PortletRequest into the authentication request as the "details" - authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); - - if (logger.isDebugEnabled()) - logger.debug("Beginning authentication request for user '" + authRequest.getName() + "'"); - - onPreAuthentication(request, response); - - // ask the authentication manager to authenticate the request - // it will throw an AuthenticationException if it fails, otherwise it succeeded - Authentication authResult = authenticationManager.authenticate(authRequest); - - // process a successful authentication - if (logger.isDebugEnabled()) { - logger.debug("Authentication success: " + authResult); - } - - ctx.setAuthentication(authResult); - onSuccessfulAuthentication(request, response, authResult); - - } catch (AuthenticationException failed) { - // process an unsuccessful authentication - if (logger.isDebugEnabled()) { - logger.debug("Authentication failed - updating ContextHolder to contain null Authentication", failed); - } - ctx.setAuthentication(null); - request.getPortletSession().setAttribute( - AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, - failed, PortletSession.APPLICATION_SCOPE); - onUnsuccessfulAuthentication(request, response, failed); - } - } - - return true; - } - - /** - * This method attempts to extract a principal from the portlet request. - * According to the JSR 168 spec, the PortletRequest should return the name - * of the user in the getRemoteUser() method. It should also provide a - * java.security.Principal object from the getUserPrincipal() - * method. We will first try these to come up with a valid username. - *

Unfortunately, some portals do not properly return these values for authenticated - * users. So, if neither of those succeeds and if the userNameAttributes - * property has been populated, then we will search through the USER_INFO - * map from the request to see if we can find a valid username. - *

This method can be overridden by subclasses to provide special handling - * for portals with weak support for the JSR 168 spec.

- * @param request the portlet request object - * @return the determined principal object, or null if none found - */ - protected Object getPrincipalFromRequest(PortletRequest request) { - - // first try getRemoteUser() - String remoteUser = request.getRemoteUser(); - if (remoteUser != null) { - return remoteUser; - } - - // next try getUserPrincipal() - Principal userPrincipal = request.getUserPrincipal(); - if (userPrincipal != null) { - String userPrincipalName = userPrincipal.getName(); - if (userPrincipalName != null) { - return userPrincipalName; - } - } - - // last try entries in USER_INFO if any attributes were defined - if (this.userNameAttributes != null) { - Map userInfo = null; - try { - userInfo = (Map)request.getAttribute(PortletRequest.USER_INFO); - } catch (Exception e) { - logger.warn("unable to retrieve USER_INFO map from portlet request", e); - } - if (userInfo != null) { - Iterator i = this.userNameAttributes.iterator(); - while(i.hasNext()) { - Object principal = (String)userInfo.get(i.next()); - if (principal != null) { - return principal; - } - } - } - } - - // none found so return null - return null; - } - - /** - * This method attempts to extract a credentials from the portlet request. - * We are trusting the portal framework to authenticate the user, so all - * we are really doing is trying to put something intelligent in here to - * indicate the user is authenticated. According to the JSR 168 spec, - * PortletRequest.getAuthType() should return a non-null value if the - * user is authenticated and should be null if not authenticated. So we - * will use this as the credentials and the token will be trusted as - * authenticated if the credentials are not null. - *

This method can be overridden by subclasses to provide special handling - * for portals with weak support for the JSR 168 spec. If that is done, - * be sure the value is non-null for authenticated users and null for - * non-authenticated users.

- * @param request the portlet request object - * @return the determined credentials object, or null if none found - */ - protected Object getCredentialsFromRequest(PortletRequest request) { - if (useAuthTypeAsCredentials) { - return request.getAuthType(); - } - - return "dummy"; - } - - /** - * Callback for custom processing prior to the authentication attempt. - * @param request the portlet request to be authenticated - * @param response the portlet response to be authenticated - * @throws AuthenticationException to indicate that authentication attempt is not valid and should be terminated - * @throws IOException - */ - protected void onPreAuthentication(PortletRequest request, PortletResponse response) - throws AuthenticationException, IOException {} - - /** - * Callback for custom processing after a successful authentication attempt. - * @param request the portlet request that was authenticated - * @param response the portlet response that was authenticated - * @param authResult the resulting Authentication object - * @throws IOException - */ - protected void onSuccessfulAuthentication(PortletRequest request, PortletResponse response, Authentication authResult) - throws IOException {} - - /** - * Callback for custom processing after an unsuccessful authentication attempt. - * @param request the portlet request that failed authentication - * @param response the portlet response that failed authentication - * @param failed the AuthenticationException that occurred - * @throws IOException - */ - protected void onUnsuccessfulAuthentication(PortletRequest request, PortletResponse response, AuthenticationException failed) - throws IOException {} - - - public void setAuthenticationManager(AuthenticationManager authenticationManager) { - this.authenticationManager = authenticationManager; - } - - public void setUserNameAttributes(List userNameAttributes) { - this.userNameAttributes = userNameAttributes; - } - - public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { - this.authenticationDetailsSource = authenticationDetailsSource; - } - - /** - * It true, the "authType" proerty of the PortletRequest will be used as the credentials. - * Defaults to false. - * - * @param useAuthTypeAsCredentials - */ - public void setUseAuthTypeAsCredentials(boolean useAuthTypeAsCredentials) { - this.useAuthTypeAsCredentials = useAuthTypeAsCredentials; - } -} +/* + * Copyright 2005-2007 the original author or authors. + * + * 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 org.springframework.security.ui.portlet; + +import java.io.IOException; +import java.security.Principal; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.portlet.*; + +import org.springframework.security.Authentication; +import org.springframework.security.AuthenticationDetailsSource; +import org.springframework.security.AuthenticationDetailsSourceImpl; +import org.springframework.security.AuthenticationException; +import org.springframework.security.AuthenticationManager; +import org.springframework.security.web.authentication.AbstractProcessingFilter; +import org.springframework.security.context.SecurityContext; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.preauth.PreAuthenticatedAuthenticationToken; +import org.springframework.security.web.authentication.AbstractProcessingFilter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; +import org.springframework.web.portlet.HandlerInterceptor; +import org.springframework.web.portlet.ModelAndView; + +/** + *

This interceptor is responsible for processing portlet authentication requests. This + * is the portlet equivalent of the AuthenticationProcessingFilter used for + * traditional servlet-based web applications. It is applied to both ActionRequests + * and RenderRequests alike. If authentication is successful, the resulting + * {@link Authentication} object will be placed into the SecurityContext, which + * is guaranteed to have already been created by an earlier interceptor. If authentication + * fails, the AuthenticationException will be placed into the + * APPLICATION_SCOPE of the PortletSession with the attribute defined + * by {@link AbstractProcessingFilter#SPRING_SECURITY_LAST_EXCEPTION_KEY}.

+ * + *

Some portals do not properly provide the identity of the current user via the + * getRemoteUser() or getUserPrincipal() methods of the + * PortletRequest. In these cases they sometimes make it available in the + * USER_INFO map provided as one of the attributes of the request. If this is + * the case in your portal, you can specify a list of USER_INFO attributes + * to check for the username via the userNameAttributes property of this bean. + * You can also completely override the {@link #getPrincipalFromRequest(PortletRequest)} + * and {@link #getCredentialsFromRequest(PortletRequest)} methods to suit the particular + * behavior of your portal.

+ * + *

This interceptor will put the PortletRequest object into the + * details property of the Authentication object that is sent + * as a request to the AuthenticationManager. + * + * @see org.springframework.security.web.authentication.AbstractProcessingFilter + * @see org.springframework.security.web.authentication.AuthenticationProcessingFilter + * @author John A. Lewis + * @since 2.0 + * @version $Id$ + */ +public class PortletProcessingInterceptor implements HandlerInterceptor, InitializingBean { + + //~ Static fields/initializers ===================================================================================== + + private static final Log logger = LogFactory.getLog(PortletProcessingInterceptor.class); + + //~ Instance fields ================================================================================================ + + private AuthenticationManager authenticationManager; + + private List userNameAttributes; + + private AuthenticationDetailsSource authenticationDetailsSource; + + private boolean useAuthTypeAsCredentials = false; + + public PortletProcessingInterceptor() { + authenticationDetailsSource = new AuthenticationDetailsSourceImpl(); + ((AuthenticationDetailsSourceImpl)authenticationDetailsSource).setClazz(PortletAuthenticationDetails.class); + } + + //~ Methods ======================================================================================================== + + public void afterPropertiesSet() throws Exception { + Assert.notNull(authenticationManager, "An AuthenticationManager must be set"); + } + + public boolean preHandleAction(ActionRequest request, ActionResponse response, + Object handler) throws Exception { + return preHandle(request, response, handler); + } + + public boolean preHandleRender(RenderRequest request, + RenderResponse response, Object handler) throws Exception { + return preHandle(request, response, handler); + } + + public void postHandleRender(RenderRequest request, RenderResponse response, + Object handler, ModelAndView modelAndView) throws Exception { + } + + public void afterActionCompletion(ActionRequest request, ActionResponse response, + Object handler, Exception ex) throws Exception { + } + + public void afterRenderCompletion(RenderRequest request, RenderResponse response, + Object handler, Exception ex) throws Exception { + } + + /** + * {@inheritDoc} + */ + public boolean preHandleResource(ResourceRequest request, ResourceResponse response, Object handler) throws Exception { + return preHandle(request, response, handler); + } + + /** + * {@inheritDoc} + */ + public void postHandleResource(ResourceRequest request, ResourceResponse response, Object handler, ModelAndView modelAndView) throws Exception { + } + + public void afterResourceCompletion(ResourceRequest request, ResourceResponse response, Object handler, Exception ex) throws Exception { + } + + /** + * {@inheritDoc} + */ + public boolean preHandleEvent(EventRequest request, EventResponse response, Object handler) throws Exception { + return preHandle(request, response, handler); + } + + /** + * {@inheritDoc} + */ + public void afterEventCompletion(EventRequest request, EventResponse response, Object handler, Exception ex) throws Exception { + } + + /** + * Common preHandle method for both the action and render phases of the interceptor. + */ + private boolean preHandle(PortletRequest request, PortletResponse response, + Object handler) throws Exception { + + // get the SecurityContext + SecurityContext ctx = SecurityContextHolder.getContext(); + + if (logger.isDebugEnabled()) + logger.debug("Checking secure context token: " + ctx.getAuthentication()); + + // if there is no existing Authentication object, then lets create one + if (ctx.getAuthentication() == null) { + + try { + + // build the authentication request from the PortletRequest + PreAuthenticatedAuthenticationToken authRequest = new PreAuthenticatedAuthenticationToken( + getPrincipalFromRequest(request), + getCredentialsFromRequest(request)); + + // put the PortletRequest into the authentication request as the "details" + authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); + + if (logger.isDebugEnabled()) + logger.debug("Beginning authentication request for user '" + authRequest.getName() + "'"); + + onPreAuthentication(request, response); + + // ask the authentication manager to authenticate the request + // it will throw an AuthenticationException if it fails, otherwise it succeeded + Authentication authResult = authenticationManager.authenticate(authRequest); + + // process a successful authentication + if (logger.isDebugEnabled()) { + logger.debug("Authentication success: " + authResult); + } + + ctx.setAuthentication(authResult); + onSuccessfulAuthentication(request, response, authResult); + + } catch (AuthenticationException failed) { + // process an unsuccessful authentication + if (logger.isDebugEnabled()) { + logger.debug("Authentication failed - updating ContextHolder to contain null Authentication", failed); + } + ctx.setAuthentication(null); + request.getPortletSession().setAttribute( + AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY, + failed, PortletSession.APPLICATION_SCOPE); + onUnsuccessfulAuthentication(request, response, failed); + } + } + + return true; + } + + /** + * This method attempts to extract a principal from the portlet request. + * According to the JSR 168 spec, the PortletRequest should return the name + * of the user in the getRemoteUser() method. It should also provide a + * java.security.Principal object from the getUserPrincipal() + * method. We will first try these to come up with a valid username. + *

Unfortunately, some portals do not properly return these values for authenticated + * users. So, if neither of those succeeds and if the userNameAttributes + * property has been populated, then we will search through the USER_INFO + * map from the request to see if we can find a valid username. + *

This method can be overridden by subclasses to provide special handling + * for portals with weak support for the JSR 168 spec.

+ * @param request the portlet request object + * @return the determined principal object, or null if none found + */ + protected Object getPrincipalFromRequest(PortletRequest request) { + + // first try getRemoteUser() + String remoteUser = request.getRemoteUser(); + if (remoteUser != null) { + return remoteUser; + } + + // next try getUserPrincipal() + Principal userPrincipal = request.getUserPrincipal(); + if (userPrincipal != null) { + String userPrincipalName = userPrincipal.getName(); + if (userPrincipalName != null) { + return userPrincipalName; + } + } + + // last try entries in USER_INFO if any attributes were defined + if (this.userNameAttributes != null) { + Map userInfo = null; + try { + userInfo = (Map)request.getAttribute(PortletRequest.USER_INFO); + } catch (Exception e) { + logger.warn("unable to retrieve USER_INFO map from portlet request", e); + } + if (userInfo != null) { + Iterator i = this.userNameAttributes.iterator(); + while(i.hasNext()) { + Object principal = (String)userInfo.get(i.next()); + if (principal != null) { + return principal; + } + } + } + } + + // none found so return null + return null; + } + + /** + * This method attempts to extract a credentials from the portlet request. + * We are trusting the portal framework to authenticate the user, so all + * we are really doing is trying to put something intelligent in here to + * indicate the user is authenticated. According to the JSR 168 spec, + * PortletRequest.getAuthType() should return a non-null value if the + * user is authenticated and should be null if not authenticated. So we + * will use this as the credentials and the token will be trusted as + * authenticated if the credentials are not null. + *

This method can be overridden by subclasses to provide special handling + * for portals with weak support for the JSR 168 spec. If that is done, + * be sure the value is non-null for authenticated users and null for + * non-authenticated users.

+ * @param request the portlet request object + * @return the determined credentials object, or null if none found + */ + protected Object getCredentialsFromRequest(PortletRequest request) { + if (useAuthTypeAsCredentials) { + return request.getAuthType(); + } + + return "dummy"; + } + + /** + * Callback for custom processing prior to the authentication attempt. + * @param request the portlet request to be authenticated + * @param response the portlet response to be authenticated + * @throws AuthenticationException to indicate that authentication attempt is not valid and should be terminated + * @throws IOException + */ + protected void onPreAuthentication(PortletRequest request, PortletResponse response) + throws AuthenticationException, IOException {} + + /** + * Callback for custom processing after a successful authentication attempt. + * @param request the portlet request that was authenticated + * @param response the portlet response that was authenticated + * @param authResult the resulting Authentication object + * @throws IOException + */ + protected void onSuccessfulAuthentication(PortletRequest request, PortletResponse response, Authentication authResult) + throws IOException {} + + /** + * Callback for custom processing after an unsuccessful authentication attempt. + * @param request the portlet request that failed authentication + * @param response the portlet response that failed authentication + * @param failed the AuthenticationException that occurred + * @throws IOException + */ + protected void onUnsuccessfulAuthentication(PortletRequest request, PortletResponse response, AuthenticationException failed) + throws IOException {} + + + public void setAuthenticationManager(AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public void setUserNameAttributes(List userNameAttributes) { + this.userNameAttributes = userNameAttributes; + } + + public void setAuthenticationDetailsSource(AuthenticationDetailsSource authenticationDetailsSource) { + this.authenticationDetailsSource = authenticationDetailsSource; + } + + /** + * It true, the "authType" proerty of the PortletRequest will be used as the credentials. + * Defaults to false. + * + * @param useAuthTypeAsCredentials + */ + public void setUseAuthTypeAsCredentials(boolean useAuthTypeAsCredentials) { + this.useAuthTypeAsCredentials = useAuthTypeAsCredentials; + } +} diff --git a/portlet/src/test/java/org/springframework/security/ui/portlet/PortletProcessingInterceptorTests.java b/portlet/src/test/java/org/springframework/security/ui/portlet/PortletProcessingInterceptorTests.java index 42ca2cdaa2..b73223f490 100644 --- a/portlet/src/test/java/org/springframework/security/ui/portlet/PortletProcessingInterceptorTests.java +++ b/portlet/src/test/java/org/springframework/security/ui/portlet/PortletProcessingInterceptorTests.java @@ -34,6 +34,7 @@ import org.springframework.mock.web.portlet.MockRenderResponse; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationManager; import org.springframework.security.BadCredentialsException; +import org.springframework.security.web.authentication.AbstractProcessingFilter; import org.springframework.security.context.SecurityContextHolder; import org.springframework.security.providers.TestingAuthenticationToken; import org.springframework.security.providers.UsernamePasswordAuthenticationToken; diff --git a/samples/pom.xml b/samples/pom.xml index f251960cf1..66aafd7952 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -16,7 +16,7 @@ preauth openid ldap - + portlet cas diff --git a/samples/portlet/src/main/java/org/springframework/web/portlet/sample/SecurityContextPortlet.java b/samples/portlet/src/main/java/org/springframework/web/portlet/sample/SecurityContextPortlet.java index 8f387cbec8..9f58890c46 100644 --- a/samples/portlet/src/main/java/org/springframework/web/portlet/sample/SecurityContextPortlet.java +++ b/samples/portlet/src/main/java/org/springframework/web/portlet/sample/SecurityContextPortlet.java @@ -11,11 +11,11 @@ import javax.portlet.RenderRequest; import javax.portlet.RenderResponse; import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.ui.AbstractProcessingFilter; +import org.springframework.security.web.authentication.AbstractProcessingFilter; /** - * A simple portlet which prints out the contents of the current {@link SecurityContext} + * A simple portlet which prints out the contents of the current {@link org.springframework.security.context.SecurityContext} * * @author Luke Taylor */ diff --git a/samples/portlet/src/main/resources/portlet/securityContextPortlet.xml b/samples/portlet/src/main/resources/portlet/securityContextPortlet.xml index 4394b99c2e..a8e0a36812 100644 --- a/samples/portlet/src/main/resources/portlet/securityContextPortlet.xml +++ b/samples/portlet/src/main/resources/portlet/securityContextPortlet.xml @@ -38,7 +38,7 @@ - + tomcat admin manager @@ -47,7 +47,7 @@ Guest User Power User - + diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests.java index 2dbbe1ef0b..873eb1208f 100755 --- a/web/src/test/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/j2ee/J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests.java @@ -1,150 +1,151 @@ -package org.springframework.security.web.authentication.preauth.j2ee; - -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; - -import junit.framework.TestCase; - -import org.springframework.security.authoritymapping.MappableAttributesRetriever; -import org.springframework.security.authoritymapping.Attributes2GrantedAuthoritiesMapper; -import org.springframework.security.authoritymapping.SimpleMappableAttributesRetriever; -import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper; -import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; -import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; -import org.springframework.security.GrantedAuthority; - -import org.springframework.mock.web.MockHttpServletRequest; - -/** - * - * @author TSARDD - */ -public class J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests extends TestCase { - - public final void testAfterPropertiesSetException() { - J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource t = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(); - try { - t.afterPropertiesSet(); - fail("AfterPropertiesSet didn't throw expected exception"); - } catch (IllegalArgumentException expected) { - } catch (Exception unexpected) { - fail("AfterPropertiesSet throws unexpected exception"); - } - } - - public final void testBuildDetailsHttpServletRequestNoMappedNoUserRoles() { - String[] mappedRoles = new String[] {}; - String[] roles = new String[] {}; - String[] expectedRoles = new String[] {}; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestNoMappedUnmappedUserRoles() { - String[] mappedRoles = new String[] {}; - String[] roles = new String[] { "Role1", "Role2" }; - String[] expectedRoles = new String[] {}; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestNoUserRoles() { - String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] roles = new String[] {}; - String[] expectedRoles = new String[] {}; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestAllUserRoles() { - String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] roles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] expectedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestUnmappedUserRoles() { - String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] roles = new String[] { "Role1", "Role2", "Role3", "Role4", "Role5" }; - String[] expectedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestPartialUserRoles() { - String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] roles = new String[] { "Role2", "Role3" }; - String[] expectedRoles = new String[] { "Role2", "Role3" }; - testDetails(mappedRoles, roles, expectedRoles); - } - - public final void testBuildDetailsHttpServletRequestPartialAndUnmappedUserRoles() { - String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; - String[] roles = new String[] { "Role2", "Role3", "Role5" }; - String[] expectedRoles = new String[] { "Role2", "Role3" }; - testDetails(mappedRoles, roles, expectedRoles); - } - - private void testDetails(String[] mappedRoles, String[] userRoles, String[] expectedRoles) { - J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource src = getJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(mappedRoles); - Object o = src.buildDetails(getRequest("testUser", userRoles)); - assertNotNull(o); - assertTrue("Returned object not of type PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails, actual type: " + o.getClass(), - o instanceof PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails); - PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails details = (PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails) o; - List gas = details.getGrantedAuthorities(); - assertNotNull("Granted authorities should not be null", gas); - assertEquals(expectedRoles.length, gas.size()); - - Collection expectedRolesColl = Arrays.asList(expectedRoles); - Collection gasRolesSet = new HashSet(); - for (int i = 0; i < gas.size(); i++) { - gasRolesSet.add(gas.get(i).getAuthority()); - } - assertTrue("Granted Authorities do not match expected roles", expectedRolesColl.containsAll(gasRolesSet) - && gasRolesSet.containsAll(expectedRolesColl)); - } - - private final J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource getJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource( - String[] mappedRoles) { - J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource result = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(); - result.setMappableRolesRetriever(getMappableRolesRetriever(mappedRoles)); - result.setUserRoles2GrantedAuthoritiesMapper(getJ2eeUserRoles2GrantedAuthoritiesMapper()); - result.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class); - - try { - result.afterPropertiesSet(); - } catch (Exception expected) { - fail("AfterPropertiesSet throws unexpected exception"); - } - return result; - } - - private MappableAttributesRetriever getMappableRolesRetriever(String[] mappedRoles) { - SimpleMappableAttributesRetriever result = new SimpleMappableAttributesRetriever(); - result.setMappableAttributes(mappedRoles); - return result; - } - - private Attributes2GrantedAuthoritiesMapper getJ2eeUserRoles2GrantedAuthoritiesMapper() { - SimpleAttributes2GrantedAuthoritiesMapper result = new SimpleAttributes2GrantedAuthoritiesMapper(); - result.setAddPrefixIfAlreadyExisting(false); - result.setConvertAttributeToLowerCase(false); - result.setConvertAttributeToUpperCase(false); - result.setAttributePrefix(""); - return result; - } - - private final HttpServletRequest getRequest(final String userName,final String[] aRoles) - { - MockHttpServletRequest req = new MockHttpServletRequest() { - private Set roles = new HashSet(Arrays.asList(aRoles)); - public boolean isUserInRole(String arg0) { - return roles.contains(arg0); - } - }; - req.setRemoteUser(userName); - return req; - } -} +package org.springframework.security.web.authentication.preauth.j2ee; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; + +import junit.framework.TestCase; + +import org.springframework.security.authoritymapping.MappableAttributesRetriever; +import org.springframework.security.authoritymapping.Attributes2GrantedAuthoritiesMapper; +import org.springframework.security.authoritymapping.SimpleMappableAttributesRetriever; +import org.springframework.security.authoritymapping.SimpleAttributes2GrantedAuthoritiesMapper; +import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; +import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; +import org.springframework.security.GrantedAuthority; + +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.util.StringUtils; + +/** + * + * @author TSARDD + */ +public class J2eeBasedPreAuthenticatedWebAuthenticationDetailsSourceTests extends TestCase { + + public final void testAfterPropertiesSetException() { + J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource t = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(); + try { + t.afterPropertiesSet(); + fail("AfterPropertiesSet didn't throw expected exception"); + } catch (IllegalArgumentException expected) { + } catch (Exception unexpected) { + fail("AfterPropertiesSet throws unexpected exception"); + } + } + + public final void testBuildDetailsHttpServletRequestNoMappedNoUserRoles() { + String[] mappedRoles = new String[] {}; + String[] roles = new String[] {}; + String[] expectedRoles = new String[] {}; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestNoMappedUnmappedUserRoles() { + String[] mappedRoles = new String[] {}; + String[] roles = new String[] { "Role1", "Role2" }; + String[] expectedRoles = new String[] {}; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestNoUserRoles() { + String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] roles = new String[] {}; + String[] expectedRoles = new String[] {}; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestAllUserRoles() { + String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] roles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] expectedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestUnmappedUserRoles() { + String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] roles = new String[] { "Role1", "Role2", "Role3", "Role4", "Role5" }; + String[] expectedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestPartialUserRoles() { + String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] roles = new String[] { "Role2", "Role3" }; + String[] expectedRoles = new String[] { "Role2", "Role3" }; + testDetails(mappedRoles, roles, expectedRoles); + } + + public final void testBuildDetailsHttpServletRequestPartialAndUnmappedUserRoles() { + String[] mappedRoles = new String[] { "Role1", "Role2", "Role3", "Role4" }; + String[] roles = new String[] { "Role2", "Role3", "Role5" }; + String[] expectedRoles = new String[] { "Role2", "Role3" }; + testDetails(mappedRoles, roles, expectedRoles); + } + + private void testDetails(String[] mappedRoles, String[] userRoles, String[] expectedRoles) { + J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource src = getJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(mappedRoles); + Object o = src.buildDetails(getRequest("testUser", userRoles)); + assertNotNull(o); + assertTrue("Returned object not of type PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails, actual type: " + o.getClass(), + o instanceof PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails); + PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails details = (PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails) o; + List gas = details.getGrantedAuthorities(); + assertNotNull("Granted authorities should not be null", gas); + assertEquals(expectedRoles.length, gas.size()); + + Collection expectedRolesColl = Arrays.asList(expectedRoles); + Collection gasRolesSet = new HashSet(); + for (int i = 0; i < gas.size(); i++) { + gasRolesSet.add(gas.get(i).getAuthority()); + } + assertTrue("Granted Authorities do not match expected roles", expectedRolesColl.containsAll(gasRolesSet) + && gasRolesSet.containsAll(expectedRolesColl)); + } + + private final J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource getJ2eeBasedPreAuthenticatedWebAuthenticationDetailsSource( + String[] mappedRoles) { + J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource result = new J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource(); + result.setMappableRolesRetriever(getMappableRolesRetriever(mappedRoles)); + result.setUserRoles2GrantedAuthoritiesMapper(getJ2eeUserRoles2GrantedAuthoritiesMapper()); + result.setClazz(PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails.class); + + try { + result.afterPropertiesSet(); + } catch (Exception expected) { + fail("AfterPropertiesSet throws unexpected exception"); + } + return result; + } + + private MappableAttributesRetriever getMappableRolesRetriever(String[] mappedRoles) { + SimpleMappableAttributesRetriever result = new SimpleMappableAttributesRetriever(); + result.setMappableAttributes(new HashSet(Arrays.asList(mappedRoles))); + return result; + } + + private Attributes2GrantedAuthoritiesMapper getJ2eeUserRoles2GrantedAuthoritiesMapper() { + SimpleAttributes2GrantedAuthoritiesMapper result = new SimpleAttributes2GrantedAuthoritiesMapper(); + result.setAddPrefixIfAlreadyExisting(false); + result.setConvertAttributeToLowerCase(false); + result.setConvertAttributeToUpperCase(false); + result.setAttributePrefix(""); + return result; + } + + private final HttpServletRequest getRequest(final String userName,final String[] aRoles) + { + MockHttpServletRequest req = new MockHttpServletRequest() { + private Set roles = new HashSet(Arrays.asList(aRoles)); + public boolean isUserInRole(String arg0) { + return roles.contains(arg0); + } + }; + req.setRemoteUser(userName); + return req; + } +}