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