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 org.springframework.richclient.command.ActionCommand; 019 import org.springframework.richclient.command.support.ApplicationWindowAwareCommand; 020 import org.springframework.richclient.dialog.ApplicationDialog; 021 import org.springframework.richclient.dialog.CompositeDialogPage; 022 import org.springframework.richclient.dialog.TabbedDialogPage; 023 import org.springframework.richclient.dialog.TitledPageApplicationDialog; 024 import org.springframework.security.Authentication; 025 026 /** 027 * Provides a login interface to the user. 028 * <P> 029 * Presents a dialog to the user to collect login credentials. It then invokes the 030 * {@link ApplicationSecurityManager#doLogin} method to validate the credentials. The 031 * ApplicationSecurityManager is responsible for updating the security context and firing 032 * appropriate security events. 033 * <p> 034 * The default user name can be specified as a configuration parameter. This is useful 035 * when combined with 036 * {@link org.springframework.beans.factory.config.PropertyPlaceholderConfigurer} to 037 * install the current (system) username. 038 * <p> 039 * If the login is unsuccesful, a message is presented to the user and they are offered 040 * another chance to login. 041 * <p> 042 * The <code>closeOnCancel</code> property controls what happens if the user cancels the 043 * login dialog. If closeOnCancel is true (the default), if there is no valid 044 * authentication in place (from a previous login) then the application is closed. If it 045 * is false or an authentication token is available, then no action is taken other than 046 * closing the dialog. 047 * <p> 048 * The <code>clearPasswordOnFailure</code> controls the handling of the password field 049 * after a login failure. If clearPasswordOnFailure is <code>true</code> (the default), 050 * then the password field will be cleared after the failure is reported. 051 * <p> 052 * A typical configuration for this component might look like this: 053 * 054 * <pre> 055 * <bean id="placeholderConfigurer" 056 * class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/> 057 * 058 * <bean id="loginCommand" 059 * class="org.springframework.richclient.security.LoginCommand"> 060 * <property name="displaySuccess" value="false"/> 061 * <property name="defaultUserName" value="${user.name}"/> 062 * </bean> 063 * </pre> 064 * 065 * @author Ben Alex 066 * @author Larry Streepy 067 * 068 * @see LoginForm 069 * @see LoginDetails 070 * @see ApplicationSecurityManager 071 */ 072 public class LoginCommand extends ApplicationWindowAwareCommand { 073 private static final String ID = "loginCommand"; 074 075 private boolean displaySuccessMessage = true; 076 077 private boolean closeOnCancel = true; 078 079 private boolean clearPasswordOnFailure = true; 080 081 private String defaultUserName = null; 082 083 private ApplicationDialog dialog = null; 084 085 /** 086 * Constructor. 087 */ 088 public LoginCommand() { 089 super( ID ); 090 } 091 092 /** 093 * Indicates whether an information message is displayed to the user upon successful 094 * authentication. Defaults to true. 095 * 096 * @param displaySuccessMessage displays an information message upon successful login if 097 * true, otherwise false 098 */ 099 public void setDisplaySuccess(boolean displaySuccessMessage) { 100 this.displaySuccessMessage = displaySuccessMessage; 101 } 102 103 /** 104 * Execute the login command. Display the dialog and attempt authentication. 105 */ 106 protected void doExecuteCommand() { 107 CompositeDialogPage tabbedPage = new TabbedDialogPage( "loginForm" ); 108 109 final LoginForm loginForm = createLoginForm(); 110 111 tabbedPage.addForm( loginForm ); 112 113 if( getDefaultUserName() != null ) { 114 loginForm.setUserName( getDefaultUserName() ); 115 } 116 117 dialog = new TitledPageApplicationDialog( tabbedPage ) { 118 protected boolean onFinish() { 119 loginForm.commit(); 120 Authentication authentication = loginForm.getAuthentication(); 121 122 // Hand this token to the security manager to actually attempt the login 123 ApplicationSecurityManager sm = (ApplicationSecurityManager)getService(ApplicationSecurityManager.class); 124 try { 125 sm.doLogin( authentication ); 126 postLogin(); 127 return true; 128 } finally { 129 if( isClearPasswordOnFailure() ) { 130 loginForm.setPassword(""); 131 } 132 loginForm.requestFocusInWindow(); 133 } 134 } 135 136 protected void onCancel() { 137 super.onCancel(); // Close the dialog 138 139 // Now exit if configured 140 if( isCloseOnCancel() ) { 141 ApplicationSecurityManager sm = (ApplicationSecurityManager)getService(ApplicationSecurityManager.class); 142 Authentication authentication = sm.getAuthentication(); 143 if( authentication == null ) { 144 LoginCommand.this.logger.info( "User canceled login; close the application." ); 145 getApplication().close(); 146 } 147 } 148 } 149 150 protected ActionCommand getCallingCommand() { 151 return LoginCommand.this; 152 } 153 154 protected void onAboutToShow() { 155 loginForm.requestFocusInWindow(); 156 } 157 }; 158 dialog.setDisplayFinishSuccessMessage( displaySuccessMessage ); 159 dialog.showDialog(); 160 } 161 162 /** 163 * Construct the Form to place in the login dialog. 164 * @return form to use 165 */ 166 protected LoginForm createLoginForm() { 167 return new LoginForm(); 168 } 169 170 /** 171 * Get the dialog in use, if available. 172 * @return dialog instance in use 173 */ 174 protected ApplicationDialog getDialog() { 175 return dialog; 176 } 177 178 /** 179 * Called to give subclasses control after a successful login. 180 */ 181 protected void postLogin() { 182 } 183 184 /** 185 * Get the "close on cancel" setting. 186 * @return close on cancel 187 */ 188 public boolean isCloseOnCancel() { 189 return closeOnCancel; 190 } 191 192 /** 193 * Indicates if the application should be closed if the user cancels the login 194 * operation. Default is true. 195 * @param closeOnCancel 196 */ 197 public void setCloseOnCancel(boolean closeOnCancel) { 198 this.closeOnCancel = closeOnCancel; 199 } 200 201 /** 202 * Get the "clear password on failure" setting. 203 * @return clear password 204 */ 205 public boolean isClearPasswordOnFailure() { 206 return clearPasswordOnFailure; 207 } 208 209 /** 210 * Indicates if the password field should be cleared after a login failure. Default is 211 * true. 212 * @param clearPasswordOnFailure 213 */ 214 public void setClearPasswordOnFailure(boolean clearPasswordOnFailure) { 215 this.clearPasswordOnFailure = clearPasswordOnFailure; 216 } 217 218 /** 219 * Get the default user name. 220 * @return default user name. 221 */ 222 public String getDefaultUserName() { 223 return defaultUserName; 224 } 225 226 /** 227 * Set the default user name. 228 * @param defaultUserName to use as default, null indicates no default 229 */ 230 public void setDefaultUserName(String defaultUserName) { 231 this.defaultUserName = defaultUserName; 232 } 233 234 }