001 /*
002 * Copyright (c) 2002-2005 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package org.springframework.richclient.security;
017
018 import junit.framework.TestCase;
019
020 import org.springframework.context.ApplicationEvent;
021 import org.springframework.context.ApplicationListener;
022 import org.springframework.context.support.ClassPathXmlApplicationContext;
023 import org.springframework.richclient.application.Application;
024 import org.springframework.richclient.application.ApplicationServicesLocator;
025 import org.springframework.richclient.application.config.DefaultApplicationLifecycleAdvisor;
026 import org.springframework.richclient.security.support.DefaultApplicationSecurityManager;
027 import org.springframework.security.Authentication;
028 import org.springframework.security.AuthenticationManager;
029 import org.springframework.security.BadCredentialsException;
030 import org.springframework.security.LockedException;
031 import org.springframework.security.SpringSecurityException;
032
033 /**
034 * Test cases for the DefaultApplicationSecurityManager implementation.
035 *
036 * @author Larry Streepy
037 *
038 */
039 public class DefaultApplicationSecurityManagerTests extends TestCase {
040
041 private ClassPathXmlApplicationContext ac;
042 private EventCounter eventCounter;
043
044 /**
045 * Configure an Application instance with the specified context file.
046 * @param ctxFileName Name of context configuration file to read, may be null
047 */
048 private void prepareApplication(String ctxFileName) {
049 Application.load( null );
050 ApplicationServicesLocator.load(null);
051 Application app = new Application( new DefaultApplicationLifecycleAdvisor() );
052
053 if( ctxFileName != null ) {
054 ac = new ClassPathXmlApplicationContext( "org/springframework/richclient/security/" + ctxFileName );
055 app.setApplicationContext( ac );
056 }
057 }
058
059 public void testConfiguration() {
060 prepareApplication( "security-test-ctx.xml" );
061
062 Object asm = ac.getBean( "applicationSecurityManager" );
063 Object am = ac.getBean( "authenticationManager" );
064
065 assertTrue( "securityManager must implement ApplicationSecurityManager",
066 asm instanceof ApplicationSecurityManager );
067 assertTrue( "securityManager must be instance of DefaultApplicationSecurityManager",
068 asm instanceof DefaultApplicationSecurityManager );
069 assertTrue( "authenticationManager must implement AuthenticationManager", am instanceof AuthenticationManager );
070 assertTrue( "authenticationManager must be instance of TestAuthenticationManager",
071 am instanceof TestAuthenticationManager );
072 assertEquals( asm, ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class) );
073 }
074
075 public void testSecurityEvents() {
076 prepareApplication( "security-test-ctx.xml" );
077 eventCounter = (EventCounter) ac.getBean( "eventCounter" );
078
079 ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
080 eventCounter.resetCounters();
081 asm.doLogin( TestAuthenticationManager.VALID_USER1 );
082 testCounters( 1, 0, 1, 0 );
083 assertTrue( "User should be logged in now", asm.isUserLoggedIn() );
084 assertEquals( "Authentiation token should be == VALID_USER1", asm.getAuthentication(),
085 TestAuthenticationManager.VALID_USER1 );
086
087 // Test various failed logins, current login shouldn't be affected
088 doOneFailed( TestAuthenticationManager.BAD_CREDENTIALS, BadCredentialsException.class );
089 doOneFailed( TestAuthenticationManager.LOCKED, LockedException.class );
090
091 // Logout - generates an Authentication event and a Logout event
092 eventCounter.resetCounters();
093 asm.doLogout();
094 testCounters( 1, 0, 0, 1 );
095 assertNull( "Authentication token should now be null", asm.getAuthentication() );
096 }
097
098 public void testUserInRole() {
099 prepareApplication( "security-test-ctx.xml" );
100 ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
101 asm.doLogin( TestAuthenticationManager.VALID_USER1 );
102
103 assertTrue( "User should be in role ROLE_EXPECTED", asm.isUserInRole( TestAuthenticationManager.ROLE_EXPECTED ) );
104 assertFalse( "User should not be in role ROLE_UNEXPECTED", asm.isUserInRole( "ROLE_UNEXPECTED" ) );
105 }
106
107 public void testLoginAfterLogin() {
108 prepareApplication( "security-test-ctx.xml" );
109 eventCounter = (EventCounter) ac.getBean( "eventCounter" );
110
111 ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
112
113 asm.doLogout(); // Start with no one logged in
114 asm.doLogin( TestAuthenticationManager.VALID_USER1 );
115
116 eventCounter.resetCounters();
117 asm.doLogin( TestAuthenticationManager.VALID_USER2 );
118
119 testCounters( 1, 0, 1, 0 );
120 assertTrue( "User should be logged in now", asm.isUserLoggedIn() );
121 assertEquals( "Authentiation token should be == VALID_USER2", asm.getAuthentication(),
122 TestAuthenticationManager.VALID_USER2 );
123 }
124
125 public void testAutoConfigurationOnNew() {
126 // Ensure that the DefaultApplicationSecurityManager will properly
127 // auto-configure when it is created with "new" instead of through an
128 // application context.
129
130 prepareApplication( "security-test-autoconfig-ctx.xml" );
131 ApplicationSecurityManager asm = new DefaultApplicationSecurityManager( true );
132
133 // Ensure it's the right one
134 Object am = ac.getBean( "authenticationManager" );
135 assertEquals( "Wrong authentication manager configured", am, asm.getAuthenticationManager() );
136 }
137
138 public void testAutoConfigurationFailsWithoutContext() {
139 // Test that the auto-configuration fails when there is no context
140 prepareApplication( null ); // No context
141 try {
142 ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
143 fail( "Shouldn't be able to auto-configure without context" );
144 } catch( Exception e ) {
145 // expected
146 }
147 }
148
149 public void testAutoConfigurationFromServices() {
150 // Test that the application services will provide a properly auto-configured
151 // security manager.
152
153 prepareApplication( "security-test-autoconfig-ctx.xml" );
154 ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
155
156 // Ensure it's the right one
157 Object am = ac.getBean( "authenticationManager" );
158 assertEquals( "Wrong authentication manager configured", am, asm.getAuthenticationManager() );
159 }
160
161 /**
162 * Do one failed authentication invocation and test results.
163 * @param authentication token to use
164 * @param exceptionType Type of exception that should be thrown
165 */
166 private void doOneFailed(Authentication authentication, Class exceptionType) {
167 ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
168 Authentication current = asm.getAuthentication();
169
170 eventCounter.resetCounters();
171 try {
172 asm.doLogin( authentication );
173 fail( exceptionType.getName() + " should have been thrown" );
174 } catch( SpringSecurityException e ) {
175 // We expect an exception
176 assertTrue( "Wrong exception thrown; expecting: " + exceptionType.getName(), exceptionType
177 .isAssignableFrom( e.getClass() ) );
178 testCounters( 0, 1, 0, 0 );
179 assertTrue( "User should still be logged in now", asm.isUserLoggedIn() );
180 // Shouldn't have changed
181 assertEquals( "Authentiation token should not have changed", asm.getAuthentication(), current );
182 }
183 }
184
185 /**
186 * Test the event counters and ensure they all match.
187 */
188 private void testCounters(int authCount, int authFailedCount, int loginCount, int logoutCount) {
189 assertEquals( "AuthenticationEventCount wrong", authCount, eventCounter.authEventCount );
190 assertEquals( "AuthenticationFailedEventCount wrong", authFailedCount, eventCounter.authFailedEventCount );
191 assertEquals( "LoginEventCount wrong", loginCount, eventCounter.loginEventCount );
192 assertEquals( "LogoutEventCount wrong", logoutCount, eventCounter.logoutEventCount );
193 }
194
195 /**
196 * Class to count interesting security lifecycle events.
197 */
198 public static class EventCounter implements ApplicationListener {
199 public int authEventCount = 0;
200 public int authFailedEventCount = 0;
201 public int loginEventCount = 0;
202 public int logoutEventCount = 0;
203
204 public EventCounter() {
205 }
206
207 public void onApplicationEvent(ApplicationEvent event) {
208 if( event instanceof AuthenticationEvent ) {
209 authEventCount += 1;
210 } else if( event instanceof AuthenticationFailedEvent ) {
211 authFailedEventCount += 1;
212 } else if( event instanceof LoginEvent ) {
213 loginEventCount += 1;
214 } else if( event instanceof LogoutEvent ) {
215 logoutEventCount += 1;
216 }
217 }
218
219 public void resetCounters() {
220 authEventCount = authFailedEventCount = loginEventCount = logoutEventCount = 0;
221 }
222 }
223 }