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 }