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     *        &lt;bean id=&quot;placeholderConfigurer&quot;
056     *             class=&quot;org.springframework.beans.factory.config.PropertyPlaceholderConfigurer&quot;/&gt;
057     *
058     *         &lt;bean id=&quot;loginCommand&quot;
059     *             class=&quot;org.springframework.richclient.security.LoginCommand&quot;&gt;
060     *             &lt;property name=&quot;displaySuccess&quot; value=&quot;false&quot;/&gt;
061     *             &lt;property name=&quot;defaultUserName&quot; value=&quot;${user.name}&quot;/&gt;
062     *         &lt;/bean&gt;
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    }