001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    
020    package com.mucommander.ui.dialog.auth;
021    
022    
023    import com.mucommander.auth.Credentials;
024    import com.mucommander.auth.CredentialsManager;
025    import com.mucommander.auth.CredentialsMapping;
026    import com.mucommander.file.FileURL;
027    import com.mucommander.text.Translator;
028    import com.mucommander.ui.combobox.EditableComboBox;
029    import com.mucommander.ui.combobox.EditableComboBoxListener;
030    import com.mucommander.ui.combobox.SaneComboBox;
031    import com.mucommander.ui.dialog.DialogToolkit;
032    import com.mucommander.ui.dialog.FocusDialog;
033    import com.mucommander.ui.helper.FocusRequester;
034    import com.mucommander.ui.layout.XAlignedComponentPanel;
035    import com.mucommander.ui.layout.YBoxPanel;
036    import com.mucommander.ui.main.MainFrame;
037    import com.mucommander.ui.text.FontUtils;
038    import com.mucommander.ui.text.MultiLineLabel;
039    import com.mucommander.util.StringUtils;
040    
041    import javax.swing.*;
042    import java.awt.*;
043    import java.awt.event.ActionEvent;
044    import java.awt.event.ActionListener;
045    
046    
047    /**
048     * This dialog is used to ask the user for credentials (login/password) to access a particular location and offer him
049     * to store them to disk.
050     *
051     * <p>It uses CredentialsManager to retrieve and display a list of credentials matching the location so
052     * they can quickly be recalled.
053     *
054     * @see CredentialsManager
055     * @author Maxence Bernard
056     */
057    public class AuthDialog extends FocusDialog implements ActionListener, EditableComboBoxListener {
058    
059        private JButton okButton;
060        private JButton cancelButton;
061    
062        private JRadioButton guestRadioButton;
063        private JRadioButton userRadioButton;
064    
065        private JTextField loginField;
066        private EditableComboBox loginComboBox;
067    
068        private JPasswordField passwordField;
069    
070        private JCheckBox saveCredentialsCheckBox;
071    
072        private CredentialsMapping selectedCredentialsMapping;
073        private boolean guestCredentialsSelected;
074    
075        private FileURL fileURL;
076    
077        private CredentialsMapping[] credentialsMappings;
078    
079        // Dialog size constraints
080        private final static Dimension MINIMUM_DIALOG_DIMENSION = new Dimension(320,0);
081        private final static Dimension MAXIMUM_DIALOG_DIMENSION = new Dimension(480,10000);
082    
083    
084        public AuthDialog(MainFrame mainFrame, FileURL fileURL, boolean authFailed, String errorMessage) {
085            super(mainFrame, Translator.get("auth_dialog.title"), mainFrame);
086            
087            Container contentPane = getContentPane();
088            contentPane.setLayout(new BorderLayout());
089    
090            YBoxPanel yPanel = new YBoxPanel(5);
091    
092            if(authFailed) {
093                JTextArea label = new MultiLineLabel(Translator.get("auth_dialog.authentication_failed")+(errorMessage==null||(errorMessage=errorMessage.trim()).equals("")?"":": "+errorMessage));
094                FontUtils.makeBold(label);
095    
096                yPanel.add(label);
097                yPanel.addSpace(5);
098            }
099    
100            this.fileURL = fileURL;
101    
102            // Retrieve guest credentials (if any)
103            Credentials guestCredentials = fileURL.getGuestCredentials();
104            // Fetch credentials from the specified FileURL (if any) and use them only if they're different from the guest ones
105            Credentials urlCredentials = fileURL.getCredentials();
106            if(urlCredentials!=null && guestCredentials!=null && urlCredentials.equals(guestCredentials))
107                urlCredentials = null;
108            // Retrieve a list of credentials matching the URL from CredentialsManager
109            credentialsMappings = CredentialsManager.getMatchingCredentials(fileURL);
110    
111            XAlignedComponentPanel compPanel = new XAlignedComponentPanel(10);
112    
113            // Connect as Guest/User radio buttons, displayed only if the URL has guest credentials
114            if(guestCredentials!=null) {
115                guestRadioButton = new JRadioButton(StringUtils.capitalize(guestCredentials.getLogin()));
116                guestRadioButton.addActionListener(this);
117                compPanel.addRow(Translator.get("auth_dialog.connect_as"), guestRadioButton, 0);
118    
119                userRadioButton = new JRadioButton(Translator.get("user"));
120                userRadioButton.addActionListener(this);
121                compPanel.addRow("", userRadioButton, 15);
122    
123                ButtonGroup buttonGroup = new ButtonGroup();
124                buttonGroup.add(guestRadioButton);
125                buttonGroup.add(userRadioButton);
126            }
127            // If not display an introductive label ("please enter a login and password")
128            else {
129                yPanel.add(new JLabel(Translator.get("auth_dialog.desc")+" :"));
130                yPanel.addSpace(15);
131            }
132    
133            // Server URL for which the user has to authenticate
134            compPanel.addRow(Translator.get("auth_dialog.server")+":", new JLabel(fileURL.toString(false)), 10);
135    
136            // Login field: create either a text field or an editable combo box, depending on whether
137            // CredentialsManager returned matches (-> combo box) or not (-> text field).
138            int nbCredentials = credentialsMappings.length;
139            JComponent loginComponent;
140            if(nbCredentials>0) {
141                // Editable combo box
142                loginComboBox = new EditableComboBox();
143                this.loginField = loginComboBox.getTextField();
144    
145                // Add credentials to the combo box's choices
146                for(int i=0; i<nbCredentials; i++)
147                    loginComboBox.addItem(credentialsMappings[i].getCredentials().getLogin());
148    
149                loginComboBox.addEditableComboBoxListener(this);
150    
151                loginComponent = loginComboBox;
152            }
153                    else {
154                // Simple text field
155                loginField = new JTextField();
156                loginComponent = loginField;
157            }
158    
159            compPanel.addRow(Translator.get("login")+":", loginComponent, 5);
160    
161            // Create password field
162            this.passwordField = new JPasswordField();
163            passwordField.addActionListener(this);
164            compPanel.addRow(Translator.get("password")+":", passwordField, 10);
165    
166            // Contains the credentials to set in the login and password text fields
167            Credentials selectedCredentials = null;
168            // Whether the 'save credentials' checkbox should be enabled
169            boolean saveCredentialsCheckBoxSelected = false;
170    
171            // If the provided URL contains credentials, use them
172            if(urlCredentials!=null) {
173                selectedCredentials = urlCredentials;
174            }
175            // Else if CredentialsManager had matching credentials, use the best ones  
176            else if(nbCredentials>0) {
177                CredentialsMapping bestCredentialsMapping = credentialsMappings[0];
178    
179                selectedCredentials = bestCredentialsMapping.getCredentials();
180                saveCredentialsCheckBoxSelected = bestCredentialsMapping.isPersistent();
181            }
182    
183            yPanel.add(compPanel);
184    
185            this.saveCredentialsCheckBox = new JCheckBox(Translator.get("auth_dialog.store_credentials"), saveCredentialsCheckBoxSelected);
186            yPanel.add(saveCredentialsCheckBox);
187    
188            yPanel.addSpace(5);
189            contentPane.add(yPanel, BorderLayout.CENTER);
190    
191            // If we have some existing credentials for this location...
192            if(selectedCredentials!=null) {
193                // Prefill the login and password fields with the selected credentials
194                loginField.setText(selectedCredentials.getLogin());
195                passwordField.setText(selectedCredentials.getPassword());
196    
197                // Select the text fields' so their content can be erased just by typing the replacement string
198                loginField.selectAll();
199                passwordField.selectAll();
200    
201                // Select the 'Connect as User' radio button if there is one
202                if(userRadioButton!=null)
203                    userRadioButton.setSelected(true);
204            }
205            else {
206                // Select the 'Connect as Guest' radio button if there is one
207                if(guestRadioButton!=null) {
208                    guestRadioButton.setSelected(true);
209    
210                    loginField.setEnabled(false);
211                    passwordField.setEnabled(false);
212                    saveCredentialsCheckBox.setEnabled(false);
213                }
214            }
215    
216            // Add OK/Cancel buttons
217            this.okButton = new JButton(Translator.get("ok"));
218            this.cancelButton = new JButton(Translator.get("cancel"));
219            contentPane.add(DialogToolkit.createOKCancelPanel(okButton, cancelButton, getRootPane(), this), BorderLayout.SOUTH);
220    
221            // Set the component that will receive the initial focus
222            setInitialFocusComponent(guestRadioButton==null?(JComponent)loginField:guestRadioButton.isSelected()?guestRadioButton:(JComponent)loginField);
223    
224            // Set minimum dimension
225            setMinimumSize(MINIMUM_DIALOG_DIMENSION);
226    
227            // Set minimum dimension
228            setMaximumSize(MAXIMUM_DIALOG_DIMENSION);
229        }
230    
231    
232        /**
233         * Returns the <Code>CredentialsMapping</code> corresponding to the credentials selected by the user, either
234         * entered in the login and password fields, or the guest credentials.
235         *
236         * @return the credentials entered by the user, <code>null</code> if the dialog was cancelled
237         */
238        public CredentialsMapping getCredentialsMapping() {
239            return selectedCredentialsMapping;
240        }
241    
242        /**
243         * Returns <code>true</code> if the user chose the guest credentials (radio button) in the dialog.
244         * If <code>true</code>, {@link #getCredentialsMapping()} will return the guest credentials.
245         *
246         * @return <code>true</code> if the user chose the guest credentials (radio button) in the dialog
247         */
248        public boolean guestCredentialsSelected() {
249            return guestCredentialsSelected;
250        }
251    
252        /**
253         * Called when the dialog has been validated by the user, when the OK button has been pressed or when enter has
254         * been pressed in a text field.
255         */
256        private void setCredentialMapping() {
257            if(guestRadioButton!=null && guestRadioButton.isSelected()) {
258                guestCredentialsSelected = true;
259                selectedCredentialsMapping = new CredentialsMapping(fileURL.getGuestCredentials(), fileURL, false);
260            }
261            else {
262                Credentials enteredCredentials = new Credentials(loginField.getText(), new String(passwordField.getPassword()));
263                guestCredentialsSelected = false;
264                selectedCredentialsMapping = new CredentialsMapping(enteredCredentials, fileURL, saveCredentialsCheckBox.isSelected());
265    
266                // Reuse any existing instance which may contain connection properties
267                int nbCredentials = credentialsMappings.length;
268                CredentialsMapping cm;
269                for(int i=0; i<nbCredentials; i++) {
270                    cm = credentialsMappings[i];
271                    if(cm.getCredentials().equals(enteredCredentials, true)) {  // Comparison must be password-sensitive
272                        selectedCredentialsMapping = cm;
273                        break;
274                    }
275                }
276            }
277        }
278    
279    
280        ////////////////////////////
281        // ActionListener methods //
282        ////////////////////////////
283    
284        public void actionPerformed(ActionEvent e) {
285            Object source = e.getSource();
286    
287            if(source==okButton || source==loginField || source==passwordField) {
288                setCredentialMapping();
289                dispose();
290            }
291            else if(source==cancelButton) {
292                dispose();
293            }
294            else if(source==guestRadioButton) {
295                loginField.setEnabled(false);
296                passwordField.setEnabled(false);
297                saveCredentialsCheckBox.setEnabled(false);
298            }
299            else if(source==userRadioButton) {
300                loginField.setEnabled(true);
301                passwordField.setEnabled(true);
302                saveCredentialsCheckBox.setEnabled(true);
303    
304                loginField.selectAll();
305                FocusRequester.requestFocus(loginField);
306            }
307        }
308    
309    
310        /////////////////////////////////////////////
311        // EditableComboBoxListener implementation //
312        /////////////////////////////////////////////
313    
314        public void comboBoxSelectionChanged(SaneComboBox source) {
315            CredentialsMapping selectedCredentialsMapping = credentialsMappings[loginComboBox.getSelectedIndex()];
316            Credentials selectedCredentials = selectedCredentialsMapping.getCredentials();
317            loginField.setText(selectedCredentials.getLogin());
318            passwordField.setText(selectedCredentials.getPassword());
319    
320            // Enable/disable 'save credentials' checkbox depending on whether the selected credentials are persistent or not
321            if(saveCredentialsCheckBox!=null)
322                saveCredentialsCheckBox.setSelected(selectedCredentialsMapping.isPersistent());
323        }
324    
325        public void textFieldValidated(EditableComboBox source) {
326            setCredentialMapping();
327            dispose();
328        }
329    
330        public void textFieldCancelled(EditableComboBox source) {
331        }
332    }