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.JDialog; 026import javax.swing.JLabel; 027import javax.swing.JPanel; 028import javax.swing.JTextField; 029 030import org.openstreetmap.josm.Main; 031import org.openstreetmap.josm.gui.SideButton; 032import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 033import org.openstreetmap.josm.gui.help.HelpUtil; 034import org.openstreetmap.josm.gui.preferences.server.ProxyPreferencesPanel; 035import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 036import org.openstreetmap.josm.gui.widgets.JosmPasswordField; 037import org.openstreetmap.josm.gui.widgets.JosmTextField; 038import org.openstreetmap.josm.io.OsmApi; 039import org.openstreetmap.josm.tools.ImageProvider; 040import org.openstreetmap.josm.tools.InputMapUtils; 041import org.openstreetmap.josm.tools.WindowGeometry; 042 043public class CredentialDialog extends JDialog { 044 045 public static CredentialDialog getOsmApiCredentialDialog(String username, String password, String host, 046 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, 058 String saveUsernameAndPasswordCheckboxText) { 059 CredentialDialog dialog = new CredentialDialog(saveUsernameAndPasswordCheckboxText); 060 dialog.prepareForProxyCredentials(username, password); 061 dialog.pack(); 062 return dialog; 063 } 064 065 private boolean canceled; 066 protected CredentialPanel pnlCredentials; 067 private final String saveUsernameAndPasswordCheckboxText; 068 069 public boolean isCanceled() { 070 return canceled; 071 } 072 073 protected void setCanceled(boolean canceled) { 074 this.canceled = canceled; 075 } 076 077 @Override 078 public void setVisible(boolean visible) { 079 if (visible) { 080 WindowGeometry.centerInWindow(Main.parent, new Dimension(350, 300)).applySafe(this); 081 } 082 super.setVisible(visible); 083 } 084 085 protected JPanel createButtonPanel() { 086 JPanel pnl = new JPanel(new FlowLayout()); 087 pnl.add(new SideButton(new OKAction())); 088 pnl.add(new SideButton(new CancelAction())); 089 pnl.add(new SideButton(new ContextSensitiveHelpAction(HelpUtil.ht("/Dialog/Password")))); 090 return pnl; 091 } 092 093 protected void build() { 094 getContentPane().setLayout(new BorderLayout()); 095 getContentPane().add(createButtonPanel(), BorderLayout.SOUTH); 096 097 addWindowListener(new WindowEventHander()); 098 InputMapUtils.addEscapeAction(getRootPane(), 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(e, 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 pnlCredentials = new OsmApiCredentialsPanel(this); 117 getContentPane().add(pnlCredentials, BorderLayout.CENTER); 118 pnlCredentials.init(username, password); 119 validate(); 120 } 121 122 public void prepareForOtherHostCredentials(String username, String password, String host) { 123 setTitle(tr("Enter credentials for host")); 124 pnlCredentials = new OtherHostCredentialsPanel(this, host); 125 getContentPane().add(pnlCredentials, BorderLayout.CENTER); 126 pnlCredentials.init(username, password); 127 validate(); 128 } 129 130 public void prepareForProxyCredentials(String username, String password) { 131 setTitle(tr("Enter credentials for HTTP proxy")); 132 pnlCredentials = new HttpProxyCredentialsPanel(this); 133 getContentPane().add(pnlCredentials, BorderLayout.CENTER); 134 pnlCredentials.init(username, password); 135 validate(); 136 } 137 138 public String getUsername() { 139 if (pnlCredentials == null) 140 return null; 141 return pnlCredentials.getUserName(); 142 } 143 144 public char[] getPassword() { 145 if (pnlCredentials == null) 146 return null; 147 return pnlCredentials.getPassword(); 148 } 149 150 public boolean isSaveCredentials() { 151 if (pnlCredentials == null) 152 return false; 153 return pnlCredentials.isSaveCredentials(); 154 } 155 156 protected static class CredentialPanel extends JPanel { 157 protected JosmTextField tfUserName; 158 protected JosmPasswordField tfPassword; 159 protected JCheckBox cbSaveCredentials; 160 protected final JMultilineLabel lblHeading = new JMultilineLabel(""); 161 protected final JMultilineLabel lblWarning = new JMultilineLabel(""); 162 protected CredentialDialog owner; // owner Dependency Injection to use Key listeners for username and password text fields 163 164 protected void build() { 165 tfUserName = new JosmTextField(20); 166 tfPassword = new JosmPasswordField(20); 167 tfUserName.addFocusListener(new SelectAllOnFocusHandler()); 168 tfPassword.addFocusListener(new SelectAllOnFocusHandler()); 169 tfUserName.addKeyListener(new TFKeyListener(owner, tfUserName, tfPassword)); 170 tfPassword.addKeyListener(new TFKeyListener(owner, tfPassword, tfUserName)); 171 cbSaveCredentials = new JCheckBox(owner != null ? owner.saveUsernameAndPasswordCheckboxText : ""); 172 173 setLayout(new GridBagLayout()); 174 GridBagConstraints gc = new GridBagConstraints(); 175 gc.gridwidth = 2; 176 gc.gridheight = 1; 177 gc.fill = GridBagConstraints.HORIZONTAL; 178 gc.weightx = 1.0; 179 gc.weighty = 0.0; 180 gc.insets = new Insets(0, 0, 10, 0); 181 add(lblHeading, gc); 182 183 gc.gridx = 0; 184 gc.gridy = 1; 185 gc.gridwidth = 1; 186 gc.gridheight = 1; 187 gc.fill = GridBagConstraints.HORIZONTAL; 188 gc.weightx = 0.0; 189 gc.weighty = 0.0; 190 gc.insets = new Insets(0, 0, 10, 10); 191 add(new JLabel(tr("Username")), gc); 192 gc.gridx = 1; 193 gc.gridy = 1; 194 gc.weightx = 1.0; 195 add(tfUserName, gc); 196 gc.gridx = 0; 197 gc.gridy = 2; 198 gc.weightx = 0.0; 199 add(new JLabel(tr("Password")), gc); 200 201 gc.gridx = 1; 202 gc.gridy = 2; 203 gc.weightx = 0.0; 204 add(tfPassword, gc); 205 206 gc.gridx = 0; 207 gc.gridy = 3; 208 gc.gridwidth = 2; 209 gc.gridheight = 1; 210 gc.fill = GridBagConstraints.BOTH; 211 gc.weightx = 1.0; 212 gc.weighty = 0.0; 213 lblWarning.setFont(lblWarning.getFont().deriveFont(Font.ITALIC)); 214 add(lblWarning, gc); 215 216 gc.gridx = 0; 217 gc.gridy = 4; 218 gc.weighty = 0.0; 219 add(cbSaveCredentials, gc); 220 221 // consume the remaining space 222 gc.gridx = 0; 223 gc.gridy = 5; 224 gc.weighty = 1.0; 225 add(new JPanel(), gc); 226 227 } 228 229 public CredentialPanel(CredentialDialog owner) { 230 this.owner = owner; 231 } 232 233 public void init(String username, String password) { 234 username = username == null ? "" : username; 235 password = password == null ? "" : password; 236 tfUserName.setText(username); 237 tfPassword.setText(password); 238 cbSaveCredentials.setSelected(!username.isEmpty() && !password.isEmpty()); 239 } 240 241 public void startUserInput() { 242 tfUserName.requestFocusInWindow(); 243 } 244 245 public String getUserName() { 246 return tfUserName.getText(); 247 } 248 249 public char[] getPassword() { 250 return tfPassword.getPassword(); 251 } 252 253 public boolean isSaveCredentials() { 254 return cbSaveCredentials.isSelected(); 255 } 256 257 protected final void updateWarningLabel(String url) { 258 boolean https = url != null && url.startsWith("https"); 259 if (https) { 260 lblWarning.setText(null); 261 } else { 262 lblWarning.setText(tr("Warning: The password is transferred unencrypted.")); 263 } 264 lblWarning.setVisible(!https); 265 } 266 } 267 268 private static class OsmApiCredentialsPanel extends CredentialPanel { 269 270 @Override 271 protected void build() { 272 super.build(); 273 tfUserName.setToolTipText(tr("Please enter the user name of your OSM account")); 274 tfPassword.setToolTipText(tr("Please enter the password of your OSM account")); 275 String apiUrl = OsmApi.getOsmApi().getBaseUrl(); 276 lblHeading.setText( 277 "<html>" + tr("Authenticating at the OSM API ''{0}'' failed. Please enter a valid username and a valid password.", 278 apiUrl) + "</html>"); 279 updateWarningLabel(apiUrl); 280 } 281 282 OsmApiCredentialsPanel(CredentialDialog owner) { 283 super(owner); 284 build(); 285 } 286 } 287 288 private static class OtherHostCredentialsPanel extends CredentialPanel { 289 290 private final String host; 291 292 @Override 293 protected void build() { 294 super.build(); 295 tfUserName.setToolTipText(tr("Please enter the user name of your account")); 296 tfPassword.setToolTipText(tr("Please enter the password of your account")); 297 lblHeading.setText( 298 "<html>" + tr("Authenticating at the host ''{0}'' failed. Please enter a valid username and a valid password.", 299 host) + "</html>"); 300 updateWarningLabel(host); 301 } 302 303 OtherHostCredentialsPanel(CredentialDialog owner, String host) { 304 super(owner); 305 this.host = host; 306 build(); 307 } 308 } 309 310 private static class HttpProxyCredentialsPanel extends CredentialPanel { 311 @Override 312 protected void build() { 313 super.build(); 314 tfUserName.setToolTipText(tr("Please enter the user name for authenticating at your proxy server")); 315 tfPassword.setToolTipText(tr("Please enter the password for authenticating at your proxy server")); 316 lblHeading.setText( 317 "<html>" + tr("Authenticating at the HTTP proxy ''{0}'' failed. Please enter a valid username and a valid password.", 318 Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_HOST) + ':' + 319 Main.pref.get(ProxyPreferencesPanel.PROXY_HTTP_PORT)) + "</html>"); 320 lblWarning.setText("<html>" + 321 tr("Warning: depending on the authentication method the proxy server uses the password may be transferred unencrypted.") 322 + "</html>"); 323 } 324 325 HttpProxyCredentialsPanel(CredentialDialog owner) { 326 super(owner); 327 build(); 328 } 329 } 330 331 private static class SelectAllOnFocusHandler extends FocusAdapter { 332 @Override 333 public void focusGained(FocusEvent e) { 334 if (e.getSource() instanceof JTextField) { 335 JTextField tf = (JTextField) e.getSource(); 336 tf.selectAll(); 337 } 338 } 339 } 340 341 /** 342 * Listener for username and password text fields key events. 343 * When user presses Enter: 344 * If current text field is empty (or just contains a sequence of spaces), nothing happens (or all spaces become selected). 345 * If current text field is not empty, but the next one is (or just contains a sequence of spaces), focuses the next text field. 346 * If both text fields contain characters, submits the form by calling owner's {@link OKAction}. 347 */ 348 private static class TFKeyListener extends KeyAdapter { 349 protected CredentialDialog owner; // owner Dependency Injection to call OKAction 350 protected JTextField currentTF; 351 protected JTextField nextTF; 352 353 TFKeyListener(CredentialDialog owner, JTextField currentTF, JTextField nextTF) { 354 this.owner = owner; 355 this.currentTF = currentTF; 356 this.nextTF = nextTF; 357 } 358 359 @Override 360 public void keyPressed(KeyEvent e) { 361 if (e.getKeyChar() == KeyEvent.VK_ENTER) { 362 if (currentTF.getText().trim().isEmpty()) { 363 currentTF.selectAll(); 364 return; 365 } else if (nextTF.getText().trim().isEmpty()) { 366 nextTF.requestFocusInWindow(); 367 nextTF.selectAll(); 368 return; 369 } else { 370 OKAction okAction = owner.new OKAction(); 371 okAction.actionPerformed(null); 372 } 373 } 374 } 375 } 376 377 class OKAction extends AbstractAction { 378 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 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}