source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java@ 4690

Last change on this file since 4690 was 4690, checked in by stoecker, 12 years ago

see #7086 - fix passing auth information to wrong server

  • Property svn:eol-style set to native
File size: 20.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.oauth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Component;
8import java.awt.FlowLayout;
9import java.awt.Font;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.io.IOException;
15import java.net.PasswordAuthentication;
16import java.net.Authenticator.RequestorType;
17
18import javax.swing.AbstractAction;
19import javax.swing.BorderFactory;
20import javax.swing.JLabel;
21import javax.swing.JOptionPane;
22import javax.swing.JPanel;
23import javax.swing.JPasswordField;
24import javax.swing.JTabbedPane;
25import javax.swing.JTextField;
26import javax.swing.SwingUtilities;
27import javax.swing.event.DocumentEvent;
28import javax.swing.event.DocumentListener;
29import javax.swing.text.JTextComponent;
30import javax.swing.text.html.HTMLEditorKit;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.data.Preferences;
34import org.openstreetmap.josm.data.oauth.OAuthToken;
35import org.openstreetmap.josm.gui.HelpAwareOptionPane;
36import org.openstreetmap.josm.gui.JMultilineLabel;
37import org.openstreetmap.josm.gui.PleaseWaitRunnable;
38import org.openstreetmap.josm.gui.SideButton;
39import org.openstreetmap.josm.gui.help.HelpUtil;
40import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
41import org.openstreetmap.josm.gui.widgets.HtmlPanel;
42import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
43import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
44import org.openstreetmap.josm.io.OsmApi;
45import org.openstreetmap.josm.io.OsmTransferException;
46import org.openstreetmap.josm.io.auth.CredentialsAgent;
47import org.openstreetmap.josm.io.auth.CredentialsAgentException;
48import org.openstreetmap.josm.io.auth.CredentialsManager;
49import org.openstreetmap.josm.tools.ImageProvider;
50import org.xml.sax.SAXException;
51
52/**
53 * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully
54 * automatic process.
55 *
56 */
57public class FullyAutomaticAuthorizationUI extends AbstractAuthorizationUI {
58
59 private JTextField tfUserName;
60 private JPasswordField tfPassword;
61 private UserNameValidator valUserName;
62 private PasswordValidator valPassword;
63 private AccessTokenInfoPanel pnlAccessTokenInfo;
64 private OsmPrivilegesPanel pnlOsmPrivileges;
65 private JPanel pnlPropertiesPanel;
66 private JPanel pnlActionButtonsPanel;
67 private JPanel pnlResult;
68
69 /**
70 * Builds the panel with the three privileges the user can grant JOSM
71 *
72 * @return
73 */
74 protected VerticallyScrollablePanel buildGrantsPanel() {
75 pnlOsmPrivileges = new OsmPrivilegesPanel();
76 return pnlOsmPrivileges;
77 }
78
79 /**
80 * Builds the panel for entering the username and password
81 *
82 * @return
83 */
84 protected VerticallyScrollablePanel buildUserNamePasswordPanel() {
85 VerticallyScrollablePanel pnl = new VerticallyScrollablePanel(new GridBagLayout());
86 GridBagConstraints gc = new GridBagConstraints();
87 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
88
89 gc.anchor = GridBagConstraints.NORTHWEST;
90 gc.fill = GridBagConstraints.HORIZONTAL;
91 gc.weightx = 1.0;
92 gc.gridwidth = 2;
93 HtmlPanel pnlMessage = new HtmlPanel();
94 HTMLEditorKit kit = (HTMLEditorKit)pnlMessage.getEditorPane().getEditorKit();
95 kit.getStyleSheet().addRule(".warning-body {background-color:rgb(253,255,221);padding: 10pt; border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
96 kit.getStyleSheet().addRule("ol {margin-left: 1cm}");
97 pnlMessage.setText("<html><body><p class=\"warning-body\">"
98 + tr("Please enter your OSM user name and password. The password will <strong>not</strong> be saved "
99 + "in clear text in the JOSM preferences and it will be submitted to the OSM server <strong>only once</strong>. "
100 + "Subsequent data upload requests don''t use your password any more.")
101 + "</p>"
102 + "</body></html>");
103 pnl.add(pnlMessage, gc);
104
105 // the user name input field
106 gc.gridy = 1;
107 gc.gridwidth = 1;
108 gc.anchor = GridBagConstraints.NORTHWEST;
109 gc.fill = GridBagConstraints.HORIZONTAL;
110 gc.weightx = 0.0;
111 gc.insets = new Insets(0,0,3,3);
112 pnl.add(new JLabel(tr("Username: ")), gc);
113
114 gc.gridx = 1;
115 gc.weightx = 1.0;
116 pnl.add(tfUserName = new JTextField(), gc);
117 SelectAllOnFocusGainedDecorator.decorate(tfUserName);
118 valUserName = new UserNameValidator(tfUserName);
119 valUserName.validate();
120
121 // the password input field
122 gc.anchor = GridBagConstraints.NORTHWEST;
123 gc.fill = GridBagConstraints.HORIZONTAL;
124 gc.gridy = 2;
125 gc.gridx = 0;
126 gc.weightx = 0.0;
127 pnl.add(new JLabel(tr("Password: ")), gc);
128
129 gc.gridx = 1;
130 gc.weightx = 1.0;
131 pnl.add(tfPassword = new JPasswordField(), gc);
132 SelectAllOnFocusGainedDecorator.decorate(tfPassword);
133 valPassword = new PasswordValidator(tfPassword);
134 valPassword.validate();
135
136 gc.gridy = 3;
137 gc.gridx = 0;
138 gc.anchor = GridBagConstraints.NORTHWEST;
139 gc.fill = GridBagConstraints.HORIZONTAL;
140 gc.weightx = 1.0;
141 gc.gridwidth = 2;
142 pnlMessage = new HtmlPanel();
143 kit = (HTMLEditorKit)pnlMessage.getEditorPane().getEditorKit();
144 kit.getStyleSheet().addRule(".warning-body {background-color:rgb(253,255,221);padding: 10pt; border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
145 kit.getStyleSheet().addRule("ol {margin-left: 1cm}");
146 pnlMessage.setText("<html><body>"
147 + "<p class=\"warning-body\">"
148 + tr("<strong>Warning:</strong> JOSM does login <strong>once</strong> using a secure connection.")
149 + "</p>"
150 + "</body></html>");
151 pnl.add(pnlMessage, gc);
152
153 // filler - grab remaining space
154 gc.gridy = 4;
155 gc.gridwidth = 2;
156 gc.fill = GridBagConstraints.BOTH;
157 gc.weightx = 1.0;
158 gc.weighty = 1.0;
159 pnl.add(new JPanel(), gc);
160
161 return pnl;
162 }
163
164 protected JPanel buildPropertiesPanel() {
165 JPanel pnl = new JPanel(new BorderLayout());
166
167 JTabbedPane tpProperties = new JTabbedPane();
168 tpProperties.add(VerticallyScrollablePanel.embed(buildUserNamePasswordPanel()));
169 tpProperties.add(VerticallyScrollablePanel.embed(buildGrantsPanel()));
170 tpProperties.add(VerticallyScrollablePanel.embed(getAdvancedPropertiesPanel()));
171 tpProperties.setTitleAt(0, tr("Basic"));
172 tpProperties.setTitleAt(1, tr("Granted rights"));
173 tpProperties.setTitleAt(2, tr("Advanced OAuth properties"));
174
175 pnl.add(tpProperties, BorderLayout.CENTER);
176 return pnl;
177 }
178
179 /**
180 * Initializes the panel with values from the preferences
181 */
182 @Override
183 public void initFromPreferences(Preferences pref) {
184 super.initFromPreferences(pref);
185 CredentialsAgent cm = CredentialsManager.getInstance();
186 try {
187 PasswordAuthentication pa = cm.lookup(RequestorType.SERVER, OsmApi.getOsmApi().getHost());
188 if (pa == null) {
189 tfUserName.setText("");
190 tfPassword.setText("");
191 } else {
192 tfUserName.setText(pa.getUserName() == null ? "" : pa.getUserName());
193 tfPassword.setText(pa.getPassword() == null ? "" : String.valueOf(pa.getPassword()));
194 }
195 } catch(CredentialsAgentException e) {
196 e.printStackTrace();
197 tfUserName.setText("");
198 tfPassword.setText("");
199 }
200 }
201
202 /**
203 * Builds the panel with the action button for starting the authorisation
204 *
205 * @return
206 */
207 protected JPanel buildActionButtonPanel() {
208 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
209
210 RunAuthorisationAction runAuthorisationAction= new RunAuthorisationAction();
211 tfPassword.getDocument().addDocumentListener(runAuthorisationAction);
212 tfUserName.getDocument().addDocumentListener(runAuthorisationAction);
213 pnl.add(new SideButton(runAuthorisationAction));
214 return pnl;
215 }
216
217 /**
218 * Builds the panel which displays the generated Access Token.
219 *
220 * @return
221 */
222 protected JPanel buildResultsPanel() {
223 JPanel pnl = new JPanel(new GridBagLayout());
224 GridBagConstraints gc = new GridBagConstraints();
225 pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
226
227 // the message panel
228 gc.anchor = GridBagConstraints.NORTHWEST;
229 gc.fill = GridBagConstraints.HORIZONTAL;
230 gc.weightx = 1.0;
231 JMultilineLabel msg = new JMultilineLabel("");
232 msg.setFont(msg.getFont().deriveFont(Font.PLAIN));
233 String lbl = tr("Accept Access Token");
234 msg.setText(tr("<html>"
235 + "You have successfully retrieved an OAuth Access Token from the OSM website. "
236 + "Click on <strong>{0}</strong> to accept the token. JOSM will use it in "
237 + "subsequent requests to gain access to the OSM API."
238 + "</html>",lbl));
239 pnl.add(msg, gc);
240
241 // infos about the access token
242 gc.gridy = 1;
243 gc.insets = new Insets(5,0,0,0);
244 pnl.add(pnlAccessTokenInfo = new AccessTokenInfoPanel(), gc);
245
246 // the actions
247 JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
248 pnl1.add(new SideButton(new BackAction()));
249 pnl1.add(new SideButton(new TestAccessTokenAction()));
250 gc.gridy = 2;
251 pnl.add(pnl1, gc);
252
253 // filler - grab the remaining space
254 gc.gridy = 3;
255 gc.fill = GridBagConstraints.BOTH;
256 gc.weightx = 1.0;
257 gc.weighty = 1.0;
258 pnl.add(new JPanel(), gc);
259
260 return pnl;
261 }
262
263 protected void build() {
264 setLayout(new BorderLayout());
265 pnlPropertiesPanel = buildPropertiesPanel();
266 pnlActionButtonsPanel = buildActionButtonPanel();
267 pnlResult = buildResultsPanel();
268
269 prepareUIForEnteringRequest();
270 }
271
272 /**
273 * Prepares the UI for the first step in the automatic process: entering the authentication
274 * and authorisation parameters.
275 *
276 */
277 protected void prepareUIForEnteringRequest() {
278 removeAll();
279 add(pnlPropertiesPanel, BorderLayout.CENTER);
280 add(pnlActionButtonsPanel, BorderLayout.SOUTH);
281 pnlPropertiesPanel.revalidate();
282 pnlActionButtonsPanel.revalidate();
283 validate();
284 repaint();
285
286 setAccessToken(null);
287 }
288
289 /**
290 * Prepares the UI for the second step in the automatic process: displaying the access token
291 *
292 */
293 protected void prepareUIForResultDisplay() {
294 removeAll();
295 add(pnlResult, BorderLayout.CENTER);
296 validate();
297 repaint();
298 }
299
300 protected String getOsmUserName() {
301 return tfUserName.getText();
302 }
303
304 protected String getOsmPassword() {
305 return String.valueOf(tfPassword.getPassword());
306 }
307
308 public FullyAutomaticAuthorizationUI() {
309 build();
310 }
311
312 @Override
313 public boolean isSaveAccessTokenToPreferences() {
314 return pnlAccessTokenInfo.isSaveToPreferences();
315 }
316
317 @Override
318 protected void setAccessToken(OAuthToken accessToken) {
319 super.setAccessToken(accessToken);
320 pnlAccessTokenInfo.setAccessToken(accessToken);
321 }
322
323 /**
324 * Starts the authorisation process
325 */
326 class RunAuthorisationAction extends AbstractAction implements DocumentListener{
327 public RunAuthorisationAction() {
328 putValue(NAME, tr("Authorize now"));
329 putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth"));
330 putValue(SHORT_DESCRIPTION, tr("Click to redirect you to the authorization form on the JOSM web site"));
331 updateEnabledState();
332 }
333
334 public void actionPerformed(ActionEvent evt) {
335 Main.worker.submit(new FullyAutomaticAuthorisationTask(FullyAutomaticAuthorizationUI.this));
336 }
337
338 protected void updateEnabledState() {
339 setEnabled(valPassword.isValid() && valUserName.isValid());
340 }
341
342 public void changedUpdate(DocumentEvent e) {
343 updateEnabledState();
344 }
345
346 public void insertUpdate(DocumentEvent e) {
347 updateEnabledState();
348 }
349
350 public void removeUpdate(DocumentEvent e) {
351 updateEnabledState();
352 }
353 }
354
355 /**
356 * Action to go back to step 1 in the process
357 */
358 class BackAction extends AbstractAction {
359 public BackAction() {
360 putValue(NAME, tr("Back"));
361 putValue(SHORT_DESCRIPTION, tr("Run the automatic authorization steps again"));
362 putValue(SMALL_ICON, ImageProvider.get("dialogs", "previous"));
363 }
364
365 public void actionPerformed(ActionEvent arg0) {
366 prepareUIForEnteringRequest();
367 }
368 }
369
370 /**
371 * Action to test an access token.
372 */
373 class TestAccessTokenAction extends AbstractAction {
374 public TestAccessTokenAction() {
375 putValue(NAME, tr("Test Access Token"));
376 /* putValue(SHORT_DESCRIPTION, ""); */
377 putValue(SMALL_ICON, ImageProvider.get("about"));
378 }
379
380 public void actionPerformed(ActionEvent arg0) {
381 Main.worker.submit(new TestAccessTokenTask(
382 FullyAutomaticAuthorizationUI.this,
383 getApiUrl(),
384 getAdvancedPropertiesPanel().getAdvancedParameters(),
385 getAccessToken()
386 ));
387 }
388 }
389
390
391 static private class UserNameValidator extends AbstractTextComponentValidator {
392 public UserNameValidator(JTextComponent tc) {
393 super(tc);
394 }
395
396 @Override
397 public boolean isValid() {
398 return getComponent().getText().trim().length() > 0;
399 }
400
401 @Override
402 public void validate() {
403 if (isValid()) {
404 feedbackValid(tr("Please enter your OSM user name"));
405 } else {
406 feedbackInvalid(tr("The user name cannot be empty. Please enter your OSM user name"));
407 }
408 }
409 }
410
411 static private class PasswordValidator extends AbstractTextComponentValidator {
412
413 public PasswordValidator(JTextComponent tc) {
414 super(tc);
415 }
416
417 @Override
418 public boolean isValid() {
419 return getComponent().getText().trim().length() > 0;
420 }
421
422 @Override
423 public void validate() {
424 if (isValid()) {
425 feedbackValid(tr("Please enter your OSM password"));
426 } else {
427 feedbackInvalid(tr("The password cannot be empty. Please enter your OSM password"));
428 }
429 }
430 }
431
432 class FullyAutomaticAuthorisationTask extends PleaseWaitRunnable {
433 private boolean canceled;
434 private OsmOAuthAuthorizationClient authClient;
435
436 public FullyAutomaticAuthorisationTask(Component parent) {
437 super(parent, tr("Authorize JOSM to access the OSM API"), false /* don't ignore exceptions */);
438 }
439
440 @Override
441 protected void cancel() {
442 canceled = true;
443 }
444
445 @Override
446 protected void finish() {}
447
448 protected void alertAuthorisationFailed(OsmOAuthAuthorizationException e) {
449 HelpAwareOptionPane.showOptionDialog(
450 FullyAutomaticAuthorizationUI.this,
451 tr("<html>"
452 + "The automatic process for retrieving an OAuth Access Token<br>"
453 + "from the OSM server failed.<br><br>"
454 + "Please try again or choose another kind of authorization process,<br>"
455 + "i.e. semi-automatic or manual authorization."
456 +"</html>"),
457 tr("OAuth authorization failed"),
458 JOptionPane.ERROR_MESSAGE,
459 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed")
460 );
461 }
462
463 protected void alertInvalidLoginUrl() {
464 HelpAwareOptionPane.showOptionDialog(
465 FullyAutomaticAuthorizationUI.this,
466 tr("<html>"
467 + "The automatic process for retrieving an OAuth Access Token<br>"
468 + "from the OSM server failed because JOSM was not able to build<br>"
469 + "a valid login URL from the OAuth Authorize Endpoint URL ''{0}''.<br><br>"
470 + "Please check your advanced setting and try again."
471 + "</html>",
472 getAdvancedPropertiesPanel().getAdvancedParameters().getAuthoriseUrl()),
473 tr("OAuth authorization failed"),
474 JOptionPane.ERROR_MESSAGE,
475 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed")
476 );
477 }
478
479 protected void alertLoginFailed(OsmLoginFailedException e) {
480 String loginUrl = null;
481 try {
482 loginUrl = authClient.buildOsmLoginUrl();
483 } catch(OsmOAuthAuthorizationException e1) {
484 alertInvalidLoginUrl();
485 return;
486 }
487 HelpAwareOptionPane.showOptionDialog(
488 FullyAutomaticAuthorizationUI.this,
489 tr("<html>"
490 + "The automatic process for retrieving an OAuth Access Token<br>"
491 + "from the OSM server failed. JOSM failed to log into {0}<br>"
492 + "for user {1}.<br><br>"
493 + "Please check username and password and try again."
494 +"</html>",
495 loginUrl,
496 getOsmUserName()),
497 tr("OAuth authorization failed"),
498 JOptionPane.ERROR_MESSAGE,
499 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed")
500 );
501 }
502
503 protected void handleException(final OsmOAuthAuthorizationException e) {
504 Runnable r = new Runnable() {
505 public void run() {
506 if (e instanceof OsmLoginFailedException) {
507 alertLoginFailed((OsmLoginFailedException)e);
508 } else {
509 alertAuthorisationFailed(e);
510 }
511 }
512 };
513 e.printStackTrace();
514 if (SwingUtilities.isEventDispatchThread()) {
515 r.run();
516 } else {
517 SwingUtilities.invokeLater(r);
518 }
519 }
520
521 @Override
522 protected void realRun() throws SAXException, IOException, OsmTransferException {
523 try {
524 getProgressMonitor().setTicksCount(3);
525 authClient = new OsmOAuthAuthorizationClient(
526 getAdvancedPropertiesPanel().getAdvancedParameters()
527 );
528 OAuthToken requestToken = authClient.getRequestToken(
529 getProgressMonitor().createSubTaskMonitor(1, false)
530 );
531 getProgressMonitor().worked(1);
532 if (canceled)return;
533 authClient.authorise(
534 requestToken,
535 getOsmUserName(),
536 getOsmPassword(),
537 pnlOsmPrivileges.getPrivileges(),
538 getProgressMonitor().createSubTaskMonitor(1, false)
539 );
540 getProgressMonitor().worked(1);
541 if (canceled)return;
542 final OAuthToken accessToken = authClient.getAccessToken(
543 getProgressMonitor().createSubTaskMonitor(1,false)
544 );
545 getProgressMonitor().worked(1);
546 if (canceled)return;
547 Runnable r = new Runnable() {
548 public void run() {
549 prepareUIForResultDisplay();
550 setAccessToken(accessToken);
551 }
552 };
553 if (SwingUtilities.isEventDispatchThread()) {
554 r.run();
555 } else {
556 SwingUtilities.invokeLater(r);
557 }
558 } catch(final OsmOAuthAuthorizationException e) {
559 handleException(e);
560 }
561 }
562 }
563}
Note: See TracBrowser for help on using the repository browser.