diff --git a/sandbox/portlet/pom.xml b/sandbox/portlet/pom.xml
new file mode 100644
index 0000000000..801b2402e7
--- /dev/null
+++ b/sandbox/portlet/pom.xml
@@ -0,0 +1,70 @@
+
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.acegisecurity.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.acegisecurity.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.
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.
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.acegisecurity.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 {
+
+ // make sure the holder is clear
+ if (SecurityContextHolder.getContext() != null) {
+ if (logger.isWarnEnabled())
+ logger.warn("SecurityContextHolder should have been null but contained: '"
+ + SecurityContextHolder.getContext() + "'; setting to null now");
+ SecurityContextHolder.clearContext();
+ }
+
+ 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(ACEGI_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 ACEGI_SECURITY_CONTEXT a valid SecurityContext and "
+ + "set to SecurityContextHolder: '" + contextFromSessionObject + "'");
+ SecurityContextHolder.setContext((SecurityContext) contextFromSessionObject);
+ } else {
+ if (logger.isWarnEnabled())
+ logger.warn("ACEGI_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 ACEGI_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(ACEGI_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/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationProvider.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationProvider.java
new file mode 100644
index 0000000000..6f8fa92148
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationProvider.java
@@ -0,0 +1,190 @@
+/*
+ * 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.acegisecurity.providers.portlet;
+
+import java.security.Principal;
+
+import javax.portlet.PortletRequest;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.BadCredentialsException;
+import org.acegisecurity.providers.AuthenticationProvider;
+import org.acegisecurity.providers.portlet.cache.NullUserCache;
+import org.acegisecurity.userdetails.UserDetails;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+/**
+ * Processes a JSR-168 Portlet authentication request. The request will typically + * originate from {@link org.acegisecurity.ui.portlet.PortletProcessingInterceptor}.
+ * + *Be aware that this provider is trusting the portal and portlet container to handle + * actual authentication. If a valid {@link PortletAuthenticationToken} is presented with + * non-null principal and credentials, then the {@link #authenticate} method will succeed.
+ * + *If the details property of the requesting Authentication
+ * object happens to be the PortletRequest, then this provider will place
+ * the contents of the USER_INFO map from of the request attributes into
+ * the details property of the authentication result.
toString() method
+ * on the principal object and return that.
+ * @param principal the principal object to inspect for a username
+ * @return the determined username, or null if no principal is passed
+ */
+ public static final String getUsernameFromPrincipal(Object principal) {
+ if (principal == null) {
+ return null;
+ }
+ if (principal instanceof UserDetails) {
+ return ((UserDetails)principal).getUsername();
+ }
+ if (principal instanceof Principal) {
+ return ((Principal)principal).getName();
+ }
+ return principal.toString();
+ }
+
+
+ public PortletAuthoritiesPopulator getPortletAuthoritiesPopulator() {
+ return this.portletAuthoritiesPopulator;
+ }
+
+ public void setPortletAuthoritiesPopulator(PortletAuthoritiesPopulator portletAuthoritiesPopulator) {
+ this.portletAuthoritiesPopulator = portletAuthoritiesPopulator;
+ }
+
+ public UserCache getUserCache() {
+ return userCache;
+ }
+
+ public void setUserCache(UserCache userCache) {
+ this.userCache = userCache;
+ }
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationToken.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationToken.java
new file mode 100644
index 0000000000..172ac121d3
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthenticationToken.java
@@ -0,0 +1,57 @@
+/*
+ * 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.acegisecurity.providers.portlet;
+
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.providers.AbstractAuthenticationToken;
+
+/**
+ * Authentication implementation for JSR-168 Portlet authentication. The
+ * corresponding authentication provider is {@link PortletAuthenticationProvider}.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public class PortletAuthenticationToken extends AbstractAuthenticationToken {
+
+ //~ Instance fields ================================================================================================
+
+ private static final long serialVersionUID = 1L;
+
+ private Object principal;
+ private Object credentials;
+
+ //~ Constructors ===================================================================================================
+
+ public PortletAuthenticationToken(Object principal, Object credentials, GrantedAuthority[] authorities) {
+ super(authorities);
+ this.principal = principal;
+ this.credentials = credentials;
+ }
+
+ //~ Methods ========================================================================================================
+
+ public Object getPrincipal() {
+ return this.principal;
+ }
+
+ public Object getCredentials() {
+ return this.credentials;
+ }
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthoritiesPopulator.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthoritiesPopulator.java
new file mode 100644
index 0000000000..a4b4f5440b
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/PortletAuthoritiesPopulator.java
@@ -0,0 +1,45 @@
+/*
+ * 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.acegisecurity.providers.portlet;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * Populates the UserDetails associated with the
+ * portlet user presented by the portlet container.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public interface PortletAuthoritiesPopulator {
+
+ //~ Methods ========================================================================================================
+
+ /**
+ * Obtains the granted authorities for the specified Authentication object.
+ * May throw any AuthenticationException or return null
+ * if the authorities are unavailable.
+ * @param authentication the authentication object seeking authorities
+ * @return the details of the indicated user (at minimum the granted authorities and the username)
+ * @throws AuthenticationException if the user details are not available or the authentication is not valid for some reason
+ */
+ public UserDetails getUserDetails(Authentication authentication) throws AuthenticationException;
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/UserCache.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/UserCache.java
new file mode 100644
index 0000000000..90e3193d54
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/UserCache.java
@@ -0,0 +1,39 @@
+/*
+ * 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.acegisecurity.providers.portlet;
+
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * Provides a cache of {@link UserDetails} objects for the
+ * {@link PortletAuthenticationProvider}.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public interface UserCache {
+
+ //~ Methods ========================================================================================================
+
+ public UserDetails getUserFromCache(String username);
+
+ public void putUserInCache(UserDetails user);
+
+ public void removeUserFromCache(String username);
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/EhCacheBasedUserCache.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/EhCacheBasedUserCache.java
new file mode 100644
index 0000000000..742e248d01
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/EhCacheBasedUserCache.java
@@ -0,0 +1,102 @@
+/*
+ * 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.acegisecurity.providers.portlet.cache;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.Element;
+
+import org.acegisecurity.providers.portlet.UserCache;
+import org.acegisecurity.userdetails.UserDetails;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.dao.DataRetrievalFailureException;
+import org.springframework.util.Assert;
+
+/**
+ * UserCache implementation for portlets that uses an injected
+ * ehcache.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public class EhCacheBasedUserCache
+ implements UserCache, InitializingBean {
+
+ //~ Static fields/initializers =====================================================================================
+
+ private static final Log logger = LogFactory.getLog(EhCacheBasedUserCache.class);
+
+ //~ Instance fields ================================================================================================
+
+ private Cache cache;
+
+ //~ Methods ========================================================================================================
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(cache, "cache mandatory");
+ }
+
+ public UserDetails getUserFromCache(String username) {
+
+ Element element = null;
+
+ try {
+ element = cache.get(username);
+ } catch (CacheException cacheException) {
+ throw new DataRetrievalFailureException("Cache failure: "
+ + cacheException.getMessage());
+ }
+
+ if (logger.isDebugEnabled())
+ logger.debug("Cache hit: " + (element != null) + "; username: " + username);
+
+ return (element != null ? (UserDetails) element.getValue() : null);
+ }
+
+ public void putUserInCache(UserDetails user) {
+
+ Element element = new Element(user.getUsername(), user);
+
+ if (logger.isDebugEnabled())
+ logger.debug("Cache put: " + element.getKey());
+
+ cache.put(element);
+ }
+
+ public void removeUserFromCache(UserDetails user) {
+ this.removeUserFromCache(user.getUsername());
+ }
+
+ public void removeUserFromCache(String username) {
+ if (logger.isDebugEnabled())
+ logger.debug("Cache remove: " + username);
+ cache.remove(username);
+ }
+
+
+ public Cache getCache() {
+ return cache;
+ }
+
+ public void setCache(Cache cache) {
+ this.cache = cache;
+ }
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/NullUserCache.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/NullUserCache.java
new file mode 100644
index 0000000000..aa5083b5a4
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/cache/NullUserCache.java
@@ -0,0 +1,41 @@
+/*
+ * 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.acegisecurity.providers.portlet.cache;
+
+import org.acegisecurity.providers.portlet.UserCache;
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * UserCache implementation for portlets that does nothing.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public class NullUserCache implements UserCache {
+
+ //~ Methods ========================================================================================================
+
+ public UserDetails getUserFromCache(String username) {
+ return null;
+ }
+
+ public void putUserInCache(UserDetails user) {}
+
+ public void removeUserFromCache(String username) {}
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/ContainerPortletAuthoritiesPopulator.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/ContainerPortletAuthoritiesPopulator.java
new file mode 100644
index 0000000000..a1200520a2
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/ContainerPortletAuthoritiesPopulator.java
@@ -0,0 +1,136 @@
+/*
+ * 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.acegisecurity.providers.portlet.populator;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.portlet.PortletRequest;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+import org.acegisecurity.providers.portlet.PortletAuthenticationProvider;
+import org.acegisecurity.providers.portlet.PortletAuthoritiesPopulator;
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+
+/**
+ * Populates the portlet authorities via role information from the portlet container.
+ * Primarily it uses the PortletRequest.isUserInRole(role) method to
+ * check if the user is in a list of configured roles.
+ * This bean has the following configurable properties:
+ *
+ * rolesToCheck : A list of strings containing names of roles to check.
+ * These roles must also be properly declared in a <security-role-ref> element
+ * of the portlet descriptor in the portlet.xml file.
+ * rolePrefix : The prefix to be added onto each role name that as it is
+ * added to the list of authorities. The default value is 'ROLE_'.
+ * userRole : The role that all authenticated users will automatically be
+ * granted. The default value is 'ROLE_USER'.
+ *
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public class ContainerPortletAuthoritiesPopulator
+ implements PortletAuthoritiesPopulator {
+
+ //~ Static fields/initializers =====================================================================================
+
+ private static final String defaultRolePrefix = "ROLE_";
+ private static final String defaultUserRole = "ROLE_USER";
+
+ //~ Instance fields ================================================================================================
+
+ private List rolesToCheck;
+ private String rolePrefix = defaultRolePrefix;
+ private String userRole = defaultUserRole;
+
+ //~ Methods ========================================================================================================
+
+ public UserDetails getUserDetails(Authentication authentication)
+ throws AuthenticationException {
+
+ // get the username and password for the authentication
+ String username = PortletAuthenticationProvider.getUsernameFromPrincipal(authentication.getPrincipal());
+ String password = authentication.getCredentials().toString();
+
+ // see if we can load authorities from the portlet request
+ Object details = authentication.getDetails();
+ if (!(details instanceof PortletRequest)) {
+ throw new AuthenticationServiceException("expected getDetails() to return the PortletRequest object");
+ }
+ GrantedAuthority[] authorities = loadGrantedAuthorities((PortletRequest)details);
+
+ // construct and return the new user
+ return new User(username, password, true, true, true, true, authorities);
+ }
+
+ private GrantedAuthority[] loadGrantedAuthorities(PortletRequest request) {
+
+ // start the list and add the standard user role
+ ArrayList authorities = new ArrayList();
+ authorities.add(new GrantedAuthorityImpl(getUserRole()));
+
+ // iterate through the configured list of roles to check (if there is one)
+ if (this.rolesToCheck != null) {
+ for(Iterator i = this.rolesToCheck.iterator(); i.hasNext(); ) {
+ String role = (String)i.next();
+
+ // if the request says the user has that role, then add it
+ if (request.isUserInRole(role)) {
+ authorities.add(new GrantedAuthorityImpl(getRolePrefix() + role));
+ }
+
+ }
+ }
+
+ // return the array of GrantedAuthority objects
+ return (GrantedAuthority[])authorities.toArray(new GrantedAuthority[authorities.size()]);
+ }
+
+
+ public List getRolesToCheck() {
+ return rolesToCheck;
+ }
+
+ public void setRolesToCheck(List rolesToCheck) {
+ this.rolesToCheck = rolesToCheck;
+ }
+
+ public String getRolePrefix() {
+ return rolePrefix;
+ }
+
+ public void setRolePrefix(String rolePrefix) {
+ this.rolePrefix = rolePrefix;
+ }
+
+ public String getUserRole() {
+ return userRole;
+ }
+
+ public void setUserRole(String userRole) {
+ this.userRole = userRole;
+ }
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/DaoPortletAuthoritiesPopulator.java b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/DaoPortletAuthoritiesPopulator.java
new file mode 100644
index 0000000000..f84416658c
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/providers/portlet/populator/DaoPortletAuthoritiesPopulator.java
@@ -0,0 +1,74 @@
+/*
+ * 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.acegisecurity.providers.portlet.populator;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.providers.portlet.PortletAuthenticationProvider;
+import org.acegisecurity.providers.portlet.PortletAuthoritiesPopulator;
+import org.acegisecurity.userdetails.UserDetails;
+import org.acegisecurity.userdetails.UserDetailsService;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+/**
+ * Populates the portlet authorities via a {@link UserDetailsService}.
+ *
+ * @author John A. Lewis
+ * @since 2.0
+ * @version $Id$
+ */
+public class DaoPortletAuthoritiesPopulator
+ implements PortletAuthoritiesPopulator, InitializingBean {
+
+ //~ Instance fields ================================================================================================
+
+ private UserDetailsService userDetailsService;
+
+ //~ Methods ========================================================================================================
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(this.userDetailsService, "A userDetailsService must be set");
+ }
+
+ public UserDetails getUserDetails(Authentication authentication)
+ throws AuthenticationException {
+
+ // make sure the Authentication object is valid
+ if (authentication == null || authentication.getPrincipal() == null) {
+ throw new AuthenticationServiceException(
+ "must pass valid Authentication object with non-null principal");
+ }
+
+ // get the username from the principal
+ String username = PortletAuthenticationProvider.getUsernameFromPrincipal(authentication.getPrincipal());
+
+ // call the UserDetailsService with the username
+ return this.userDetailsService.loadUserByUsername(username);
+ }
+
+
+ public UserDetailsService getUserDetailsService() {
+ return userDetailsService;
+ }
+
+ public void setUserDetailsService(UserDetailsService userDetailsService) {
+ this.userDetailsService = userDetailsService;
+ }
+
+}
diff --git a/sandbox/portlet/src/main/java/org/acegisecurity/ui/portlet/PortletProcessingInterceptor.java b/sandbox/portlet/src/main/java/org/acegisecurity/ui/portlet/PortletProcessingInterceptor.java
new file mode 100644
index 0000000000..97eb5fa674
--- /dev/null
+++ b/sandbox/portlet/src/main/java/org/acegisecurity/ui/portlet/PortletProcessingInterceptor.java
@@ -0,0 +1,293 @@
+/*
+ * 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.acegisecurity.ui.portlet;
+
+import java.io.IOException;
+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.RenderRequest;
+import javax.portlet.RenderResponse;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationManager;
+import org.acegisecurity.context.SecurityContext;
+import org.acegisecurity.context.SecurityContextHolder;
+import org.acegisecurity.providers.portlet.PortletAuthenticationProvider;
+import org.acegisecurity.providers.portlet.PortletAuthenticationToken;
+import org.acegisecurity.providers.portlet.populator.ContainerPortletAuthoritiesPopulator;
+import org.acegisecurity.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
+ * PortletSession with the attribute defined by
+ * {@link AbstractProcessingFilter#ACEGI_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.acegisecurity.ui.AbstractProcessingFilter
+ * @see org.acegisecurity.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;
+
+ //~ 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
+ PortletAuthenticationToken authRequest = new PortletAuthenticationToken(
+ getPrincipalFromRequest(request),
+ getCredentialsFromRequest(request),
+ null);
+
+ // put the PortletRequest into the authentication request as the "details"
+ authRequest.setDetails(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");
+ ctx.setAuthentication(null);
+ request.getPortletSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY, failed);
+ 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()
+ Object principal = request.getRemoteUser();
+ if (principal != null) {
+ return principal;
+ }
+
+ // next try getUserPrincipal()
+ principal = request.getUserPrincipal();
+ if (principal != null) {
+ return principal;
+ }
+
+ // last try entries in USER_INFO if any attributes were defined
+ if (this.userNameAttributes != null) {
+ Map userInfo = (Map)request.getAttribute(PortletRequest.USER_INFO);
+ if (userInfo != null) {
+ Iterator i = this.userNameAttributes.iterator();
+ while(i.hasNext()) {
+ 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) {
+ return request.getAuthType();
+ }
+
+
+ /**
+ * 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 AuthenticationManager getAuthenticationManager() {
+ return authenticationManager;
+ }
+
+ public void setAuthenticationManager(AuthenticationManager authenticationManager) {
+ this.authenticationManager = authenticationManager;
+ }
+
+ public List getUserNameAttributes() {
+ return userNameAttributes;
+ }
+
+ public void setUserNameAttributes(List userNameAttributes) {
+ this.userNameAttributes = userNameAttributes;
+ }
+
+}