1   /*
2    * Copyright (c) 2002-2005 the original author or authors.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    * 
8    * http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.springframework.richclient.security;
17  
18  import junit.framework.TestCase;
19  
20  import org.springframework.context.ApplicationEvent;
21  import org.springframework.context.ApplicationListener;
22  import org.springframework.context.support.ClassPathXmlApplicationContext;
23  import org.springframework.richclient.application.Application;
24  import org.springframework.richclient.application.ApplicationServicesLocator;
25  import org.springframework.richclient.application.config.DefaultApplicationLifecycleAdvisor;
26  import org.springframework.richclient.security.support.DefaultApplicationSecurityManager;
27  import org.springframework.security.Authentication;
28  import org.springframework.security.AuthenticationManager;
29  import org.springframework.security.BadCredentialsException;
30  import org.springframework.security.LockedException;
31  import org.springframework.security.SpringSecurityException;
32  
33  /**
34   * Test cases for the DefaultApplicationSecurityManager implementation.
35   * 
36   * @author Larry Streepy
37   * 
38   */
39  public class DefaultApplicationSecurityManagerTests extends TestCase {
40  
41      private ClassPathXmlApplicationContext ac;
42      private EventCounter eventCounter;
43  
44      /**
45       * Configure an Application instance with the specified context file.
46       * @param ctxFileName Name of context configuration file to read, may be null
47       */
48      private void prepareApplication(String ctxFileName) {
49          Application.load( null );
50          ApplicationServicesLocator.load(null);
51          Application app = new Application( new DefaultApplicationLifecycleAdvisor() );
52  
53          if( ctxFileName != null ) {
54              ac = new ClassPathXmlApplicationContext( "org/springframework/richclient/security/" + ctxFileName );
55              app.setApplicationContext( ac );
56          }
57      }
58  
59      public void testConfiguration() {
60          prepareApplication( "security-test-ctx.xml" );
61  
62          Object asm = ac.getBean( "applicationSecurityManager" );
63          Object am = ac.getBean( "authenticationManager" );
64  
65          assertTrue( "securityManager must implement ApplicationSecurityManager",
66              asm instanceof ApplicationSecurityManager );
67          assertTrue( "securityManager must be instance of DefaultApplicationSecurityManager",
68              asm instanceof DefaultApplicationSecurityManager );
69          assertTrue( "authenticationManager must implement AuthenticationManager", am instanceof AuthenticationManager );
70          assertTrue( "authenticationManager must be instance of TestAuthenticationManager",
71              am instanceof TestAuthenticationManager );
72          assertEquals( asm, ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class) );
73      }
74  
75      public void testSecurityEvents() {
76          prepareApplication( "security-test-ctx.xml" );
77          eventCounter = (EventCounter) ac.getBean( "eventCounter" );
78  
79          ApplicationSecurityManager asm = (ApplicationSecurityManager)ApplicationServicesLocator.services().getService(ApplicationSecurityManager.class);
80          eventCounter.resetCounters();
81          asm.doLogin( TestAuthenticationManager.VALID_USER1 );
82          testCounters( 1, 0, 1, 0 );
83          assertTrue( "User should be logged in now", asm.isUserLoggedIn() );
84          assertEquals( "Authentiation token should be == VALID_USER1", asm.getAuthentication(),
85              TestAuthenticationManager.VALID_USER1 );
86  
87          // Test various failed logins, current login shouldn't be affected
88          doOneFailed( TestAuthenticationManager.BAD_CREDENTIALS, BadCredentialsException.class );
89          doOneFailed( TestAuthenticationManager.LOCKED, LockedException.class );
90  
91          // Logout - generates an Authentication event and a Logout event
92          eventCounter.resetCounters();
93          asm.doLogout();
94          testCounters( 1, 0, 0, 1 );
95          assertNull( "Authentication token should now be null", asm.getAuthentication() );
96      }
97  
98      public void testUserInRole() {
99          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 }