diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentLoginException.java
similarity index 76%
rename from core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java
rename to core/src/main/java/org/acegisecurity/concurrent/ConcurrentLoginException.java
index f7a2381616..6a9121389c 100644
--- a/core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java
+++ b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentLoginException.java
@@ -12,21 +12,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-package net.sf.acegisecurity.providers;
+package net.sf.acegisecurity.concurrent;
import net.sf.acegisecurity.AuthenticationException;
/**
- * Thrown by the ConcurrentSessionController when the number of sessions
- * allowed is attempting to be exceeded.
+ * Thrown by ConcurrentSessionControllerImpl if
+ * an attempt is made to login and the user has already exceeded
+ * their maxmimum allowed sessions.
*
- * @author Ray Krueger
+ * @author Ben Alex
+ * @version $Id$
*/
public class ConcurrentLoginException extends AuthenticationException {
- //~ Constructors ===========================================================
-
public ConcurrentLoginException(String msg) {
super(msg);
}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionController.java b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionController.java
new file mode 100644
index 0000000000..db94ca4694
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionController.java
@@ -0,0 +1,65 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+
+
+/**
+ * Provides two methods that can be called by an {@link
+ * net.sf.acegisecurity.AuthenticationManager} to integrate with the
+ * concurrent session handling infrastructure.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface ConcurrentSessionController {
+ //~ Methods ================================================================
+
+ /**
+ * Called by any class that wishes to know whether the current
+ * authentication request should be permitted. Generally callers will be
+ * AuthenticationManagers before they authenticate, but could
+ * equally include Filters or other interceptors that wish to
+ * confirm the ongoing validity of a previously authenticated
+ * Authentication.
+ *
+ *
+ * The implementation should throw a suitable exception if the user has
+ * exceeded their maximum allowed concurrent sessions.
+ *
+ *
+ * @param request the authentication request (never null)
+ *
+ * @throws AuthenticationException if the user has exceeded their maximum
+ * allowed current sessions
+ */
+ public void checkAuthenticationAllowed(Authentication request)
+ throws AuthenticationException;
+
+ /**
+ * Called by an AuthenticationManager when the authentication
+ * was successful. An implementation is expected to register the
+ * authenticated user in some sort of registry, for future concurrent
+ * tracking via the {@link #checkConcurrentAuthentication(Authentication)}
+ * method.
+ *
+ * @param authentication the successfully authenticated user (never
+ * null)
+ */
+ public void registerSuccessfulAuthentication(Authentication authentication);
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImpl.java b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImpl.java
new file mode 100644
index 0000000000..d598e1cac6
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImpl.java
@@ -0,0 +1,155 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import java.util.Date;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationException;
+
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.util.Assert;
+
+
+/**
+ * Base implementation of {@link ConcurrentSessionControllerImpl} which
+ * prohibits simultaneous logins.
+ *
+ *
+ * By default uses {@link net.sf.acegisecurity.concurrent.SessionRegistryImpl},
+ * although any SessionRegistry may be used.
+ *
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ConcurrentSessionControllerImpl
+ implements ConcurrentSessionController, InitializingBean {
+ //~ Instance fields ========================================================
+
+ private SessionRegistry sessionRegistry = new SessionRegistryImpl();
+ private int maximumSessions = 1;
+ private boolean exceptionIfMaximumExceeded = false;
+
+ //~ Methods ================================================================
+
+ public void setMaximumSessions(int maximumSessions) {
+ this.maximumSessions = maximumSessions;
+ }
+
+ public void setSessionRegistry(SessionRegistry sessionRegistry) {
+ this.sessionRegistry = sessionRegistry;
+ }
+
+ public void setExceptionIfMaximumExceeded(boolean exceptionIfMaximumExceeded) {
+ this.exceptionIfMaximumExceeded = exceptionIfMaximumExceeded;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(sessionRegistry, "SessionRegistry required");
+ Assert.isTrue(maximumSessions != 0,
+ "MaximumLogins must be either -1 to allow unlimited logins, or a positive integer to specify a maximum");
+ }
+
+ public void checkAuthenticationAllowed(Authentication request)
+ throws AuthenticationException {
+ Assert.notNull(request,
+ "Authentication request cannot be null (violation of interface contract)");
+
+ Object principal = SessionRegistryUtils
+ .obtainPrincipalFromAuthentication(request);
+ String sessionId = SessionRegistryUtils
+ .obtainSessionIdFromAuthentication(request);
+
+ SessionInformation[] sessions = sessionRegistry.getAllSessions(principal);
+
+ int sessionCount = 0;
+
+ if (sessions != null) {
+ sessionCount = sessions.length;
+ }
+
+ int allowableSessions = getMaximumSessionsForThisUser(request);
+ Assert.isTrue(allowableSessions != 0,
+ "getMaximumSessionsForThisUser() must return either -1 to allow unlimited logins, or a positive integer to specify a maximum");
+
+ if (sessionCount < allowableSessions) {
+ return;
+ } else if (sessionCount == allowableSessions) {
+ // Only permit it though if this request is associated with one of the sessions
+ for (int i = 0; i < sessionCount; i++) {
+ if (sessions[i].getSessionId().equals(sessionId)) {
+ return;
+ }
+ }
+ }
+
+ allowableSessionsExceeded(sessionId, sessions, allowableSessions, sessionRegistry);
+ }
+
+ /**
+ * Allows subclasses to customise behaviour when too many sessions are
+ * detected.
+ *
+ * @param sessionId the session ID of the present request
+ * @param sessions either null or all unexpired sessions associated with the principal
+ * @param registry an instance of the SessionRegistry for subclass use
+ */
+ protected void allowableSessionsExceeded(String sessionId, SessionInformation[] sessions, int allowableSessions, SessionRegistry registry) {
+ if (exceptionIfMaximumExceeded || sessions == null) {
+ throw new ConcurrentLoginException("Maximum sessions of "
+ + allowableSessions + " for this principal exceeded");
+ }
+
+ // Determine least recently used session, and mark it for invalidation
+ SessionInformation leastRecentlyUsed = null;
+ for (int i = 0; i < sessions.length; i++) {
+ if (leastRecentlyUsed == null || sessions[i].getLastRequest().before(leastRecentlyUsed.getLastRequest())) {
+ leastRecentlyUsed = sessions[i];
+ }
+ }
+
+ leastRecentlyUsed.expireNow();
+ }
+
+ public void registerSuccessfulAuthentication(Authentication authentication) {
+ Assert.notNull(authentication,
+ "Authentication cannot be null (violation of interface contract)");
+
+ Object principal = SessionRegistryUtils
+ .obtainPrincipalFromAuthentication(authentication);
+ String sessionId = SessionRegistryUtils
+ .obtainSessionIdFromAuthentication(authentication);
+
+ sessionRegistry.removeSessionInformation(sessionId);
+ sessionRegistry.registerNewSession(sessionId, principal);
+ }
+
+ /**
+ * Method intended for use by subclasses to override the maximum number of
+ * sessions that are permitted for a particular authentication. The
+ * default implementation simply returns the maximumSessions
+ * value for the bean.
+ *
+ * @param authentication to determine the maximum sessions for
+ *
+ * @return either -1 meaning unlimited, or a positive integer to limit
+ * (never zero)
+ */
+ protected int getMaximumSessionsForThisUser(Authentication authentication) {
+ return maximumSessions;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionFilter.java b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionFilter.java
new file mode 100644
index 0000000000..3f07e1b60b
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/ConcurrentSessionFilter.java
@@ -0,0 +1,128 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * Filter required by concurrent session handling package.
+ *
+ *
+ * This filter performs two functions. First, it calls {@link
+ * net.sf.acegisecurity.concurrent.SessionRegistry#refreshLastRequest(String)}
+ * for each request. That way, registered sessions always have a correct "last
+ * update" date/time. Second, it retrieves {@link
+ * net.sf.acegisecurity.concurrent.SessionInformation} from the
+ * SessionRegistry for each request and checks if the session has
+ * been marked as expired. If it has been marked as expired, the session is
+ * invalidated. The invalidation of the session will also cause the request to
+ * redirect to the URL specified, and a {@link
+ * net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent} to be published
+ * via the {@link net.sf.acegisecurity.ui.session.HttpSessionEventPublisher}
+ * registered in web.xml.
+ *
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ConcurrentSessionFilter implements Filter,
+ InitializingBean {
+ //~ Instance fields ========================================================
+
+ private SessionRegistry sessionRegistry;
+ private String expiredUrl;
+
+ //~ Methods ================================================================
+
+ public void setExpiredUrl(String expiredUrl) {
+ this.expiredUrl = expiredUrl;
+ }
+
+ public void setSessionRegistry(SessionRegistry sessionRegistry) {
+ this.sessionRegistry = sessionRegistry;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(sessionRegistry, "SessionRegistry required");
+ Assert.hasText(expiredUrl, "ExpiredUrl required");
+ }
+
+ /**
+ * Does nothing. We use IoC container lifecycle services instead.
+ */
+ public void destroy() {}
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ Assert.isInstanceOf(HttpServletRequest.class, request,
+ "Can only process HttpServletRequest");
+ Assert.isInstanceOf(HttpServletResponse.class, response,
+ "Can only process HttpServletResponse");
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+
+ HttpSession session = httpRequest.getSession(false);
+
+ if (session != null) {
+ SessionInformation info = sessionRegistry.getSessionInformation(session
+ .getId());
+
+ if (info != null) {
+ if (info.isExpired()) {
+ // Expired - abort processing
+ session.invalidate();
+
+ String targetUrl = httpRequest.getContextPath()
+ + expiredUrl;
+ httpResponse.sendRedirect(httpResponse.encodeRedirectURL(
+ targetUrl));
+
+ return;
+ } else {
+ // Non-expired - update last request date/time
+ info.refreshLastRequest();
+ }
+ }
+ }
+
+ chain.doFilter(request, response);
+ }
+
+ /**
+ * Does nothing. We use IoC container lifecycle services instead.
+ *
+ * @param arg0 ignored
+ *
+ * @throws ServletException ignored
+ */
+ public void init(FilterConfig arg0) throws ServletException {}
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java b/core/src/main/java/org/acegisecurity/concurrent/NullConcurrentSessionController.java
similarity index 63%
rename from core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java
rename to core/src/main/java/org/acegisecurity/concurrent/NullConcurrentSessionController.java
index 0bdcd86e7c..0af8d36d38 100644
--- a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java
+++ b/core/src/main/java/org/acegisecurity/concurrent/NullConcurrentSessionController.java
@@ -13,24 +13,25 @@
* limitations under the License.
*/
-package net.sf.acegisecurity.providers;
+package net.sf.acegisecurity.concurrent;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
/**
- * See: {@link ConcurrentSessionControllerImpl}
+ * No-op implementation of {@link
+ * net.sf.acegisecurity.concurrent.ConcurrentSessionController}.
*
- * @author Ray Krueger
- * @see ConcurrentSessionControllerImpl
+ * @author Ben Alex
+ * @version $Id$
*/
-public interface ConcurrentSessionController {
+public class NullConcurrentSessionController
+ implements ConcurrentSessionController {
//~ Methods ================================================================
- void afterAuthentication(Authentication initialAuth, Authentication result)
- throws AuthenticationException;
+ public void checkAuthenticationAllowed(Authentication request)
+ throws AuthenticationException {}
- void beforeAuthentication(Authentication initialAuth)
- throws AuthenticationException;
+ public void registerSuccessfulAuthentication(Authentication authentication) {}
}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/SessionAlreadyUsedException.java b/core/src/main/java/org/acegisecurity/concurrent/SessionAlreadyUsedException.java
new file mode 100644
index 0000000000..91cd85f6a6
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/SessionAlreadyUsedException.java
@@ -0,0 +1,32 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.sf.acegisecurity.concurrent;
+
+import net.sf.acegisecurity.AuthenticationException;
+
+
+/**
+ * Thrown by a SessionRegistry implementation if
+ * an attempt is made to create new session information for an existing
+ * sessionId. The user should firstly clear the existing session from the
+ * ConcurrentSessionRegistry.
+ *
+ * @author Ben Alex
+ */
+public class SessionAlreadyUsedException extends AuthenticationException {
+ public SessionAlreadyUsedException(String msg) {
+ super(msg);
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/SessionInformation.java b/core/src/main/java/org/acegisecurity/concurrent/SessionInformation.java
new file mode 100644
index 0000000000..f50273db5f
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/SessionInformation.java
@@ -0,0 +1,94 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import org.springframework.util.Assert;
+
+import java.util.Date;
+
+
+/**
+ * Represents a record of a session within the Acegi Security framework.
+ *
+ *
+ * This is primarily used for concurrent session support.
+ *
+ *
+ *
+ * Sessions have three states: active, expired, and destroyed. A session can
+ * that is invalidated by session.invalidate() or via Servlet
+ * Container management is considered "destroyed". An "expired" session, on
+ * the other hand, is a session that Acegi Security wants to end because it
+ * was selected for removal for some reason (generally as it was the least
+ * recently used session and the maximum sessions for the user were reached).
+ * An "expired" session is removed as soon as possible by a
+ * Filter.
+ *
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SessionInformation {
+ //~ Instance fields ========================================================
+
+ private Date lastRequest;
+ private Object principal;
+ private String sessionId;
+ private boolean expired = false;
+
+ //~ Constructors ===========================================================
+
+ public SessionInformation(Object principal, String sessionId,
+ Date lastRequest) {
+ Assert.notNull(principal, "Principal required");
+ Assert.hasText(sessionId, "SessionId required");
+ Assert.notNull(lastRequest, "LastRequest required");
+ this.principal = principal;
+ this.sessionId = sessionId;
+ this.lastRequest = lastRequest;
+ }
+
+ private SessionInformation() {}
+
+ //~ Methods ================================================================
+
+ public boolean isExpired() {
+ return expired;
+ }
+
+ public Date getLastRequest() {
+ return lastRequest;
+ }
+
+ public Object getPrincipal() {
+ return principal;
+ }
+
+ public String getSessionId() {
+ return sessionId;
+ }
+
+ public void expireNow() {
+ this.expired = true;
+ }
+
+ /**
+ * Refreshes the internal lastRequest to the current date and time.
+ */
+ public void refreshLastRequest() {
+ this.lastRequest = new Date();
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/SessionRegistry.java b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistry.java
new file mode 100644
index 0000000000..57eebc3e42
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistry.java
@@ -0,0 +1,83 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+/**
+ * Maintains a registry of SessionInformation instances.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface SessionRegistry {
+ //~ Methods ================================================================
+
+ /**
+ * Obtains all the known sessions for the specified principal. Sessions
+ * that have expired or destroyed are not returned.
+ *
+ * @param principal to locate sessions for (should never be
+ * null)
+ *
+ * @return the unexpired and undestroyed sessions for this principal, or
+ * null if none were found
+ */
+ public SessionInformation[] getAllSessions(Object principal);
+
+ /**
+ * Obtains the session information for the specified
+ * sessionId. Even expired sessions are returned (although
+ * destroyed sessions are never returned).
+ *
+ * @param sessionId to lookup (should never be null)
+ *
+ * @return the session information, or null if not found
+ */
+ public SessionInformation getSessionInformation(String sessionId);
+
+ /**
+ * Updates the given sessionId so its last request time is
+ * equal to the present date and time. Silently returns if the given
+ * sessionId cannot be found or the session is marked to expire.
+ *
+ * @param sessionId for which to update the date and time of the last
+ * request (should never be null)
+ */
+ public void refreshLastRequest(String sessionId);
+
+ /**
+ * Registers a new session for the specified principal. The newly
+ * registered session will not be marked for expiration.
+ *
+ * @param sessionId to associate with the principal (should never be
+ * null)
+ * @param principal to associate with the session (should never be
+ * null)
+ *
+ * @throws SessionAlreadyUsedException DOCUMENT ME!
+ */
+ public void registerNewSession(String sessionId, Object principal)
+ throws SessionAlreadyUsedException;
+
+ /**
+ * Deletes all the session information being maintained for the specified
+ * sessionId. If the sessionId is not found, the
+ * method gracefully returns.
+ *
+ * @param sessionId to delete information for (should never be
+ * null)
+ */
+ public void removeSessionInformation(String sessionId);
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryImpl.java b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryImpl.java
new file mode 100644
index 0000000000..022533619d
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryImpl.java
@@ -0,0 +1,147 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
+
+import org.springframework.context.ApplicationEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.util.Assert;
+
+
+/**
+ * Base implementation of {@link
+ * net.sf.acegisecurity.concurrent.SessionRegistry} which also listens for
+ * {@link net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent}s
+ * published in the Spring application context.
+ *
+ *
+ * NB: It is important that you register the {@link
+ * net.sf.acegisecurity.ui.session.HttpSessionEventPublisher} in
+ * web.xml so that this class is notified of sessions that
+ * expire.
+ *
+ *
+ * @author Ben Alex
+ * @version $Id${date}
+ */
+public class SessionRegistryImpl implements SessionRegistry,
+ ApplicationListener {
+ //~ Instance fields ========================================================
+
+ private Map principals = Collections.synchronizedMap(new HashMap()); //
+ private Map sessionIds = Collections.synchronizedMap(new HashMap()); //
+
+ //~ Methods ================================================================
+
+ public SessionInformation[] getAllSessions(Object principal) {
+ Set sessionsUsedByPrincipal = (Set) principals.get(principal);
+
+ if (sessionsUsedByPrincipal == null) {
+ return null;
+ }
+
+ List list = new ArrayList();
+ Iterator iter = sessionsUsedByPrincipal.iterator();
+ while (iter.hasNext()) {
+ String sessionId = (String) iter.next();
+ list.add(getSessionInformation(sessionId));
+ }
+
+ return (SessionInformation[]) list.toArray(new SessionInformation[] {});
+ }
+
+ public SessionInformation getSessionInformation(String sessionId) {
+ Assert.hasText(sessionId, "SessionId required as per inerface contract");
+
+ return (SessionInformation) sessionIds.get(sessionId);
+ }
+
+ public void onApplicationEvent(ApplicationEvent event) {
+ if (event instanceof HttpSessionDestroyedEvent) {
+ String sessionId = ((HttpSession) event.getSource()).getId();
+ removeSessionInformation(sessionId);
+ }
+ }
+
+ public void refreshLastRequest(String sessionId) {
+ Assert.hasText(sessionId, "SessionId required as per inerface contract");
+
+ SessionInformation info = getSessionInformation(sessionId);
+
+ if (info != null) {
+ info.refreshLastRequest();
+ }
+ }
+
+ public void registerNewSession(String sessionId, Object principal)
+ throws SessionAlreadyUsedException {
+ Assert.hasText(sessionId, "SessionId required as per inerface contract");
+ Assert.notNull(principal, "Principal required as per inerface contract");
+
+ if (getSessionInformation(sessionId) != null) {
+ throw new SessionAlreadyUsedException("Session " + sessionId
+ + " is already is use");
+ }
+
+ sessionIds.put(sessionId,
+ new SessionInformation(principal, sessionId, new Date()));
+
+ Set sessionsUsedByPrincipal = (Set) principals.get(principal);
+
+ if (sessionsUsedByPrincipal == null) {
+ sessionsUsedByPrincipal = Collections.synchronizedSet(new HashSet());
+ }
+
+ sessionsUsedByPrincipal.add(sessionId);
+
+ principals.put(principal, sessionsUsedByPrincipal);
+ }
+
+ public void removeSessionInformation(String sessionId) {
+ Assert.hasText(sessionId, "SessionId required as per inerface contract");
+
+ SessionInformation info = getSessionInformation(sessionId);
+
+ if (info != null) {
+ sessionIds.remove(sessionId);
+
+ Set sessionsUsedByPrincipal = (Set) principals.get(info
+ .getPrincipal());
+
+ if (sessionsUsedByPrincipal != null) {
+ sessionsUsedByPrincipal.remove(sessionId);
+
+ if (sessionsUsedByPrincipal.size() == 0) {
+ // No need to keep pbject in principals Map anymore
+ principals.remove(info.getPrincipal());
+ }
+ }
+ }
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryUtils.java b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryUtils.java
new file mode 100644
index 0000000000..f04a54581d
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/concurrent/SessionRegistryUtils.java
@@ -0,0 +1,57 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.ui.WebAuthenticationDetails;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Utility methods to assist with concurrent session management.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SessionRegistryUtils {
+ //~ Methods ================================================================
+
+ public static Object obtainPrincipalFromAuthentication(Authentication auth) {
+ Assert.notNull(auth, "Authentication required");
+ Assert.notNull(auth.getPrincipal(),
+ "Authentication.getPrincipal() required");
+
+ if (auth.getPrincipal() instanceof UserDetails) {
+ return ((UserDetails) auth.getPrincipal()).getUsername();
+ } else {
+ return auth.getPrincipal();
+ }
+ }
+
+ public static String obtainSessionIdFromAuthentication(Authentication auth) {
+ Assert.notNull(auth, "Authentication required");
+ Assert.notNull(auth.getDetails(), "Authentication.getDetails() required");
+ Assert.isInstanceOf(WebAuthenticationDetails.class, auth.getDetails());
+
+ String sessionId = ((WebAuthenticationDetails) auth.getDetails())
+ .getSessionId();
+ Assert.hasText(sessionId, "WebAuthenticationDetails missing SessionId");
+
+ return sessionId;
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java
deleted file mode 100644
index 1244ef6bee..0000000000
--- a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sf.acegisecurity.providers;
-
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.AuthenticationTrustResolver;
-import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.ui.WebAuthenticationDetails;
-import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.context.ApplicationEvent;
-import org.springframework.context.ApplicationListener;
-
-import javax.servlet.http.HttpSession;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-
-/**
- * Used by the {@link ProviderManager} to track Authentications and their
- * respective sessions. A given user is allowed {@link #setMaxSessions(int)}
- * sessions. If they attempt to exceed that ammount a {@link
- * ConcurrentLoginException} will be thrown. The
- * ConcurrentSessionControllerImpl class will listen for {@link
- * HttpSessionDestroyedEvent}s in the ApplicationContext to remove a session
- * from the internal tracking. This class will not function properly
- * without a {@link net.sf.acegisecurity.ui.session.HttpSessionEventPublisher}
- * configured in web.xml.
- *
- * @author Ray Krueger
- * @author Ben Alex
- */
-public class ConcurrentSessionControllerImpl
- implements ConcurrentSessionController, ApplicationListener,
- ApplicationContextAware {
- //~ Instance fields ========================================================
-
- protected Map principalsToSessions = new HashMap();
- protected Map sessionsToPrincipals = new HashMap();
- protected Set sessionSet = new HashSet();
- private ApplicationContext applicationContext;
- private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
- private int maxSessions = 1;
-
- //~ Methods ================================================================
-
- public void setApplicationContext(ApplicationContext applicationContext)
- throws BeansException {
- this.applicationContext = applicationContext;
- }
-
- public ApplicationContext getApplicationContext() {
- return applicationContext;
- }
-
- /**
- * Set the maximum number of sessions a user is allowed to have, defaults
- * to 1. Setting this to anything less than 1 will allow unlimited
- * sessions
- *
- * @param maxSessions
- */
- public void setMaxSessions(int maxSessions) {
- this.maxSessions = maxSessions;
- }
-
- /**
- * The maximum sessions per user.
- *
- * @return int
- */
- public int getMaxSessions() {
- return maxSessions;
- }
-
- /**
- * The trustResolver to use for determining Anonymous users and ignoring
- * them. Defaults to {@link AuthenticationTrustResolverImpl}
- *
- * @param trustResolver
- */
- public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
- this.trustResolver = trustResolver;
- }
-
- /**
- * Get the configured AuthenticationTrustResolver
- *
- * @return The configured AuthenticationTrustResolver or {@link
- * AuthenticationTrustResolverImpl} by default.
- */
- public AuthenticationTrustResolver getTrustResolver() {
- return trustResolver;
- }
-
- /**
- * Called by the {@link ProviderManager} after receiving a response from a
- * configured AuthenticationProvider.
- *
- * @param request Used to retieve the {@link WebAuthenticationDetails}
- * @param response Used to store the sessionId for the current Principal
- * @throws ConcurrentLoginException If the user is already logged in the
- * maximum number of times
- * @see #determineSessionPrincipal(net.sf.acegisecurity.Authentication)
- */
- public void afterAuthentication(Authentication request,
- Authentication response) throws ConcurrentLoginException {
- enforceConcurrentLogins(response);
-
- if (request.getDetails() instanceof WebAuthenticationDetails) {
- String sessionId = ((WebAuthenticationDetails) request.getDetails())
- .getSessionId();
- addSession(determineSessionPrincipal(response), sessionId);
- }
- }
-
- /**
- * Called by the {@link ProviderManager} before iterating the configured
- * {@link AuthenticationProvider}s
- *
- * @param request The Authentication in question
- * @throws ConcurrentLoginException If the user is already logged in the
- * maximum number of times #setMaxSessions(int)}
- */
- public void beforeAuthentication(Authentication request)
- throws ConcurrentLoginException {
- enforceConcurrentLogins(request);
- }
-
- /**
- * Checks for {@link HttpSessionDestroyedEvent}s and calls {@link
- * #removeSession(String)} for the destoyed HttpSessions id.
- *
- * @param event
- */
- public void onApplicationEvent(ApplicationEvent event) {
- if (event instanceof HttpSessionDestroyedEvent) {
- String sessionId = ((HttpSession) event.getSource()).getId();
- removeSession(sessionId);
- }
- }
-
- /**
- * Compares the sessionIds stored for the given principal to determine if
- * the given sessionId is new or existing.
- *
- * @param principal The principal in question
- * @param sessionId The new or existing sessionId
- * @return true if it's the same as a session already in use, false if it
- * is a new session
- */
- protected boolean isActiveSession(Object principal, String sessionId) {
- Set sessions = (Set) principalsToSessions.get(principal);
-
- if (sessions == null) {
- return false;
- }
-
- return sessions.contains(sessionId);
- }
-
- /**
- * Updates internal maps with the sessionId for the given principal. Can be
- * overridden by subclasses to provide a specialized means of principal
- * -> session tracking.
- *
- * @param principal
- * @param sessionId
- */
- protected void addSession(Object principal, String sessionId) {
- Set sessions = (Set) principalsToSessions.get(principal);
-
- if (sessions == null) {
- sessions = new HashSet();
- principalsToSessions.put(principal, sessions);
- }
-
- sessions.add(sessionId);
- sessionsToPrincipals.put(sessionId, principal);
- }
-
- /**
- * Counts the number of sessions in use by the given principal
- *
- * @param principal The principal object
- * @return 0 if there are no sessions, > if there are any
- */
- protected int countSessions(Object principal) {
- Set set = (Set) principalsToSessions.get(principal);
-
- if (set == null) {
- return 0;
- }
-
- return set.size();
- }
-
- /**
- * Checks to see if the Authentication principal is of type UserDetails. If
- * it is then the {@link net.sf.acegisecurity.UserDetails#getUsername()}
- * is returned. Otherwise Authentication.getPrincipal().toString() is
- * returned. Subclasses can override this method to provide a more
- * specific implementation.
- *
- * @param auth The Authentication in question
- * @return The principal to be used as the key against sessions
- */
- protected Object determineSessionPrincipal(Authentication auth) {
- if (auth.getPrincipal() instanceof UserDetails) {
- return ((UserDetails) auth.getPrincipal()).getUsername();
- } else {
- return auth.getPrincipal().toString();
- }
- }
-
- /**
- * Called by both the beforeAuthentication and afterAuthentication methods.
- * Anonymous requests as determined by the configured {@link
- * AuthenticationTrustResolver} are ignored. If the details are
- * WebAuthenticationDetails, get the sessionId and and the principal off
- * of the authentication using the {@link
- * #determineSessionPrincipal(net.sf.acegisecurity.Authentication)}
- * method. Uses the sessionId and principal to determine if the session
- * is new, and if the user is already at the maxSessions value. Subclasses
- * may override for more specific functionality
- *
- * @param request Authentication being evaluated
- * @throws ConcurrentLoginException If the session is new, and the user is
- * already at maxSessions
- */
- protected void enforceConcurrentLogins(Authentication request)
- throws ConcurrentLoginException {
- //If the max is less than 1, sessions are unlimited
- if (maxSessions < 1) {
- return;
- }
-
- //If it is an anonymous user, ignore them
- if (trustResolver.isAnonymous(request)) {
- return;
- }
-
- if (request.getDetails() instanceof WebAuthenticationDetails) {
- String sessionId = ((WebAuthenticationDetails) request.getDetails())
- .getSessionId();
-
- Object principal = determineSessionPrincipal(request);
-
- if (!isActiveSession(principal, sessionId)) {
- if (maxSessions == countSessions(principal)) {
- //Publish the event
- publishViolationEvent(request);
-
- //The user is AT their max, toss them out
- throw new ConcurrentLoginException(principal
- + " has reached the maximum concurrent logins");
- }
- }
- }
- }
-
- /**
- * Publish the even to the application context.
- * The default action is to publish a new {@link ConcurrentSessionViolationEvent}
- *
- * @param auth The authentication object that caused the violation
- */
- protected void publishViolationEvent(Authentication auth) {
- getApplicationContext().publishEvent(new ConcurrentSessionViolationEvent(auth));
- }
-
- /**
- * Remove the given sessionId from storage. Used by {@link
- * #onApplicationEvent(org.springframework.context.ApplicationEvent)} for
- * HttpSessionDestroyedEvent
- *
- * @param sessionId
- */
- protected void removeSession(String sessionId) {
- // find out which principal is associated with this sessionId
- Object associatedPrincipal = sessionsToPrincipals.get(sessionId);
-
- if (associatedPrincipal != null) {
- Set sessions = (Set) principalsToSessions.get(associatedPrincipal);
- sessions.remove(sessionId);
-
- if (sessions.isEmpty()) {
- principalsToSessions.remove(associatedPrincipal);
- }
-
- sessionsToPrincipals.remove(sessionId);
- }
- }
-}
diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionViolationEvent.java b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionViolationEvent.java
deleted file mode 100644
index dd55dc3bb0..0000000000
--- a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionViolationEvent.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sf.acegisecurity.providers;
-
-import net.sf.acegisecurity.Authentication;
-
-import org.springframework.context.ApplicationEvent;
-
-
-/**
- * Published by the ConcurrentSessionControllerImpl to notify the application
- * context that a user has attempted to login more than the maximum times
- * allowed by the {@link ConcurrentSessionControllerImpl#setMaxSessions(int)}
- *
- * @author Ray Krueger
- */
-public class ConcurrentSessionViolationEvent extends ApplicationEvent {
- //~ Constructors ===========================================================
-
- public ConcurrentSessionViolationEvent(Authentication auth) {
- super(auth);
- }
-
- //~ Methods ================================================================
-
- public Authentication getAuthentication() {
- return (Authentication) getSource();
- }
-}
diff --git a/core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java b/core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java
deleted file mode 100644
index e84e8b6f4c..0000000000
--- a/core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sf.acegisecurity.providers;
-
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.AuthenticationException;
-
-
-/**
- * Do nothing implementation of ConcurrentSessionController. This is the {@link ProviderManager} default
- *
- * @author Ray Krueger
- * @see ConcurrentSessionControllerImpl
- */
-public class NullConcurrentSessionController
- implements ConcurrentSessionController {
- //~ Methods ================================================================
-
- public void afterAuthentication(Authentication initialAuth,
- Authentication result) throws AuthenticationException {
- //Do nothing
- }
-
- public void beforeAuthentication(Authentication initialAuth)
- throws AuthenticationException {
- //Do nothing
- }
-}
diff --git a/core/src/main/java/org/acegisecurity/providers/ProviderManager.java b/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
index ef01b7445b..6dcf70bd8d 100644
--- a/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
+++ b/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
@@ -18,6 +18,8 @@ package net.sf.acegisecurity.providers;
import net.sf.acegisecurity.AbstractAuthenticationManager;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
+import net.sf.acegisecurity.concurrent.ConcurrentSessionController;
+import net.sf.acegisecurity.concurrent.NullConcurrentSessionController;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -146,7 +148,7 @@ public class ProviderManager extends AbstractAuthenticationManager
Class toTest = authentication.getClass();
- sessionController.beforeAuthentication(authentication);
+ sessionController.checkAuthenticationAllowed(authentication);
while (iter.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider) iter
@@ -159,7 +161,7 @@ public class ProviderManager extends AbstractAuthenticationManager
Authentication result = provider.authenticate(authentication);
if (result != null) {
- sessionController.afterAuthentication(authentication, result);
+ sessionController.registerSuccessfulAuthentication(result);
return result;
}
diff --git a/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java b/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java
new file mode 100644
index 0000000000..4ed6f2de97
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionControllerImplTests.java
@@ -0,0 +1,124 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.ui.WebAuthenticationDetails;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpSession;
+
+
+/**
+ * Tests {@link ConcurrentSessionControllerImpl}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ConcurrentSessionControllerImplTests extends TestCase {
+ //~ Methods ================================================================
+
+ public void testLifecycle() throws Exception {
+ // Build a test fixture
+ ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
+ SessionRegistry registry = new SessionRegistryImpl();
+ sc.setSessionRegistry(registry);
+
+ // Attempt to authenticate - it should be successful
+ Authentication auth = createAuthentication("bob", "1212");
+ sc.checkAuthenticationAllowed(auth);
+ sc.registerSuccessfulAuthentication(auth);
+
+ String sessionId1 = ((WebAuthenticationDetails) auth.getDetails())
+ .getSessionId();
+ assertFalse(registry.getSessionInformation(sessionId1).isExpired());
+
+ // Attempt to authenticate again - it should still be successful
+ sc.checkAuthenticationAllowed(auth);
+ sc.registerSuccessfulAuthentication(auth);
+
+ // Attempt to authenticate with a different session for same principal - should fail
+ sc.setExceptionIfMaximumExceeded(true);
+
+ Authentication auth2 = createAuthentication("bob", "1212");
+ assertFalse(registry.getSessionInformation(sessionId1).isExpired());
+
+ try {
+ sc.checkAuthenticationAllowed(auth2);
+ fail("Should have thrown ConcurrentLoginException");
+ } catch (ConcurrentLoginException expected) {
+ assertTrue(true);
+ }
+
+ // Attempt to authenticate with a different session for same principal - should expire first session
+ sc.setExceptionIfMaximumExceeded(false);
+
+ Authentication auth3 = createAuthentication("bob", "1212");
+ sc.checkAuthenticationAllowed(auth3);
+ sc.registerSuccessfulAuthentication(auth3);
+
+ String sessionId3 = ((WebAuthenticationDetails) auth3.getDetails())
+ .getSessionId();
+ assertTrue(registry.getSessionInformation(sessionId1).isExpired());
+ assertFalse(registry.getSessionInformation(sessionId3).isExpired());
+ }
+
+ public void testStartupDetectsInvalidMaximumSessions()
+ throws Exception {
+ ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
+ sc.setMaximumSessions(0);
+
+ try {
+ sc.afterPropertiesSet();
+ fail("Should have thrown IAE");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testStartupDetectsInvalidSessionRegistry()
+ throws Exception {
+ ConcurrentSessionControllerImpl sc = new ConcurrentSessionControllerImpl();
+ sc.setSessionRegistry(null);
+
+ try {
+ sc.afterPropertiesSet();
+ fail("Should have thrown IAE");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ private Authentication createAuthentication(String user, String password) {
+ UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+ password);
+ auth.setDetails(createWebDetails(auth));
+
+ return auth;
+ }
+
+ private WebAuthenticationDetails createWebDetails(Authentication auth) {
+ MockHttpSession session = new MockHttpSession();
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setSession(session);
+ request.setUserPrincipal(auth);
+
+ return new WebAuthenticationDetails(request);
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionFilterTests.java b/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionFilterTests.java
new file mode 100644
index 0000000000..79c66e0268
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/concurrent/ConcurrentSessionFilterTests.java
@@ -0,0 +1,173 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import junit.framework.TestCase;
+
+import org.springframework.mock.web.MockFilterConfig;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.mock.web.MockHttpSession;
+
+import java.io.IOException;
+
+import java.util.Date;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Tests {@link ConcurrentSessionFilter}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ConcurrentSessionFilterTests extends TestCase {
+ //~ Constructors ===========================================================
+
+ public ConcurrentSessionFilterTests() {
+ super();
+ }
+
+ public ConcurrentSessionFilterTests(String arg0) {
+ super(arg0);
+ }
+
+ //~ Methods ================================================================
+
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(ConcurrentSessionFilterTests.class);
+ }
+
+ public void testDetectsExpiredSessions() throws Exception {
+ // Setup our HTTP request
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpSession session = new MockHttpSession();
+ request.setSession(session);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockFilterConfig config = new MockFilterConfig(null);
+
+ // Setup our expectation that the filter chain will not be invoked, as we redirect to expiredUrl
+ MockFilterChain chain = new MockFilterChain(false);
+
+ // Setup our test fixture and registry to want this session to be expired
+ ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
+ SessionRegistry registry = new SessionRegistryImpl();
+ registry.registerNewSession(session.getId(), "principal");
+ registry.getSessionInformation(session.getId()).expireNow();
+ filter.setSessionRegistry(registry);
+ filter.setExpiredUrl("/expired.jsp");
+
+ // Test
+ executeFilterInContainerSimulator(config, filter, request, response,
+ chain);
+
+ assertEquals("/expired.jsp", response.getRedirectedUrl());
+ }
+
+ public void testDetectsMissingExpiredUrl() throws Exception {
+ ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
+ filter.setSessionRegistry(new SessionRegistryImpl());
+
+ try {
+ filter.afterPropertiesSet();
+ fail("Should have thrown IAE");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testDetectsMissingSessionRegistry() throws Exception {
+ ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
+ filter.setExpiredUrl("xcx");
+
+ try {
+ filter.afterPropertiesSet();
+ fail("Should have thrown IAE");
+ } catch (IllegalArgumentException expected) {
+ assertTrue(true);
+ }
+ }
+
+ public void testUpdatesLastRequestTime() throws Exception {
+ // Setup our HTTP request
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ MockHttpSession session = new MockHttpSession();
+ request.setSession(session);
+
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ MockFilterConfig config = new MockFilterConfig(null);
+
+ // Setup our expectation that the filter chain will be invoked, as our session hasn't expired
+ MockFilterChain chain = new MockFilterChain(true);
+
+ // Setup our test fixture
+ ConcurrentSessionFilter filter = new ConcurrentSessionFilter();
+ SessionRegistry registry = new SessionRegistryImpl();
+ registry.registerNewSession(session.getId(), "principal");
+
+ Date lastRequest = registry.getSessionInformation(session.getId())
+ .getLastRequest();
+ filter.setSessionRegistry(registry);
+ filter.setExpiredUrl("/expired.jsp");
+
+ Thread.sleep(1000);
+
+ // Test
+ executeFilterInContainerSimulator(config, filter, request, response,
+ chain);
+
+ assertTrue(registry.getSessionInformation(session.getId())
+ .getLastRequest().after(lastRequest));
+ }
+
+ private void executeFilterInContainerSimulator(FilterConfig filterConfig,
+ Filter filter, ServletRequest request, ServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ filter.init(filterConfig);
+ filter.doFilter(request, response, filterChain);
+ filter.destroy();
+ }
+
+ //~ Inner Classes ==========================================================
+
+ private class MockFilterChain implements FilterChain {
+ private boolean expectToProceed;
+
+ public MockFilterChain(boolean expectToProceed) {
+ this.expectToProceed = expectToProceed;
+ }
+
+ private MockFilterChain() {
+ super();
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response)
+ throws IOException, ServletException {
+ if (expectToProceed) {
+ assertTrue(true);
+ } else {
+ fail("Did not expect filter chain to proceed");
+ }
+ }
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/concurrent/SessionInformationTests.java b/core/src/test/java/org/acegisecurity/concurrent/SessionInformationTests.java
new file mode 100644
index 0000000000..31791f82bd
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/concurrent/SessionInformationTests.java
@@ -0,0 +1,49 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import junit.framework.TestCase;
+
+import java.util.Date;
+
+
+/**
+ * Tests {@link SessionInformation}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SessionInformationTests extends TestCase {
+ //~ Methods ================================================================
+
+ public void testObject() throws Exception {
+ Object principal = "Some principal object";
+ String sessionId = "1234567890";
+ Date currentDate = new Date();
+
+ SessionInformation info = new SessionInformation(principal, sessionId,
+ currentDate);
+ assertEquals(principal, info.getPrincipal());
+ assertEquals(sessionId, info.getSessionId());
+ assertEquals(currentDate, info.getLastRequest());
+
+ Thread.sleep(1000);
+
+ info.refreshLastRequest();
+
+ assertTrue(info.getLastRequest().after(currentDate));
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/concurrent/SessionRegistryImplTests.java b/core/src/test/java/org/acegisecurity/concurrent/SessionRegistryImplTests.java
new file mode 100644
index 0000000000..545c82c338
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/concurrent/SessionRegistryImplTests.java
@@ -0,0 +1,155 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.concurrent;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
+
+import org.springframework.mock.web.MockHttpSession;
+
+import java.util.Date;
+
+
+/**
+ * Tests {@link SessionRegistryImpl}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class SessionRegistryImplTests extends TestCase {
+ //~ Methods ================================================================
+
+ public void testEventPublishing() {
+ MockHttpSession httpSession = new MockHttpSession();
+ Object principal = "Some principal object";
+ String sessionId = httpSession.getId();
+ assertNotNull(sessionId);
+
+ SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId, principal);
+
+ // Deregister session via an ApplicationEvent
+ sessionRegistry.onApplicationEvent(new HttpSessionDestroyedEvent(
+ httpSession));
+
+ // Check attempts to retrieve cleared session return null
+ assertNull(sessionRegistry.getSessionInformation(sessionId));
+ }
+
+ public void testSessionInformationLifecycle() throws Exception {
+ Object principal = "Some principal object";
+ String sessionId = "1234567890";
+ SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId, principal);
+
+ // Retrieve existing session by session ID
+ Date currentDateTime = sessionRegistry.getSessionInformation(sessionId)
+ .getLastRequest();
+ assertEquals(principal,
+ sessionRegistry.getSessionInformation(sessionId).getPrincipal());
+ assertEquals(sessionId,
+ sessionRegistry.getSessionInformation(sessionId).getSessionId());
+ assertNotNull(sessionRegistry.getSessionInformation(sessionId)
+ .getLastRequest());
+
+ // Retrieve existing session by principal
+ assertEquals(1, sessionRegistry.getAllSessions(principal).length);
+
+ // Sleep to ensure SessionRegistryImpl will update time
+ Thread.sleep(1000);
+
+ // Update request date/time
+ sessionRegistry.refreshLastRequest(sessionId);
+
+ Date retrieved = sessionRegistry.getSessionInformation(sessionId)
+ .getLastRequest();
+ assertTrue(retrieved.after(currentDateTime));
+
+ // Check it retrieves correctly when looked up via principal
+ assertEquals(retrieved,
+ sessionRegistry.getAllSessions(principal)[0].getLastRequest());
+
+ // Clear session information
+ sessionRegistry.removeSessionInformation(sessionId);
+
+ // Check attempts to retrieve cleared session return null
+ assertNull(sessionRegistry.getSessionInformation(sessionId));
+ assertNull(sessionRegistry.getAllSessions(principal));
+ }
+
+ public void testTwoSessionsOnePrincipalHandling() throws Exception {
+ Object principal = "Some principal object";
+ String sessionId1 = "1234567890";
+ String sessionId2 = "9876543210";
+ SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId1, principal);
+ assertEquals(1, sessionRegistry.getAllSessions(principal).length);
+ assertEquals(sessionId1,
+ sessionRegistry.getAllSessions(principal)[0].getSessionId());
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId2, principal);
+ assertEquals(2, sessionRegistry.getAllSessions(principal).length);
+ assertEquals(sessionId2,
+ sessionRegistry.getAllSessions(principal)[1].getSessionId());
+
+ // Clear session information
+ sessionRegistry.removeSessionInformation(sessionId1);
+ assertEquals(1, sessionRegistry.getAllSessions(principal).length);
+ assertEquals(sessionId2,
+ sessionRegistry.getAllSessions(principal)[0].getSessionId());
+
+ // Clear final session
+ sessionRegistry.removeSessionInformation(sessionId2);
+ assertNull(sessionRegistry.getSessionInformation(sessionId2));
+ assertNull(sessionRegistry.getAllSessions(principal));
+ }
+
+ public void testTwoSessionsOnePrincipalExpiring() throws Exception {
+ Object principal = "Some principal object";
+ String sessionId1 = "1234567890";
+ String sessionId2 = "9876543210";
+ SessionRegistryImpl sessionRegistry = new SessionRegistryImpl();
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId1, principal);
+ assertEquals(1, sessionRegistry.getAllSessions(principal).length);
+ assertEquals(sessionId1,
+ sessionRegistry.getAllSessions(principal)[0].getSessionId());
+
+ // Register new Session
+ sessionRegistry.registerNewSession(sessionId2, principal);
+ assertEquals(2, sessionRegistry.getAllSessions(principal).length);
+ assertEquals(sessionId2,
+ sessionRegistry.getAllSessions(principal)[1].getSessionId());
+
+ // Expire one session
+ SessionInformation session = sessionRegistry.getSessionInformation(sessionId2);
+ session.expireNow();
+
+ // Check retrieval still correct
+ assertTrue(sessionRegistry.getSessionInformation(sessionId2).isExpired());
+ assertFalse(sessionRegistry.getSessionInformation(sessionId1).isExpired());
+ }
+
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java b/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java
deleted file mode 100644
index 3bb9d64af4..0000000000
--- a/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/* Copyright 2004, 2005 Acegi Technology Pty Limited
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package net.sf.acegisecurity.providers;
-
-import junit.framework.TestCase;
-
-
-import net.sf.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
-import net.sf.acegisecurity.providers.dao.User;
-import net.sf.acegisecurity.ui.WebAuthenticationDetails;
-import net.sf.acegisecurity.ui.session.HttpSessionCreatedEvent;
-import net.sf.acegisecurity.ui.session.HttpSessionDestroyedEvent;
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.GrantedAuthorityImpl;
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
-import net.sf.acegisecurity.MockApplicationContext;
-
-import org.springframework.context.ApplicationListener;
-import org.springframework.mock.web.MockHttpSession;
-import org.springframework.mock.web.MockHttpServletRequest;
-
-import java.security.Principal;
-
-
-/**
- * Tests for {@link ConcurrentSessionControllerImpl}
- *
- * @author Ray Krueger
- * @author Luke Taylor
- * @version $Id$
- */
-public class ConcurrentSessionControllerImplTests extends TestCase {
- //~ Instance fields ========================================================
-
- ConcurrentSessionControllerImpl target;
-
- //~ Methods ================================================================
-
- public void testAnonymous() throws Exception {
- AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("blah",
- "anon",
- new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ANON")});
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
-
- public void testBumpCoverage() throws Exception {
- target.onApplicationEvent(new HttpSessionCreatedEvent(
- new MockHttpSession()));
- }
-
- public void testEnforcementKnownGood() throws Exception {
- Authentication auth = createAuthentication("user", "password");
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
-
- public void testEnforcementMultipleSessions() throws Exception {
- target.setMaxSessions(5);
-
- Authentication auth = null;
-
- for (int i = 0; i < 5; i++) { // creates 5 sessions
- auth = createAuthentication("user", "password");
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
-
- try {
- auth = createAuthentication("user", "password");
- target.beforeAuthentication(auth);
- fail(
- "Only allowed 5 sessions, this should have thrown a ConcurrentLoginException");
- } catch (ConcurrentLoginException e) {
- assertTrue(e.getMessage().startsWith(auth.getPrincipal().toString()));
- }
- }
-
- public void testEnforcementSingleSession() throws Exception {
- target.setMaxSessions(1);
-
- Authentication auth = createAuthentication("user", "password");
-
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
-
- try {
- target.beforeAuthentication(createAuthentication("user", "password"));
- fail(
- "Only allowed 1 session, this should have thrown a ConcurrentLoginException");
- } catch (ConcurrentLoginException e) {}
- }
-
- public void testEnforcementUnlimitedSameSession() throws Exception {
- target.setMaxSessions(1);
- MockHttpSession session = new MockHttpSession(); // all requests are within this session
-
- for (int i = 0; i < 100; i++) {
- UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("user",
- "password");
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setSession(session);
- request.setUserPrincipal(auth);
- auth.setDetails(new WebAuthenticationDetails(request));
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
- }
-
- public void testEnforcementUnlimitedSessions() throws Exception {
- target.setMaxSessions(0);
-
- for (int i = 0; i < 100; i++) {
- Authentication auth = createAuthentication("user", "password");
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
- }
-
- public void testEventHandler() throws Exception {
- target.setMaxSessions(1);
-
- UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("user",
- "password");
- MockHttpSession session = new MockHttpSession();
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setSession(session);
- request.setUserPrincipal(auth);
- auth.setDetails(new WebAuthenticationDetails(request));
-
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
-
- target.onApplicationEvent(new HttpSessionDestroyedEvent(session));
-
- Authentication different = createAuthentication("user", "password");
- target.beforeAuthentication(different);
- target.afterAuthentication(different, different);
- }
-
- public void testEventObject() {
- UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("user",
- "password");
- ConcurrentSessionViolationEvent ev = new ConcurrentSessionViolationEvent(token);
- assertEquals("The token that went in should be the token that comes out",
- token, ev.getAuthentication());
- }
-
- public void testImplementsApplicationListener() throws Exception {
- assertTrue("This class must implement ApplicationListener, and at one point it didn't.",
- target instanceof ApplicationListener);
- }
-
- public void testNonWebDetails() throws Exception {
- UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("asdf",
- "asdf");
- auth.setDetails("Hi there");
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
- }
-
- public void testPrincipals() throws Exception {
- target.setMaxSessions(1);
-
- final UserDetails user = new User("user", "password", true, true, true,
- true, new GrantedAuthority[0]);
- final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
- "password", user.getAuthorities());
- auth.setDetails(createWebDetails(auth));
-
- target.beforeAuthentication(auth);
- target.afterAuthentication(auth, auth);
-
- try {
- UsernamePasswordAuthenticationToken otherAuth = new UsernamePasswordAuthenticationToken(new Principal() {
- public String getName() {
- return "user";
- }
-
- public String toString() {
- return getName();
- }
- }, "password");
-
- otherAuth.setDetails(createWebDetails(otherAuth));
- target.beforeAuthentication(otherAuth);
- fail(
- "Same principal, different principal type, different session should have thrown ConcurrentLoginException");
- } catch (ConcurrentLoginException e) {}
- }
-
- public void testSetMax() throws Exception {
- target.setMaxSessions(1);
- assertEquals(1, target.getMaxSessions());
-
- target.setMaxSessions(2);
- assertEquals(2, target.getMaxSessions());
- }
-
- public void testSetTrustManager() throws Exception {
- assertNotNull("There is supposed to be a default AuthenticationTrustResolver",
- target.getTrustResolver());
-
- AuthenticationTrustResolverImpl impl = new AuthenticationTrustResolverImpl();
- target.setTrustResolver(impl);
- assertEquals(impl, target.getTrustResolver());
- }
-
- public void testUtilityMethods() throws Exception {
- Object key = new Object();
-
- target.addSession(key, "1");
- target.addSession(key, "2");
- target.addSession(key, "3");
-
- target.removeSession("2");
-
- assertFalse(target.isActiveSession(key, "2"));
- assertTrue(target.isActiveSession(key, "1"));
- assertTrue(target.isActiveSession(key, "3"));
-
- assertNull(target.sessionsToPrincipals.get("2"));
-
- assertEquals(2, target.countSessions(key));
- target.addSession(key, "2");
- assertEquals(3, target.countSessions(key));
-
- target.addSession(key, "2");
- target.addSession(key, "2");
- assertEquals(3, target.countSessions(key));
-
- assertTrue(target.isActiveSession(key, "1"));
- assertTrue(target.isActiveSession(key, "2"));
- assertTrue(target.isActiveSession(key, "3"));
-
- assertFalse(target.isActiveSession(key, "nope"));
-
- assertFalse(target.isActiveSession(new Object(), "1"));
- assertFalse(target.isActiveSession(new Object(), "1"));
-
- target.removeSession("nothing to see here");
- }
-
- protected void setUp() throws Exception {
- target = new ConcurrentSessionControllerImpl();
- target.setApplicationContext(MockApplicationContext.getContext());
- }
-
- private Authentication createAuthentication(String user, String password) {
- UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
- password);
- auth.setDetails(createWebDetails(auth));
-
- return auth;
- }
-
- private WebAuthenticationDetails createWebDetails(Authentication auth) {
- MockHttpSession session = new MockHttpSession();
- MockHttpServletRequest request = new MockHttpServletRequest();
- request.setSession(session);
- request.setUserPrincipal(auth);
-
- return new WebAuthenticationDetails(request);
- }
-}
diff --git a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
index 2b7fcdf13f..4d535d4f2b 100644
--- a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
+++ b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
@@ -18,6 +18,8 @@ package net.sf.acegisecurity.providers;
import junit.framework.TestCase;
import net.sf.acegisecurity.*;
+import net.sf.acegisecurity.concurrent.ConcurrentSessionControllerImpl;
+import net.sf.acegisecurity.concurrent.NullConcurrentSessionController;
import java.util.List;
import java.util.Vector;