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 }