source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorisationUI.java@ 2801

Last change on this file since 2801 was 2801, checked in by stoecker, 14 years ago

fixed line endings of recent checkins

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