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    }