001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.oauth;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.BorderLayout;
007import java.awt.Color;
008import java.awt.FlowLayout;
009import java.awt.Font;
010import java.awt.GridBagConstraints;
011import java.awt.GridBagLayout;
012import java.awt.Insets;
013import java.awt.event.ActionEvent;
014import java.awt.event.ItemEvent;
015import java.awt.event.ItemListener;
016import java.util.concurrent.Executor;
017
018import javax.swing.AbstractAction;
019import javax.swing.BorderFactory;
020import javax.swing.JCheckBox;
021import javax.swing.JLabel;
022import javax.swing.JPanel;
023
024import org.openstreetmap.josm.data.oauth.OAuthToken;
025import org.openstreetmap.josm.gui.SideButton;
026import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
027import org.openstreetmap.josm.gui.util.GuiHelper;
028import org.openstreetmap.josm.gui.widgets.HtmlPanel;
029import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
030import org.openstreetmap.josm.gui.widgets.JosmTextField;
031import org.openstreetmap.josm.tools.ImageProvider;
032import org.openstreetmap.josm.tools.OpenBrowser;
033
034/**
035 * This is the UI for running a semic-automic authorisation procedure.
036 *
037 * In contrast to the fully-automatic procedure the user is dispatched to an
038 * external browser for login and authorisation.
039 *
040 * @since 2746
041 */
042public class SemiAutomaticAuthorizationUI extends AbstractAuthorizationUI {
043    private AccessTokenInfoPanel pnlAccessTokenInfo;
044    private transient OAuthToken requestToken;
045
046    private RetrieveRequestTokenPanel pnlRetrieveRequestToken;
047    private RetrieveAccessTokenPanel pnlRetrieveAccessToken;
048    private ShowAccessTokenPanel pnlShowAccessToken;
049    private final transient Executor executor;
050
051    /**
052     * build the UI
053     */
054    protected final void build() {
055        setLayout(new BorderLayout());
056        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
057        pnlRetrieveRequestToken = new RetrieveRequestTokenPanel();
058        pnlRetrieveAccessToken = new RetrieveAccessTokenPanel();
059        pnlShowAccessToken = new ShowAccessTokenPanel();
060        add(pnlRetrieveRequestToken, BorderLayout.CENTER);
061    }
062
063    /**
064     * Constructs a new {@code SemiAutomaticAuthorizationUI} for the given API URL.
065     * @param apiUrl The OSM API URL
066     * @param executor the executor used for running the HTTP requests for the authorization
067     * @since 5422
068     */
069    public SemiAutomaticAuthorizationUI(String apiUrl, Executor executor) {
070        super(apiUrl);
071        this.executor = executor;
072        build();
073    }
074
075    @Override
076    public boolean isSaveAccessTokenToPreferences() {
077        return pnlAccessTokenInfo.isSaveToPreferences();
078    }
079
080    protected void transitionToRetrieveAccessToken() {
081        OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient(
082                getAdvancedPropertiesPanel().getAdvancedParameters()
083        );
084        String authoriseUrl = client.getAuthoriseUrl(requestToken);
085        OpenBrowser.displayUrl(authoriseUrl);
086
087        removeAll();
088        pnlRetrieveAccessToken.setAuthoriseUrl(authoriseUrl);
089        add(pnlRetrieveAccessToken, BorderLayout.CENTER);
090        pnlRetrieveAccessToken.invalidate();
091        validate();
092        repaint();
093    }
094
095    protected void transitionToRetrieveRequestToken() {
096        requestToken = null;
097        setAccessToken(null);
098        removeAll();
099        add(pnlRetrieveRequestToken, BorderLayout.CENTER);
100        pnlRetrieveRequestToken.invalidate();
101        validate();
102        repaint();
103    }
104
105    protected void transitionToShowAccessToken() {
106        removeAll();
107        add(pnlShowAccessToken, BorderLayout.CENTER);
108        pnlShowAccessToken.invalidate();
109        validate();
110        repaint();
111        pnlShowAccessToken.setAccessToken(getAccessToken());
112    }
113
114    /**
115     * This is the panel displayed in the first step of the semi-automatic authorisation process.
116     */
117    private class RetrieveRequestTokenPanel extends JPanel {
118
119        /**
120         * Constructs a new {@code RetrieveRequestTokenPanel}.
121         */
122        RetrieveRequestTokenPanel() {
123            build();
124        }
125
126        protected JPanel buildAdvancedParametersPanel() {
127            JPanel pnl = new JPanel(new GridBagLayout());
128            GridBagConstraints gc = new GridBagConstraints();
129
130            gc.anchor = GridBagConstraints.NORTHWEST;
131            gc.fill = GridBagConstraints.HORIZONTAL;
132            gc.weightx = 0.0;
133            gc.insets = new Insets(0, 0, 0, 3);
134            JCheckBox cbShowAdvancedParameters = new JCheckBox();
135            pnl.add(cbShowAdvancedParameters, gc);
136            cbShowAdvancedParameters.setSelected(false);
137            cbShowAdvancedParameters.addItemListener(
138                    new ItemListener() {
139                        @Override
140                        public void itemStateChanged(ItemEvent evt) {
141                            getAdvancedPropertiesPanel().setVisible(evt.getStateChange() == ItemEvent.SELECTED);
142                        }
143                    }
144            );
145
146            gc.gridx = 1;
147            gc.weightx = 1.0;
148            JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters"));
149            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
150            pnl.add(lbl, gc);
151
152            gc.gridy = 1;
153            gc.gridx = 1;
154            gc.insets = new Insets(3, 0, 3, 0);
155            gc.fill = GridBagConstraints.BOTH;
156            gc.weightx = 1.0;
157            gc.weighty = 1.0;
158            pnl.add(getAdvancedPropertiesPanel(), gc);
159            getAdvancedPropertiesPanel().setBorder(
160                    BorderFactory.createCompoundBorder(
161                            BorderFactory.createLineBorder(Color.GRAY, 1),
162                            BorderFactory.createEmptyBorder(3, 3, 3, 3)
163                    )
164            );
165            getAdvancedPropertiesPanel().setVisible(false);
166            return pnl;
167        }
168
169        protected JPanel buildCommandPanel() {
170            JPanel pnl = new JPanel(new GridBagLayout());
171            GridBagConstraints gc = new GridBagConstraints();
172
173            gc.anchor = GridBagConstraints.NORTHWEST;
174            gc.fill = GridBagConstraints.BOTH;
175            gc.weightx = 1.0;
176            gc.weighty = 1.0;
177            gc.insets = new Insets(0, 0, 0, 3);
178
179
180            HtmlPanel h = new HtmlPanel();
181            h.setText(tr("<html>"
182                    + "Please click on <strong>{0}</strong> to retrieve an OAuth Request Token from "
183                    + "''{1}''.</html>",
184                    tr("Retrieve Request Token"),
185                    getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl()
186            ));
187            pnl.add(h, gc);
188
189            JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
190            pnl1.add(new SideButton(new RetrieveRequestTokenAction()));
191            gc.fill = GridBagConstraints.HORIZONTAL;
192            gc.weightx = 1.0;
193            gc.gridy = 1;
194            pnl.add(pnl1, gc);
195            return pnl;
196
197        }
198
199        protected final void build() {
200            setLayout(new BorderLayout(0, 5));
201            JLabel lbl = new JLabel(tr("<html>Step 1/3: Retrieve an OAuth Request Token</html>"));
202            lbl.setFont(lbl.getFont().deriveFont(16f));
203            add(lbl, BorderLayout.NORTH);
204            add(buildAdvancedParametersPanel(), BorderLayout.CENTER);
205            add(buildCommandPanel(), BorderLayout.SOUTH);
206        }
207    }
208
209    /**
210     * This is the panel displayed in the second step of the semi-automatic authorization process.
211     */
212    private class RetrieveAccessTokenPanel extends JPanel {
213
214        private JosmTextField tfAuthoriseUrl;
215
216        /**
217         * Constructs a new {@code RetrieveAccessTokenPanel}.
218         */
219        RetrieveAccessTokenPanel() {
220            build();
221        }
222
223        protected JPanel buildTitlePanel() {
224            JPanel pnl = new JPanel(new BorderLayout());
225            JLabel lbl = new JLabel(tr("<html>Step 2/3: Authorize and retrieve an Access Token</html>"));
226            lbl.setFont(lbl.getFont().deriveFont(16f));
227            pnl.add(lbl, BorderLayout.CENTER);
228            return pnl;
229        }
230
231        protected JPanel buildContentPanel() {
232            JPanel pnl = new JPanel(new GridBagLayout());
233            GridBagConstraints gc = new GridBagConstraints();
234
235            gc.anchor = GridBagConstraints.NORTHWEST;
236            gc.fill = GridBagConstraints.HORIZONTAL;
237            gc.weightx = 1.0;
238            gc.gridwidth = 2;
239            HtmlPanel html = new HtmlPanel();
240            html.setText(tr("<html>"
241                    + "JOSM successfully retrieved a Request Token. "
242                    + "JOSM is now launching an authorization page in an external browser. "
243                    + "Please login with your OSM username and password and follow the instructions "
244                    + "to authorize the Request Token. Then switch back to this dialog and click on "
245                    + "<strong>{0}</strong><br><br>"
246                    + "If launching the external browser fails you can copy the following authorize URL "
247                    + "and paste it into the address field of your browser.</html>",
248                    tr("Request Access Token")
249            ));
250            pnl.add(html, gc);
251
252            gc.gridx = 0;
253            gc.gridy = 1;
254            gc.weightx = 0.0;
255            gc.gridwidth = 1;
256            pnl.add(new JLabel(tr("Authorize URL:")), gc);
257
258            gc.gridx = 1;
259            gc.weightx = 1.0;
260            pnl.add(tfAuthoriseUrl = new JosmTextField(), gc);
261            tfAuthoriseUrl.setEditable(false);
262
263            return pnl;
264        }
265
266        protected JPanel buildActionPanel() {
267            JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
268
269            pnl.add(new SideButton(new BackAction()));
270            pnl.add(new SideButton(new RetrieveAccessTokenAction()));
271            return pnl;
272        }
273
274        protected final void build() {
275            setLayout(new BorderLayout());
276            add(buildTitlePanel(), BorderLayout.NORTH);
277            add(buildContentPanel(), BorderLayout.CENTER);
278            add(buildActionPanel(), BorderLayout.SOUTH);
279        }
280
281        public void setAuthoriseUrl(String url) {
282            tfAuthoriseUrl.setText(url);
283        }
284
285        /**
286         * Action to go back to step 1 in the process
287         */
288        class BackAction extends AbstractAction {
289            BackAction() {
290                putValue(NAME, tr("Back"));
291                putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3"));
292                putValue(SMALL_ICON, ImageProvider.get("dialogs", "previous"));
293            }
294
295            @Override
296            public void actionPerformed(ActionEvent arg0) {
297                transitionToRetrieveRequestToken();
298            }
299        }
300    }
301
302    /**
303     * Displays the retrieved Access Token in step 3.
304     */
305    class ShowAccessTokenPanel extends JPanel {
306
307        /**
308         * Constructs a new {@code ShowAccessTokenPanel}.
309         */
310        ShowAccessTokenPanel() {
311            build();
312        }
313
314        protected JPanel buildTitlePanel() {
315            JPanel pnl = new JPanel(new BorderLayout());
316            JLabel lbl = new JLabel(tr("<html>Step 3/3: Successfully retrieved an Access Token</html>"));
317            lbl.setFont(lbl.getFont().deriveFont(16f));
318            pnl.add(lbl, BorderLayout.CENTER);
319            return pnl;
320        }
321
322        protected JPanel buildContentPanel() {
323            JPanel pnl = new JPanel(new GridBagLayout());
324            GridBagConstraints gc = new GridBagConstraints();
325
326            gc.anchor = GridBagConstraints.NORTHWEST;
327            gc.fill = GridBagConstraints.HORIZONTAL;
328            gc.weightx = 1.0;
329            HtmlPanel html = new HtmlPanel();
330            html.setText(tr("<html>"
331                    + "JOSM has successfully retrieved an Access Token. "
332                    + "You can now accept this token. JOSM will use it in the future for authentication "
333                    + "and authorization to the OSM server.<br><br>"
334                    + "The access token is: </html>"
335            ));
336            pnl.add(html, gc);
337
338            gc.gridx = 0;
339            gc.gridy = 1;
340            gc.weightx = 1.0;
341            gc.gridwidth = 1;
342            pnl.add(pnlAccessTokenInfo = new AccessTokenInfoPanel(), gc);
343            pnlAccessTokenInfo.setSaveToPreferences(
344                    OAuthAccessTokenHolder.getInstance().isSaveToPreferences()
345            );
346            return pnl;
347        }
348
349        protected JPanel buildActionPanel() {
350            JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
351            pnl.add(new SideButton(new RestartAction()));
352            pnl.add(new SideButton(new TestAccessTokenAction()));
353            return pnl;
354        }
355
356        protected final void build() {
357            setLayout(new BorderLayout());
358            add(buildTitlePanel(), BorderLayout.NORTH);
359            add(buildContentPanel(), BorderLayout.CENTER);
360            add(buildActionPanel(), BorderLayout.SOUTH);
361        }
362
363        /**
364         * Action to go back to step 1 in the process
365         */
366        class RestartAction extends AbstractAction {
367            RestartAction() {
368                putValue(NAME, tr("Restart"));
369                putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3"));
370                putValue(SMALL_ICON, ImageProvider.get("dialogs", "previous"));
371            }
372
373            @Override
374            public void actionPerformed(ActionEvent arg0) {
375                transitionToRetrieveRequestToken();
376            }
377        }
378
379        public void setAccessToken(OAuthToken accessToken) {
380            pnlAccessTokenInfo.setAccessToken(accessToken);
381        }
382    }
383
384    /**
385     * Action for retrieving a request token
386     */
387    class RetrieveRequestTokenAction extends AbstractAction {
388
389        RetrieveRequestTokenAction() {
390            putValue(NAME, tr("Retrieve Request Token"));
391            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
392            putValue(SHORT_DESCRIPTION, tr("Click to retrieve a Request Token"));
393        }
394
395        @Override
396        public void actionPerformed(ActionEvent evt) {
397            final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask(
398                    SemiAutomaticAuthorizationUI.this,
399                    getAdvancedPropertiesPanel().getAdvancedParameters()
400            );
401            executor.execute(task);
402            Runnable r  = new Runnable() {
403                @Override
404                public void run() {
405                    if (task.isCanceled()) return;
406                    if (task.getRequestToken() == null) return;
407                    requestToken = task.getRequestToken();
408                    GuiHelper.runInEDT(new Runnable() {
409                        @Override
410                        public void run() {
411                            transitionToRetrieveAccessToken();
412                        }
413                    });
414                }
415            };
416            executor.execute(r);
417        }
418    }
419
420    /**
421     * Action for retrieving an Access Token
422     */
423    class RetrieveAccessTokenAction extends AbstractAction {
424
425        RetrieveAccessTokenAction() {
426            putValue(NAME, tr("Retrieve Access Token"));
427            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
428            putValue(SHORT_DESCRIPTION, tr("Click to retrieve an Access Token"));
429        }
430
431        @Override
432        public void actionPerformed(ActionEvent evt) {
433            final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask(
434                    SemiAutomaticAuthorizationUI.this,
435                    getAdvancedPropertiesPanel().getAdvancedParameters(),
436                    requestToken
437            );
438            executor.execute(task);
439            Runnable r  = new Runnable() {
440                @Override
441                public void run() {
442                    if (task.isCanceled()) return;
443                    if (task.getAccessToken() == null) return;
444                    GuiHelper.runInEDT(new Runnable() {
445                        @Override
446                        public void run() {
447                            setAccessToken(task.getAccessToken());
448                            transitionToShowAccessToken();
449                        }
450                    });
451                }
452            };
453            executor.execute(r);
454        }
455    }
456
457    /**
458     * Action for testing an Access Token
459     */
460    class TestAccessTokenAction extends AbstractAction {
461
462        TestAccessTokenAction() {
463            putValue(NAME, tr("Test Access Token"));
464            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
465            putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
466        }
467
468        @Override
469        public void actionPerformed(ActionEvent evt) {
470            TestAccessTokenTask task = new TestAccessTokenTask(
471                    SemiAutomaticAuthorizationUI.this,
472                    getApiUrl(),
473                    getAdvancedPropertiesPanel().getAdvancedParameters(),
474                    getAccessToken()
475            );
476            executor.execute(task);
477        }
478    }
479}