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

Revision 4690, 20.5 KB checked in by stoecker, 5 months ago (diff)

see #7086 - fix passing auth information to wrong server

  • Property svn:eol-style set to native
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.