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