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

Last change on this file since 6296 was 6296, checked in by Don-vip, 5 years ago

Sonar/Findbugs - Avoid commented-out lines of code, javadoc

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