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