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 final AccessTokenInfoPanel pnlAccessTokenInfo = new AccessTokenInfoPanel();
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    static class StepLabel extends JLabel {
115        StepLabel(String text) {
116            super(text);
117            setFont(getFont().deriveFont(16f));
118        }
119    }
120
121    /**
122     * This is the panel displayed in the first step of the semi-automatic authorisation process.
123     */
124    private class RetrieveRequestTokenPanel extends JPanel {
125
126        /**
127         * Constructs a new {@code RetrieveRequestTokenPanel}.
128         */
129        RetrieveRequestTokenPanel() {
130            build();
131        }
132
133        protected JPanel buildAdvancedParametersPanel() {
134            JPanel pnl = new JPanel(new GridBagLayout());
135            GridBagConstraints gc = new GridBagConstraints();
136
137            gc.anchor = GridBagConstraints.NORTHWEST;
138            gc.fill = GridBagConstraints.HORIZONTAL;
139            gc.weightx = 0.0;
140            gc.insets = new Insets(0, 0, 0, 3);
141            JCheckBox cbShowAdvancedParameters = new JCheckBox();
142            pnl.add(cbShowAdvancedParameters, gc);
143            cbShowAdvancedParameters.setSelected(false);
144            cbShowAdvancedParameters.addItemListener(
145                    new ItemListener() {
146                        @Override
147                        public void itemStateChanged(ItemEvent evt) {
148                            getAdvancedPropertiesPanel().setVisible(evt.getStateChange() == ItemEvent.SELECTED);
149                        }
150                    }
151            );
152
153            gc.gridx = 1;
154            gc.weightx = 1.0;
155            JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters"));
156            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
157            pnl.add(lbl, gc);
158
159            gc.gridy = 1;
160            gc.gridx = 1;
161            gc.insets = new Insets(3, 0, 3, 0);
162            gc.fill = GridBagConstraints.BOTH;
163            gc.weightx = 1.0;
164            gc.weighty = 1.0;
165            pnl.add(getAdvancedPropertiesPanel(), gc);
166            getAdvancedPropertiesPanel().setBorder(
167                    BorderFactory.createCompoundBorder(
168                            BorderFactory.createLineBorder(Color.GRAY, 1),
169                            BorderFactory.createEmptyBorder(3, 3, 3, 3)
170                    )
171            );
172            getAdvancedPropertiesPanel().setVisible(false);
173            return pnl;
174        }
175
176        protected JPanel buildCommandPanel() {
177            JPanel pnl = new JPanel(new GridBagLayout());
178            GridBagConstraints gc = new GridBagConstraints();
179
180            gc.anchor = GridBagConstraints.NORTHWEST;
181            gc.fill = GridBagConstraints.BOTH;
182            gc.weightx = 1.0;
183            gc.weighty = 1.0;
184            gc.insets = new Insets(0, 0, 0, 3);
185
186
187            HtmlPanel h = new HtmlPanel();
188            h.setText(tr("<html>"
189                    + "Please click on <strong>{0}</strong> to retrieve an OAuth Request Token from "
190                    + "''{1}''.</html>",
191                    tr("Retrieve Request Token"),
192                    getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl()
193            ));
194            pnl.add(h, gc);
195
196            JPanel pnl1 = new JPanel(new FlowLayout(FlowLayout.LEFT));
197            pnl1.add(new SideButton(new RetrieveRequestTokenAction()));
198            gc.fill = GridBagConstraints.HORIZONTAL;
199            gc.weightx = 1.0;
200            gc.gridy = 1;
201            pnl.add(pnl1, gc);
202            return pnl;
203
204        }
205
206        protected final void build() {
207            setLayout(new BorderLayout(0, 5));
208            add(new StepLabel(tr("<html>Step 1/3: Retrieve an OAuth Request Token</html>")), BorderLayout.NORTH);
209            add(buildAdvancedParametersPanel(), BorderLayout.CENTER);
210            add(buildCommandPanel(), BorderLayout.SOUTH);
211        }
212    }
213
214    /**
215     * This is the panel displayed in the second step of the semi-automatic authorization process.
216     */
217    private class RetrieveAccessTokenPanel extends JPanel {
218
219        private final JosmTextField tfAuthoriseUrl = new JosmTextField();
220
221        /**
222         * Constructs a new {@code RetrieveAccessTokenPanel}.
223         */
224        RetrieveAccessTokenPanel() {
225            build();
226        }
227
228        protected JPanel buildTitlePanel() {
229            JPanel pnl = new JPanel(new BorderLayout());
230            pnl.add(new StepLabel(tr("<html>Step 2/3: Authorize and retrieve an Access Token</html>")), BorderLayout.CENTER);
231            return pnl;
232        }
233
234        protected JPanel buildContentPanel() {
235            JPanel pnl = new JPanel(new GridBagLayout());
236            GridBagConstraints gc = new GridBagConstraints();
237
238            gc.anchor = GridBagConstraints.NORTHWEST;
239            gc.fill = GridBagConstraints.HORIZONTAL;
240            gc.weightx = 1.0;
241            gc.gridwidth = 2;
242            HtmlPanel html = new HtmlPanel();
243            html.setText(tr("<html>"
244                    + "JOSM successfully retrieved a Request Token. "
245                    + "JOSM is now launching an authorization page in an external browser. "
246                    + "Please login with your OSM username and password and follow the instructions "
247                    + "to authorize the Request Token. Then switch back to this dialog and click on "
248                    + "<strong>{0}</strong><br><br>"
249                    + "If launching the external browser fails you can copy the following authorize URL "
250                    + "and paste it into the address field of your browser.</html>",
251                    tr("Request Access Token")
252            ));
253            pnl.add(html, gc);
254
255            gc.gridx = 0;
256            gc.gridy = 1;
257            gc.weightx = 0.0;
258            gc.gridwidth = 1;
259            pnl.add(new JLabel(tr("Authorize URL:")), gc);
260
261            gc.gridx = 1;
262            gc.weightx = 1.0;
263            pnl.add(tfAuthoriseUrl, gc);
264            tfAuthoriseUrl.setEditable(false);
265
266            return pnl;
267        }
268
269        protected JPanel buildActionPanel() {
270            JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
271            pnl.add(new SideButton(new BackAction()));
272            pnl.add(new SideButton(new RetrieveAccessTokenAction()));
273            return pnl;
274        }
275
276        protected final void build() {
277            setLayout(new BorderLayout());
278            add(buildTitlePanel(), BorderLayout.NORTH);
279            add(buildContentPanel(), BorderLayout.CENTER);
280            add(buildActionPanel(), BorderLayout.SOUTH);
281        }
282
283        public void setAuthoriseUrl(String url) {
284            tfAuthoriseUrl.setText(url);
285        }
286
287        /**
288         * Action to go back to step 1 in the process
289         */
290        class BackAction extends AbstractAction {
291            BackAction() {
292                putValue(NAME, tr("Back"));
293                putValue(SHORT_DESCRIPTION, tr("Go back to step 1/3"));
294                putValue(SMALL_ICON, ImageProvider.get("dialogs", "previous"));
295            }
296
297            @Override
298            public void actionPerformed(ActionEvent arg0) {
299                transitionToRetrieveRequestToken();
300            }
301        }
302    }
303
304    /**
305     * Displays the retrieved Access Token in step 3.
306     */
307    class ShowAccessTokenPanel extends JPanel {
308
309        /**
310         * Constructs a new {@code ShowAccessTokenPanel}.
311         */
312        ShowAccessTokenPanel() {
313            build();
314        }
315
316        protected JPanel buildTitlePanel() {
317            JPanel pnl = new JPanel(new BorderLayout());
318            pnl.add(new StepLabel(tr("<html>Step 3/3: Successfully retrieved an Access Token</html>")), 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, 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}