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

Last change on this file since 4253 was 4253, checked in by stoecker, 13 years ago

see #6227 - fix wrong quote escaping in strings

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