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 }