001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.server;
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.beans.PropertyChangeEvent;
017import java.beans.PropertyChangeListener;
018
019import javax.swing.AbstractAction;
020import javax.swing.BorderFactory;
021import javax.swing.JCheckBox;
022import javax.swing.JLabel;
023import javax.swing.JPanel;
024
025import org.openstreetmap.josm.Main;
026import org.openstreetmap.josm.data.oauth.OAuthParameters;
027import org.openstreetmap.josm.data.oauth.OAuthToken;
028import org.openstreetmap.josm.gui.SideButton;
029import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel;
030import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
031import org.openstreetmap.josm.gui.oauth.TestAccessTokenTask;
032import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
033import org.openstreetmap.josm.gui.widgets.JosmTextField;
034import org.openstreetmap.josm.io.OsmApi;
035import org.openstreetmap.josm.io.auth.CredentialsManager;
036import org.openstreetmap.josm.tools.ImageProvider;
037
038/**
039 * The preferences panel for the OAuth preferences. This just a summary panel
040 * showing the current Access Token Key and Access Token Secret, if the
041 * user already has an Access Token.
042 *
043 * For initial authorisation see {@link OAuthAuthorizationWizard}.
044 *
045 */
046public class OAuthAuthenticationPreferencesPanel extends JPanel implements PropertyChangeListener {
047    private JPanel pnlAuthorisationMessage;
048    private NotYetAuthorisedPanel pnlNotYetAuthorised;
049    private AlreadyAuthorisedPanel pnlAlreadyAuthorised;
050    private AdvancedOAuthPropertiesPanel pnlAdvancedProperties;
051    private String apiUrl;
052    private JCheckBox cbShowAdvancedParameters;
053    private JCheckBox cbSaveToPreferences;
054
055    /**
056     * Builds the panel for entering the advanced OAuth parameters
057     *
058     * @return panel with advanced settings
059     */
060    protected JPanel buildAdvancedPropertiesPanel() {
061        JPanel pnl = new JPanel(new GridBagLayout());
062        GridBagConstraints gc = new GridBagConstraints();
063
064        gc.anchor = GridBagConstraints.NORTHWEST;
065        gc.fill = GridBagConstraints.HORIZONTAL;
066        gc.weightx = 0.0;
067        gc.insets = new Insets(0, 0, 0, 3);
068        pnl.add(cbShowAdvancedParameters = new JCheckBox(), gc);
069        cbShowAdvancedParameters.setSelected(false);
070        cbShowAdvancedParameters.addItemListener(
071                new ItemListener() {
072                    @Override
073                    public void itemStateChanged(ItemEvent evt) {
074                        pnlAdvancedProperties.setVisible(evt.getStateChange() == ItemEvent.SELECTED);
075                    }
076                }
077        );
078
079        gc.gridx = 1;
080        gc.weightx = 1.0;
081        JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters"));
082        lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
083        pnl.add(lbl, gc);
084
085        gc.gridy = 1;
086        gc.gridx = 1;
087        gc.insets = new Insets(3, 0, 3, 0);
088        gc.fill = GridBagConstraints.BOTH;
089        gc.weightx = 1.0;
090        gc.weighty = 1.0;
091        pnl.add(pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(), gc);
092        pnlAdvancedProperties.initFromPreferences(Main.pref);
093        pnlAdvancedProperties.setBorder(
094                BorderFactory.createCompoundBorder(
095                        BorderFactory.createLineBorder(Color.GRAY, 1),
096                        BorderFactory.createEmptyBorder(3, 3, 3, 3)
097                )
098        );
099        pnlAdvancedProperties.setVisible(false);
100        return pnl;
101    }
102
103    /**
104     * builds the UI
105     */
106    protected final void build() {
107        setLayout(new GridBagLayout());
108        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
109        GridBagConstraints gc = new GridBagConstraints();
110
111        // the panel for the OAuth parameters. pnlAuthorisationMessage is an
112        // empty panel. It is going to be filled later, depending on the
113        // current OAuth state in JOSM.
114        gc.fill = GridBagConstraints.BOTH;
115        gc.anchor = GridBagConstraints.NORTHWEST;
116        gc.weighty = 1.0;
117        gc.weightx = 1.0;
118        gc.insets = new Insets(10, 0, 0, 0);
119        add(pnlAuthorisationMessage = new JPanel(), gc);
120        pnlAuthorisationMessage.setLayout(new BorderLayout());
121
122        // create these two panels, they are going to be used later in refreshView
123        //
124        pnlAlreadyAuthorised = new AlreadyAuthorisedPanel();
125        pnlNotYetAuthorised = new NotYetAuthorisedPanel();
126    }
127
128    protected void refreshView() {
129        pnlAuthorisationMessage.removeAll();
130        if (OAuthAccessTokenHolder.getInstance().containsAccessToken()) {
131            pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER);
132            pnlAlreadyAuthorised.refreshView();
133            pnlAlreadyAuthorised.revalidate();
134        } else {
135            pnlAuthorisationMessage.add(pnlNotYetAuthorised, BorderLayout.CENTER);
136            pnlNotYetAuthorised.revalidate();
137        }
138        repaint();
139    }
140
141    /**
142     * Create the panel
143     */
144    public OAuthAuthenticationPreferencesPanel() {
145        build();
146        refreshView();
147    }
148
149    /**
150     * Sets the URL of the OSM API for which this panel is currently displaying OAuth properties.
151     *
152     * @param apiUrl the api URL
153     */
154    public void setApiUrl(String apiUrl) {
155        this.apiUrl = apiUrl;
156        pnlAdvancedProperties.setApiUrl(apiUrl);
157    }
158
159    /**
160     * Initializes the panel from preferences
161     */
162    public void initFromPreferences() {
163        setApiUrl(Main.pref.get("osm-server.url", OsmApi.DEFAULT_API_URL).trim());
164        refreshView();
165    }
166
167    /**
168     * Saves the current values to preferences
169     */
170    public void saveToPreferences() {
171        OAuthAccessTokenHolder.getInstance().setSaveToPreferences(cbSaveToPreferences.isSelected());
172        OAuthAccessTokenHolder.getInstance().save(Main.pref, CredentialsManager.getInstance());
173        pnlAdvancedProperties.rememberPreferences(Main.pref);
174    }
175
176    /**
177     * The preferences panel displayed if there is currently no Access Token available.
178     * This means that the user didn't run through the OAuth authorisation procedure yet.
179     *
180     */
181    private class NotYetAuthorisedPanel extends JPanel {
182        /**
183         * Constructs a new {@code NotYetAuthorisedPanel}.
184         */
185        NotYetAuthorisedPanel() {
186            build();
187        }
188
189        protected void build() {
190            setLayout(new GridBagLayout());
191            GridBagConstraints gc = new GridBagConstraints();
192
193            // A message explaining that the user isn't authorised yet
194            gc.anchor = GridBagConstraints.NORTHWEST;
195            gc.insets = new Insets(0, 0, 3, 0);
196            gc.fill = GridBagConstraints.HORIZONTAL;
197            gc.weightx = 1.0;
198            JMultilineLabel lbl;
199            add(lbl = new JMultilineLabel(
200                    tr("You do not have an Access Token yet to access the OSM server using OAuth. Please authorize first.")), gc);
201            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
202
203            // Action for authorising now
204            gc.gridy = 1;
205            gc.fill = GridBagConstraints.NONE;
206            gc.weightx = 0.0;
207            add(new SideButton(new AuthoriseNowAction()), gc);
208
209            // filler - grab remaining space
210            gc.gridy = 2;
211            gc.fill = GridBagConstraints.BOTH;
212            gc.weightx = 1.0;
213            gc.weighty = 1.0;
214            add(new JPanel(), gc);
215        }
216    }
217
218    /**
219     * The preferences panel displayed if there is currently an AccessToken available.
220     *
221     */
222    private class AlreadyAuthorisedPanel extends JPanel {
223        private JosmTextField tfAccessTokenKey;
224        private JosmTextField tfAccessTokenSecret;
225
226        protected void build() {
227            setLayout(new GridBagLayout());
228            GridBagConstraints gc = new GridBagConstraints();
229            gc.anchor = GridBagConstraints.NORTHWEST;
230            gc.insets = new Insets(0, 0, 3, 3);
231            gc.fill = GridBagConstraints.HORIZONTAL;
232            gc.weightx = 1.0;
233            gc.gridwidth = 2;
234            JMultilineLabel lbl;
235            add(lbl = new JMultilineLabel(tr("You already have an Access Token to access the OSM server using OAuth.")), gc);
236            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
237
238            // -- access token key
239            gc.gridy = 1;
240            gc.gridx = 0;
241            gc.gridwidth = 1;
242            gc.weightx = 0.0;
243            add(new JLabel(tr("Access Token Key:")), gc);
244
245            gc.gridx = 1;
246            gc.weightx = 1.0;
247            add(tfAccessTokenKey = new JosmTextField(), gc);
248            tfAccessTokenKey.setEditable(false);
249
250            // -- access token secret
251            gc.gridy = 2;
252            gc.gridx = 0;
253            gc.gridwidth = 1;
254            gc.weightx = 0.0;
255            add(new JLabel(tr("Access Token Secret:")), gc);
256
257            gc.gridx = 1;
258            gc.weightx = 1.0;
259            add(tfAccessTokenSecret = new JosmTextField(), gc);
260            tfAccessTokenSecret.setEditable(false);
261
262            // -- access token secret
263            gc.gridy = 3;
264            gc.gridx = 0;
265            gc.gridwidth = 2;
266            gc.weightx = 1.0;
267            add(cbSaveToPreferences = new JCheckBox(tr("Save to preferences")), gc);
268            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
269
270            // -- action buttons
271            JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
272            btns.add(new SideButton(new RenewAuthorisationAction()));
273            btns.add(new SideButton(new TestAuthorisationAction()));
274            gc.gridy = 4;
275            gc.gridx = 0;
276            gc.gridwidth = 2;
277            gc.weightx = 1.0;
278            add(btns, gc);
279
280            // the panel with the advanced options
281            gc.gridy = 5;
282            gc.gridx = 0;
283            gc.gridwidth = 2;
284            gc.weightx = 1.0;
285            add(buildAdvancedPropertiesPanel(), gc);
286
287            // filler - grab the remaining space
288            gc.gridy = 6;
289            gc.fill = GridBagConstraints.BOTH;
290            gc.weightx = 1.0;
291            gc.weighty = 1.0;
292            add(new JPanel(), gc);
293
294        }
295
296        public final void refreshView() {
297            String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey();
298            tfAccessTokenKey.setText(v == null ? "" : v);
299            v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret();
300            tfAccessTokenSecret.setText(v == null ? "" : v);
301            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
302        }
303
304        /**
305         * Constructs a new {@code AlreadyAuthorisedPanel}.
306         */
307        AlreadyAuthorisedPanel() {
308            build();
309            refreshView();
310        }
311    }
312
313    /**
314     * Action to authorise the current user
315     */
316    private class AuthoriseNowAction extends AbstractAction {
317        AuthoriseNowAction() {
318            putValue(NAME, tr("Authorize now"));
319            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process"));
320            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
321        }
322
323        @Override
324        public void actionPerformed(ActionEvent arg0) {
325            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
326                    OAuthAuthenticationPreferencesPanel.this,
327                    apiUrl
328            );
329            wizard.setVisible(true);
330            if (wizard.isCanceled()) return;
331            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
332            holder.setAccessToken(wizard.getAccessToken());
333            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
334            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
335            refreshView();
336        }
337    }
338
339    /**
340     * Launches the OAuthAuthorisationWizard to generate a new Access Token
341     */
342    private class RenewAuthorisationAction extends AbstractAction {
343        /**
344         * Constructs a new {@code RenewAuthorisationAction}.
345         */
346        RenewAuthorisationAction() {
347            putValue(NAME, tr("New Access Token"));
348            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process and generate a new Access Token"));
349            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
350
351        }
352
353        @Override
354        public void actionPerformed(ActionEvent arg0) {
355            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
356                    OAuthAuthenticationPreferencesPanel.this,
357                    apiUrl
358            );
359            wizard.setVisible(true);
360            if (wizard.isCanceled()) return;
361            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
362            holder.setAccessToken(wizard.getAccessToken());
363            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
364            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
365            refreshView();
366        }
367    }
368
369    /**
370     * Runs a test whether we can access the OSM server with the current Access Token
371     */
372    private class TestAuthorisationAction extends AbstractAction {
373        /**
374         * Constructs a new {@code TestAuthorisationAction}.
375         */
376        TestAuthorisationAction() {
377            putValue(NAME, tr("Test Access Token"));
378            putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token"));
379            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
380
381        }
382
383        @Override
384        public void actionPerformed(ActionEvent evt) {
385            OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken();
386            OAuthParameters parameters = OAuthParameters.createFromPreferences(Main.pref);
387            TestAccessTokenTask task = new TestAccessTokenTask(
388                    OAuthAuthenticationPreferencesPanel.this,
389                    apiUrl,
390                    parameters,
391                    token
392            );
393            Main.worker.submit(task);
394        }
395    }
396
397    @Override
398    public void propertyChange(PropertyChangeEvent evt) {
399        if (!evt.getPropertyName().equals(OsmApiUrlInputPanel.API_URL_PROP))
400            return;
401        setApiUrl((String) evt.getNewValue());
402    }
403}