diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java b/core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java
new file mode 100644
index 0000000000..f7a2381616
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/ConcurrentLoginException.java
@@ -0,0 +1,33 @@
+/* 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.AuthenticationException;
+
+
+/**
+ * Thrown by the ConcurrentSessionController when the number of sessions
+ * allowed is attempting to be exceeded.
+ *
+ * @author Ray Krueger
+ */
+public class ConcurrentLoginException extends AuthenticationException {
+ //~ Constructors ===========================================================
+
+ public ConcurrentLoginException(String msg) {
+ super(msg);
+ }
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java
new file mode 100644
index 0000000000..0bdcd86e7c
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionController.java
@@ -0,0 +1,36 @@
+/* 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;
+
+
+/**
+ * See: {@link ConcurrentSessionControllerImpl}
+ *
+ * @author Ray Krueger
+ * @see ConcurrentSessionControllerImpl
+ */
+public interface ConcurrentSessionController {
+ //~ Methods ================================================================
+
+ void afterAuthentication(Authentication initialAuth, Authentication result)
+ throws AuthenticationException;
+
+ void beforeAuthentication(Authentication initialAuth)
+ throws AuthenticationException;
+}
diff --git a/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java
new file mode 100644
index 0000000000..a56a1e20bf
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/ConcurrentSessionControllerImpl.java
@@ -0,0 +1,290 @@
+/* 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.context.ApplicationEvent;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * 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 {
+ //~ Instance fields ========================================================
+
+ protected Map principalsToSessions = new HashMap();
+ protected Map sessionsToPrincipals = new HashMap();
+ protected Set sessionSet = new HashSet();
+ private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
+ private int maxSessions = 1;
+
+ //~ Methods ================================================================
+
+ /**
+ * 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
+ *
+ * @see #determineSessionPrincipal(net.sf.acegisecurity.Authentication)
+ */
+ public void afterAuthentication(Authentication request,
+ Authentication response) {
+ 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 has already met the {@link
+ * #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)) {
+ //The user is AT their max, toss them out
+ throw new ConcurrentLoginException(principal
+ + " has reached the maximum concurrent logins");
+ }
+ }
+ }
+ }
+
+ /**
+ * 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/NullConcurrentSessionController.java b/core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java
new file mode 100644
index 0000000000..e84e8b6f4c
--- /dev/null
+++ b/core/src/main/java/org/acegisecurity/providers/NullConcurrentSessionController.java
@@ -0,0 +1,41 @@
+/* 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 f1e9b75472..ef01b7445b 100644
--- a/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
+++ b/core/src/main/java/org/acegisecurity/providers/ProviderManager.java
@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* 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.
@@ -30,11 +30,16 @@ import java.util.List;
/**
* Iterates an {@link Authentication} request through a list of {@link
- * AuthenticationProvider}s.
+ * AuthenticationProvider}s. Can optionally be configured with a {@link
+ * ConcurrentSessionController} to limit the number of sessions a user can
+ * have.
*
* @author Ben Alex
* @author Wesley Hall
+ * @author Ray Krueger
* @version $Id$
+ *
+ * @see ConcurrentSessionController
*/
public class ProviderManager extends AbstractAuthenticationManager
implements InitializingBean {
@@ -44,6 +49,7 @@ public class ProviderManager extends AbstractAuthenticationManager
//~ Instance fields ========================================================
+ private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
private List providers;
//~ Methods ================================================================
@@ -82,6 +88,29 @@ public class ProviderManager extends AbstractAuthenticationManager
return this.providers;
}
+ /**
+ * Set the {@link ConcurrentSessionController} to be used for limiting
+ * user's sessions. The {@link NullConcurrentSessionController} is used
+ * by default
+ *
+ * @param sessionController {@link ConcurrentSessionController}
+ */
+ public void setSessionController(
+ ConcurrentSessionController sessionController) {
+ this.sessionController = sessionController;
+ }
+
+ /**
+ * The configured {@link ConcurrentSessionController} is returned or the
+ * {@link NullConcurrentSessionController} if a specific one has not been
+ * set.
+ *
+ * @return{@link ConcurrentSessionController} instance
+ */
+ public ConcurrentSessionController getSessionController() {
+ return sessionController;
+ }
+
public void afterPropertiesSet() throws Exception {
checkIfValidList(this.providers);
}
@@ -117,6 +146,8 @@ public class ProviderManager extends AbstractAuthenticationManager
Class toTest = authentication.getClass();
+ sessionController.beforeAuthentication(authentication);
+
while (iter.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider) iter
.next();
@@ -128,6 +159,8 @@ public class ProviderManager extends AbstractAuthenticationManager
Authentication result = provider.authenticate(authentication);
if (result != null) {
+ sessionController.afterAuthentication(authentication, result);
+
return result;
}
}
diff --git a/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java b/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java
new file mode 100644
index 0000000000..0ffba888e4
--- /dev/null
+++ b/core/src/test/java/org/acegisecurity/providers/ConcurrentSessionControllerImplTests.java
@@ -0,0 +1,244 @@
+/* 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.*;
+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 java.security.Principal;
+
+
+/**
+ * Tests for {@link ConcurrentSessionControllerImpl}
+ *
+ * @author Ray Krueger
+ */
+public class ConcurrentSessionControllerImplTests extends TestCase {
+ //~ Instance fields ========================================================
+
+ ConcurrentSessionControllerImpl target = new ConcurrentSessionControllerImpl();
+
+ //~ Methods ================================================================
+
+ public void testBumpCoverage() throws Exception {
+ target.onApplicationEvent(new HttpSessionCreatedEvent(new MockHttpSession()));
+ }
+
+ public void testEnforcementKnownGood() throws Exception {
+ Authentication auth = createAuthentication("user", "password", "session");
+ 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++) {
+ auth = createAuthentication("user", "password", String.valueOf(i));
+ target.beforeAuthentication(auth);
+ target.afterAuthentication(auth, auth);
+ }
+
+ try {
+ auth = createAuthentication("user", "password", "lastsession");
+ 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",
+ "session1");
+
+ target.beforeAuthentication(auth);
+ target.afterAuthentication(auth, auth);
+
+ try {
+ target.beforeAuthentication(createAuthentication("user",
+ "password", "session2"));
+ fail("Only allowed 1 session, this should have thrown a ConcurrentLoginException");
+ } catch (ConcurrentLoginException e) {
+ }
+ }
+
+ public void testEnforcementUnlimitedSameSession() throws Exception {
+ target.setMaxSessions(1);
+
+ for (int i = 0; i < 100; i++) {
+ Authentication auth = createAuthentication("user", "password",
+ "samesession");
+ 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",
+ String.valueOf(i));
+ 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(auth,
+ session);
+ auth.setDetails(new WebAuthenticationDetails(request));
+
+ target.beforeAuthentication(auth);
+ target.afterAuthentication(auth, auth);
+
+ target.onApplicationEvent(new HttpSessionDestroyedEvent(session));
+
+ Authentication different = createAuthentication("user", "password",
+ "differentsession");
+ target.beforeAuthentication(different);
+ target.afterAuthentication(different, different);
+ }
+
+ 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,
+ new GrantedAuthority[0]);
+ final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+ "password", user.getAuthorities());
+ auth.setDetails(createWebDetails(auth, "session1"));
+
+ 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, "session2"));
+ 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");
+ }
+
+ private Authentication createAuthentication(String user, String password,
+ String sessionId) {
+ UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(user,
+ password);
+ auth.setDetails(createWebDetails(auth, sessionId));
+
+ return auth;
+ }
+
+ private WebAuthenticationDetails createWebDetails(Authentication auth,
+ String sessionId) {
+ MockHttpSession session = new MockHttpSession(sessionId);
+ MockHttpServletRequest request = new MockHttpServletRequest(auth,
+ session);
+
+ return new WebAuthenticationDetails(request);
+ }
+
+ public void testAnonymous() throws Exception {
+ AnonymousAuthenticationToken auth = new AnonymousAuthenticationToken("blah", "anon", new GrantedAuthority[]{new GrantedAuthorityImpl("ROLE_ANON")});
+ target.beforeAuthentication(auth);
+ target.afterAuthentication(auth, auth);
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
index a8c8a119af..2b7fcdf13f 100644
--- a/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
+++ b/core/src/test/java/org/acegisecurity/providers/ProviderManagerTests.java
@@ -1,4 +1,4 @@
-/* Copyright 2004 Acegi Technology Pty Limited
+/* 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.
@@ -17,11 +17,7 @@ package net.sf.acegisecurity.providers;
import junit.framework.TestCase;
-import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.AuthenticationException;
-import net.sf.acegisecurity.AuthenticationServiceException;
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.GrantedAuthorityImpl;
+import net.sf.acegisecurity.*;
import java.util.List;
import java.util.Vector;
@@ -110,6 +106,19 @@ public class ProviderManagerTests extends TestCase {
assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
}
+ public void testConcurrentSessionControllerConfiguration()
+ throws Exception {
+ ProviderManager target = new ProviderManager();
+
+ //The NullConcurrentSessionController should be the default
+ assertNotNull(target.getSessionController());
+ assertTrue(target.getSessionController() instanceof NullConcurrentSessionController);
+
+ ConcurrentSessionControllerImpl impl = new ConcurrentSessionControllerImpl();
+ target.setSessionController(impl);
+ assertEquals(impl, target.getSessionController());
+ }
+
public void testStartupFailsIfProviderListDoesNotContainingProviders()
throws Exception {
List providers = new Vector();
diff --git a/doc/xdocs/upgrade/upgrade-070-080.html b/doc/xdocs/upgrade/upgrade-070-080.html
index 9e403fe189..d51d7a4650 100644
--- a/doc/xdocs/upgrade/upgrade-070-080.html
+++ b/doc/xdocs/upgrade/upgrade-070-080.html
@@ -26,7 +26,11 @@ applications:
SecureContext (and its implementation). These classes were moved as part of refactorings aimed at
improving the simplicity of the project's design.
-