001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.Dimension; 008import java.awt.FlowLayout; 009import java.awt.Font; 010import java.awt.GridBagConstraints; 011import java.awt.GridBagLayout; 012import java.awt.Insets; 013import java.awt.event.ActionEvent; 014import java.awt.event.FocusAdapter; 015import java.awt.event.FocusEvent; 016import java.awt.event.KeyEvent; 017import java.awt.event.KeyListener; 018import java.awt.event.WindowAdapter; 019import java.awt.event.WindowEvent; 020import java.util.Objects; 021 022import javax.swing.AbstractAction; 023import javax.swing.BorderFactory; 024import javax.swing.JCheckBox; 025import javax.swing.JComponent; 026import javax.swing.JDialog; 027import javax.swing.JLabel; 028import javax.swing.JPanel; 029import javax.swing.JTextField; 030import javax.swing.KeyStroke; 031 032import org.openstreetmap.josm.Main; 033import org.openstreetmap.josm.gui.SideButton; 034import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 035import org.openstreetmap.josm.gui.help.HelpUtil; 036import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel; 037import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 038import org.openstreetmap.josm.gui.widgets.JosmPasswordField; 039import org.openstreetmap.josm.io.OsmApi; 040import org.openstreetmap.josm.tools.ImageProvider; 041import org.openstreetmap.josm.tools.WindowGeometry; 042 043public class CredentialDialog extends JDialog { 044 045 public static CredentialDialog getOsmApiCredentialDialog(String username, String password, String host, String saveUsernameAndPasswordCheckboxText) { 046 CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText); 047 if (Objects.equals(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 public static 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 Main.warn(tr("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 JosmPasswordField 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 JosmPasswordField(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.isEmpty() && !password.isEmpty()); 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 protected final void updateWarningLabel(String url) { 252 boolean https = url != null && url.startsWith("https"); 253 if (https) { 254 lblWarning.setText(null); 255 } else { 256 lblWarning.setText(tr("Warning: The password is transferred unencrypted.")); 257 } 258 lblWarning.setVisible(!https); 259 } 260 } 261 262 private static class OsmApiCredentialsPanel extends CredentialPanel { 263 264 @Override 265 protected void build() { 266 super.build(); 267 tfUserName.setToolTipText(tr("Please enter the user name of your OSM account")); 268 tfPassword.setToolTipText(tr("Please enter the password of your OSM account")); 269 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 270 lblHeading.setText( 271 "<html>" + tr("Authenticating at the OSM API ''{0}'' failed. Please enter a valid username and a valid password.", 272 apiUrl) + "</html>"); 273 updateWarningLabel(apiUrl); 274 } 275 276 public OsmApiCredentialsPanel(CredentialDialog owner) { 277 super(owner); 278 build(); 279 } 280 } 281 282 private static class OtherHostCredentialsPanel extends CredentialPanel { 283 284 String host; 285 286 @Override 287 protected void build() { 288 super.build(); 289 tfUserName.setToolTipText(tr("Please enter the user name of your account")); 290 tfPassword.setToolTipText(tr("Please enter the password of your account")); 291 lblHeading.setText( 292 "<html>" + tr("Authenticating at the host ''{0}'' failed. Please enter a valid username and a valid password.", 293 host) + "</html>"); 294 updateWarningLabel(host); 295 } 296 297 public OtherHostCredentialsPanel(CredentialDialog owner, String host) { 298 super(owner); 299 this.host = host; 300 build(); 301 } 302 } 303 304 private static class HttpProxyCredentialsPanel extends CredentialPanel { 305 @Override 306 protected void build() { 307 super.build(); 308 tfUserName.setToolTipText(tr("Please enter the user name for authenticating at your proxy server")); 309 tfPassword.setToolTipText(tr("Please enter the password for authenticating at your proxy server")); 310 lblHeading.setText( 311 "<html>" + tr("Authenticating at the HTTP proxy ''{0}'' failed. Please enter a valid username and a valid password.", 312 Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_HOST) + ":" + Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_PORT)) + "</html>"); 313 lblWarning.setText("<html>" + tr("Warning: depending on the authentication method the proxy server uses the password may be transferred unencrypted.") + "</html>"); 314 } 315 316 public HttpProxyCredentialsPanel(CredentialDialog owner) { 317 super(owner); 318 build(); 319 } 320 } 321 322 private static class SelectAllOnFocusHandler extends FocusAdapter { 323 @Override 324 public void focusGained(FocusEvent e) { 325 if (e.getSource() instanceof JTextField) { 326 JTextField tf = (JTextField)e.getSource(); 327 tf.selectAll(); 328 } 329 } 330 } 331 332 /** 333 * Listener for username and password text fields key events. 334 * When user presses Enter: 335 * If current text field is empty (or just contains a sequence of spaces), nothing happens (or all spaces become selected). 336 * If current text field is not empty, but the next one is (or just contains a sequence of spaces), focuses the next text field. 337 * If both text fields contain characters, submits the form by calling owner's {@link OKAction}. 338 */ 339 private static class TFKeyListener implements KeyListener{ 340 protected CredentialDialog owner; // owner Dependency Injection to call OKAction 341 protected JTextField currentTF; 342 protected JTextField nextTF; 343 344 public TFKeyListener (CredentialDialog owner, JTextField currentTF, JTextField nextTF) 345 { 346 this.owner = owner; 347 this.currentTF = currentTF; 348 this.nextTF = nextTF; 349 } 350 351 @Override 352 public void keyPressed(KeyEvent e) { 353 if(e.getKeyChar() == KeyEvent.VK_ENTER) { 354 if (currentTF.getText().trim().isEmpty()) { 355 currentTF.selectAll(); 356 return; 357 } else if (nextTF.getText().trim().isEmpty()) { 358 nextTF.requestFocusInWindow(); 359 nextTF.selectAll(); 360 return; 361 } else { 362 OKAction okAction = owner.new OKAction(); 363 okAction.actionPerformed(null); 364 } 365 } 366 } 367 368 @Override 369 public void keyReleased ( KeyEvent e ){ 370 } 371 372 @Override 373 public void keyTyped ( KeyEvent e ){ 374 } 375 } 376 377 class OKAction extends AbstractAction { 378 public OKAction() { 379 putValue(NAME, tr("Authenticate")); 380 putValue(SHORT_DESCRIPTION, tr("Authenticate with the supplied username and password")); 381 putValue(SMALL_ICON, ImageProvider.get("ok")); 382 } 383 384 @Override 385 public void actionPerformed(ActionEvent arg0) { 386 setCanceled(false); 387 setVisible(false); 388 } 389 } 390 391 class CancelAction extends AbstractAction { 392 public CancelAction() { 393 putValue(NAME, tr("Cancel")); 394 putValue(SHORT_DESCRIPTION, tr("Cancel authentication")); 395 putValue(SMALL_ICON, ImageProvider.get("cancel")); 396 } 397 398 public void cancel() { 399 setCanceled(true); 400 setVisible(false); 401 } 402 403 @Override 404 public void actionPerformed(ActionEvent arg0) { 405 cancel(); 406 } 407 } 408 409 class WindowEventHander extends WindowAdapter { 410 411 @Override 412 public void windowActivated(WindowEvent e) { 413 if (pnlCredentials != null) { 414 pnlCredentials.startUserInput(); 415 } 416 } 417 418 @Override 419 public void windowClosing(WindowEvent e) { 420 new CancelAction().cancel(); 421 } 422 } 423}