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