001 // License: GPL. For details, see LICENSE file. 002 package org.openstreetmap.josm.gui.io; 003 004 import static org.openstreetmap.josm.tools.I18n.tr; 005 006 import java.awt.BorderLayout; 007 import java.awt.Dimension; 008 import java.awt.FlowLayout; 009 import java.awt.Font; 010 import java.awt.GridBagConstraints; 011 import java.awt.GridBagLayout; 012 import java.awt.Insets; 013 import java.awt.event.ActionEvent; 014 import java.awt.event.FocusAdapter; 015 import java.awt.event.FocusEvent; 016 import java.awt.event.KeyEvent; 017 import java.awt.event.KeyListener; 018 import java.awt.event.WindowAdapter; 019 import java.awt.event.WindowEvent; 020 021 import javax.swing.AbstractAction; 022 import javax.swing.BorderFactory; 023 import javax.swing.JCheckBox; 024 import javax.swing.JComponent; 025 import javax.swing.JDialog; 026 import javax.swing.JLabel; 027 import javax.swing.JPanel; 028 import javax.swing.JPasswordField; 029 import javax.swing.JTextField; 030 import javax.swing.KeyStroke; 031 032 import org.openstreetmap.josm.Main; 033 import org.openstreetmap.josm.gui.JMultilineLabel; 034 import org.openstreetmap.josm.gui.SideButton; 035 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 036 import org.openstreetmap.josm.gui.help.HelpUtil; 037 import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel; 038 import org.openstreetmap.josm.io.OsmApi; 039 import org.openstreetmap.josm.tools.ImageProvider; 040 import org.openstreetmap.josm.tools.Utils; 041 import org.openstreetmap.josm.tools.WindowGeometry; 042 043 public class CredentialDialog extends JDialog { 044 045 static public CredentialDialog getOsmApiCredentialDialog(String username, String password, String host, String saveUsernameAndPasswordCheckboxText) { 046 CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText); 047 if (Utils.equal(OsmApi.getOsmApi().getHost(), host)) { 048 dialog.prepareForOsmApiCredentials(username, password); 049 } else { 050 dialog.prepareForOtherHostCredentials(username, password, host); 051 } 052 dialog.pack(); 053 return dialog; 054 } 055 056 static public CredentialDialog getHttpProxyCredentialDialog(String username, String password, String host, String saveUsernameAndPasswordCheckboxText) { 057 CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText); 058 dialog.prepareForProxyCredentials(username, password); 059 dialog.pack(); 060 return dialog; 061 } 062 063 private boolean canceled; 064 protected CredentialPanel pnlCredentials; 065 String saveUsernameAndPasswordCheckboxText; 066 067 public boolean isCanceled() { 068 return canceled; 069 } 070 071 protected void setCanceled(boolean canceled) { 072 this.canceled = canceled; 073 } 074 075 @Override 076 public void setVisible(boolean visible) { 077 if (visible) { 078 WindowGeometry.centerInWindow(Main.parent, new Dimension(350,300)).applySafe(this); 079 } 080 super.setVisible(visible); 081 } 082 083 protected JPanel createButtonPanel() { 084 JPanel pnl = new JPanel(new FlowLayout()); 085 pnl.add(new SideButton(new OKAction())); 086 pnl.add(new SideButton(new CancelAction())); 087 pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/Password")))); 088 return pnl; 089 } 090 091 protected void build() { 092 getContentPane().setLayout(new BorderLayout()); 093 getContentPane().add(createButtonPanel(), BorderLayout.SOUTH); 094 095 addWindowListener(new WindowEventHander()); 096 getRootPane().getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "escape"); 097 getRootPane().getActionMap().put("escape", new CancelAction()); 098 099 getRootPane().setBorder(BorderFactory.createEmptyBorder(5,5,5,5)); 100 } 101 102 public CredentialDialog(String saveUsernameAndPasswordCheckboxText) { 103 this.saveUsernameAndPasswordCheckboxText = saveUsernameAndPasswordCheckboxText; 104 setModalityType(ModalityType.DOCUMENT_MODAL); 105 try { 106 setAlwaysOnTop(true); 107 } catch(SecurityException e) { 108 System.out.println(tr("Warning: failed to put Credential Dialog always on top. Caught security exception.")); 109 } 110 build(); 111 } 112 113 public void prepareForOsmApiCredentials(String username, String password) { 114 setTitle(tr("Enter credentials for OSM API")); 115 getContentPane().add(pnlCredentials = new OsmApiCredentialsPanel(this), BorderLayout.CENTER); 116 pnlCredentials.init(username, password); 117 validate(); 118 } 119 120 public void prepareForOtherHostCredentials(String username, String password, String host) { 121 setTitle(tr("Enter credentials for host")); 122 getContentPane().add(pnlCredentials = new OtherHostCredentialsPanel(this, host), BorderLayout.CENTER); 123 pnlCredentials.init(username, password); 124 validate(); 125 } 126 127 public void prepareForProxyCredentials(String username, String password) { 128 setTitle(tr("Enter credentials for HTTP proxy")); 129 getContentPane().add(pnlCredentials = new HttpProxyCredentialsPanel(this), BorderLayout.CENTER); 130 pnlCredentials.init(username, password); 131 validate(); 132 } 133 134 public String getUsername() { 135 if (pnlCredentials== null) return null; 136 return pnlCredentials.getUserName(); 137 } 138 139 public char[] getPassword() { 140 if (pnlCredentials== null) return null; 141 return pnlCredentials.getPassword(); 142 } 143 144 public boolean isSaveCredentials() { 145 if (pnlCredentials== null) return false; 146 return pnlCredentials.isSaveCredentials(); 147 } 148 149 protected static class CredentialPanel extends JPanel { 150 protected JTextField tfUserName; 151 protected JPasswordField tfPassword; 152 protected JCheckBox cbSaveCredentials; 153 protected JMultilineLabel lblHeading; 154 protected JMultilineLabel lblWarning; 155 protected CredentialDialog owner; // owner Dependency Injection to use Key listeners for username and password text fields 156 157 protected void build() { 158 tfUserName = new JTextField(20); 159 tfPassword = new JPasswordField(20); 160 tfUserName.addFocusListener(new SelectAllOnFocusHandler()); 161 tfPassword.addFocusListener(new SelectAllOnFocusHandler()); 162 tfUserName.addKeyListener(new TFKeyListener(owner, tfUserName, tfPassword)); 163 tfPassword.addKeyListener(new TFKeyListener(owner, tfPassword, tfUserName)); 164 cbSaveCredentials = new JCheckBox(owner.saveUsernameAndPasswordCheckboxText); 165 166 setLayout(new GridBagLayout()); 167 GridBagConstraints gc = new GridBagConstraints(); 168 gc.gridwidth = 2; 169 gc.gridheight = 1; 170 gc.fill = GridBagConstraints.HORIZONTAL; 171 gc.weightx = 1.0; 172 gc.weighty = 0.0; 173 gc.insets = new Insets(0,0,10,0); 174 add(lblHeading = new JMultilineLabel(""), gc); 175 176 gc.gridx = 0; 177 gc.gridy = 1; 178 gc.gridwidth = 1; 179 gc.gridheight = 1; 180 gc.fill = GridBagConstraints.HORIZONTAL; 181 gc.weightx = 0.0; 182 gc.weighty = 0.0; 183 gc.insets = new Insets(0,0,10,10); 184 add(new JLabel(tr("Username")), gc); 185 gc.gridx = 1; 186 gc.gridy = 1; 187 gc.weightx = 1.0; 188 add(tfUserName, gc); 189 gc.gridx = 0; 190 gc.gridy = 2; 191 gc.weightx = 0.0; 192 add(new JLabel(tr("Password")), gc); 193 194 gc.gridx = 1; 195 gc.gridy = 2; 196 gc.weightx = 0.0; 197 add(tfPassword, gc); 198 199 gc.gridx = 0; 200 gc.gridy = 3; 201 gc.gridwidth = 2; 202 gc.gridheight = 1; 203 gc.fill = GridBagConstraints.BOTH; 204 gc.weightx = 1.0; 205 gc.weighty = 0.0; 206 lblWarning = new JMultilineLabel(""); 207 lblWarning.setFont(lblWarning.getFont().deriveFont(Font.ITALIC)); 208 add(lblWarning, gc); 209 210 gc.gridx = 0; 211 gc.gridy = 4; 212 gc.weighty = 0.0; 213 add(cbSaveCredentials, gc); 214 215 // consume the remaining space 216 gc.gridx = 0; 217 gc.gridy = 5; 218 gc.weighty = 1.0; 219 add(new JPanel(),gc); 220 221 } 222 223 public CredentialPanel(CredentialDialog owner) { 224 this.owner = owner; 225 } 226 227 public void init(String username, String password) { 228 username = username == null ? "" : username; 229 password = password == null ? "" : password; 230 tfUserName.setText(username); 231 tfPassword.setText(password); 232 cbSaveCredentials.setSelected(!username.equals("") && ! password.equals("")); 233 } 234 235 public void startUserInput() { 236 tfUserName.requestFocusInWindow(); 237 } 238 239 public String getUserName() { 240 return tfUserName.getText(); 241 } 242 243 public char[] getPassword() { 244 return tfPassword.getPassword(); 245 } 246 247 public boolean isSaveCredentials() { 248 return cbSaveCredentials.isSelected(); 249 } 250 } 251 252 private static class OsmApiCredentialsPanel extends CredentialPanel { 253 254 @Override 255 protected void build() { 256 super.build(); 257 tfUserName.setToolTipText(tr("Please enter the user name of your OSM account")); 258 tfPassword.setToolTipText(tr("Please enter the password of your OSM account")); 259 lblHeading.setText( 260 "<html>" + tr("Authenticating at the OSM API ''{0}'' failed. Please enter a valid username and a valid password.", 261 OsmApi.getOsmApi().getBaseUrl()) + "</html>"); 262 lblWarning.setText(tr("Warning: The password is transferred unencrypted.")); 263 } 264 265 public OsmApiCredentialsPanel(CredentialDialog owner) { 266 super(owner); 267 build(); 268 } 269 } 270 271 private static class OtherHostCredentialsPanel extends CredentialPanel { 272 273 String host; 274 275 @Override 276 protected void build() { 277 super.build(); 278 tfUserName.setToolTipText(tr("Please enter the user name of your account")); 279 tfPassword.setToolTipText(tr("Please enter the password of your account")); 280 lblHeading.setText( 281 "<html>" + tr("Authenticating at the host ''{0}'' failed. Please enter a valid username and a valid password.", 282 host) + "</html>"); 283 lblWarning.setText(tr("Warning: The password is transferred unencrypted.")); 284 } 285 286 public OtherHostCredentialsPanel(CredentialDialog owner, String host) { 287 super(owner); 288 this.host = host; 289 build(); 290 } 291 } 292 293 private static class HttpProxyCredentialsPanel extends CredentialPanel { 294 @Override 295 protected void build() { 296 super.build(); 297 tfUserName.setToolTipText(tr("Please enter the user name for authenticating at your proxy server")); 298 tfPassword.setToolTipText(tr("Please enter the password for authenticating at your proxy server")); 299 lblHeading.setText( 300 "<html>" + tr("Authenticating at the HTTP proxy ''{0}'' failed. Please enter a valid username and a valid password.", 301 Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_HOST) + ":" + Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_PORT)) + "</html>"); 302 lblWarning.setText("<html>" + tr("Warning: depending on the authentication method the proxy server uses the password may be transferred unencrypted.") + "</html>"); 303 } 304 305 public HttpProxyCredentialsPanel(CredentialDialog owner) { 306 super(owner); 307 build(); 308 } 309 } 310 311 static private class SelectAllOnFocusHandler extends FocusAdapter { 312 @Override 313 public void focusGained(FocusEvent e) { 314 if (e.getSource() instanceof JTextField) { 315 JTextField tf = (JTextField)e.getSource(); 316 tf.selectAll(); 317 } 318 } 319 } 320 321 /** 322 * Listener for username and password text fields key events. 323 * When user presses Enter: 324 * If current text field is empty (or just contains a sequence of spaces), nothing happens (or all spaces become selected). 325 * If current text field is not empty, but the next one is (or just contains a sequence of spaces), focuses the next text field. 326 * If both text fields contain characters, submits the form by calling owner's {@link OKAction}. 327 */ 328 static private class TFKeyListener implements KeyListener{ 329 protected CredentialDialog owner; // owner Dependency Injection to call OKAction 330 protected JTextField currentTF; 331 protected JTextField nextTF; 332 333 public TFKeyListener (CredentialDialog owner, JTextField currentTF, JTextField nextTF) 334 { 335 this.owner = owner; 336 this.currentTF = currentTF; 337 this.nextTF = nextTF; 338 } 339 340 @Override 341 public void keyPressed(KeyEvent e) { 342 if(e.getKeyChar() == KeyEvent.VK_ENTER) { 343 if (currentTF.getText().trim().isEmpty()) { 344 currentTF.selectAll(); 345 return; 346 } else if (nextTF.getText().trim().isEmpty()) { 347 nextTF.requestFocusInWindow(); 348 nextTF.selectAll(); 349 return; 350 } else { 351 OKAction okAction = owner.new OKAction(); 352 okAction.actionPerformed(null); 353 } 354 } 355 } 356 357 @Override 358 public void keyReleased ( KeyEvent e ){ 359 } 360 361 @Override 362 public void keyTyped ( KeyEvent e ){ 363 } 364 } 365 366 class OKAction extends AbstractAction { 367 public OKAction() { 368 putValue(NAME, tr("Authenticate")); 369 putValue(SHORT_DESCRIPTION, tr("Authenticate with the supplied username and password")); 370 putValue(SMALL_ICON, ImageProvider.get("ok")); 371 } 372 373 @Override 374 public void actionPerformed(ActionEvent arg0) { 375 setCanceled(false); 376 setVisible(false); 377 } 378 } 379 380 class CancelAction extends AbstractAction { 381 public CancelAction() { 382 putValue(NAME, tr("Cancel")); 383 putValue(SHORT_DESCRIPTION, tr("Cancel authentication")); 384 putValue(SMALL_ICON, ImageProvider.get("cancel")); 385 } 386 387 public void cancel() { 388 setCanceled(true); 389 setVisible(false); 390 } 391 392 @Override 393 public void actionPerformed(ActionEvent arg0) { 394 cancel(); 395 } 396 } 397 398 class WindowEventHander extends WindowAdapter { 399 400 @Override 401 public void windowActivated(WindowEvent e) { 402 if (pnlCredentials != null) { 403 pnlCredentials.startUserInput(); 404 } 405 } 406 407 @Override 408 public void windowClosing(WindowEvent e) { 409 new CancelAction().cancel(); 410 } 411 } 412 }