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;
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 *
046 */
047public class OAuthAuthenticationPreferencesPanel extends JPanel implements PropertyChangeListener {
048    private JPanel pnlAuthorisationMessage;
049    private NotYetAuthorisedPanel pnlNotYetAuthorised;
050    private AlreadyAuthorisedPanel pnlAlreadyAuthorised;
051    private AdvancedOAuthPropertiesPanel pnlAdvancedProperties;
052    private String apiUrl;
053    private JCheckBox cbShowAdvancedParameters;
054    private JCheckBox cbSaveToPreferences;
055
056    /**
057     * Builds the panel for entering the advanced OAuth parameters
058     *
059     * @return panel with advanced settings
060     */
061    protected JPanel buildAdvancedPropertiesPanel() {
062        JPanel pnl = new JPanel(new GridBagLayout());
063        GridBagConstraints gc = new GridBagConstraints();
064
065        gc.anchor = GridBagConstraints.NORTHWEST;
066        gc.fill = GridBagConstraints.HORIZONTAL;
067        gc.weightx = 0.0;
068        gc.insets = new Insets(0, 0, 0, 3);
069        pnl.add(cbShowAdvancedParameters = new JCheckBox(), gc);
070        cbShowAdvancedParameters.setSelected(false);
071        cbShowAdvancedParameters.addItemListener(
072                new ItemListener() {
073                    @Override
074                    public void itemStateChanged(ItemEvent evt) {
075                        pnlAdvancedProperties.setVisible(evt.getStateChange() == ItemEvent.SELECTED);
076                    }
077                }
078        );
079
080        gc.gridx = 1;
081        gc.weightx = 1.0;
082        JMultilineLabel lbl = new JMultilineLabel(tr("Display Advanced OAuth Parameters"));
083        lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
084        pnl.add(lbl, gc);
085
086        gc.gridy = 1;
087        gc.gridx = 1;
088        gc.insets = new Insets(3, 0, 3, 0);
089        gc.fill = GridBagConstraints.BOTH;
090        gc.weightx = 1.0;
091        gc.weighty = 1.0;
092        pnl.add(pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(), gc);
093        pnlAdvancedProperties.initFromPreferences(Main.pref);
094        pnlAdvancedProperties.setBorder(
095                BorderFactory.createCompoundBorder(
096                        BorderFactory.createLineBorder(Color.GRAY, 1),
097                        BorderFactory.createEmptyBorder(3, 3, 3, 3)
098                )
099        );
100        pnlAdvancedProperties.setVisible(false);
101        return pnl;
102    }
103
104    /**
105     * builds the UI
106     */
107    protected final void build() {
108        setLayout(new GridBagLayout());
109        setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
110        GridBagConstraints gc = new GridBagConstraints();
111
112        // the panel for the OAuth parameters. pnlAuthorisationMessage is an
113        // empty panel. It is going to be filled later, depending on the
114        // current OAuth state in JOSM.
115        gc.fill = GridBagConstraints.BOTH;
116        gc.anchor = GridBagConstraints.NORTHWEST;
117        gc.weighty = 1.0;
118        gc.weightx = 1.0;
119        gc.insets = new Insets(10, 0, 0, 0);
120        add(pnlAuthorisationMessage = new JPanel(new BorderLayout()), gc);
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(OsmApi.getOsmApi().getServerUrl().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                    Main.worker);
329            try {
330                wizard.showDialog();
331            } catch (UserCancelException ignore) {
332                Main.trace(ignore.toString());
333                return;
334            }
335            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
336            refreshView();
337        }
338    }
339
340    /**
341     * Launches the OAuthAuthorisationWizard to generate a new Access Token
342     */
343    private class RenewAuthorisationAction extends AuthoriseNowAction {
344        /**
345         * Constructs a new {@code RenewAuthorisationAction}.
346         */
347        RenewAuthorisationAction() {
348            putValue(NAME, tr("New Access Token"));
349            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process and generate a new Access Token"));
350            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
351        }
352    }
353
354    /**
355     * Runs a test whether we can access the OSM server with the current Access Token
356     */
357    private class TestAuthorisationAction extends AbstractAction {
358        /**
359         * Constructs a new {@code TestAuthorisationAction}.
360         */
361        TestAuthorisationAction() {
362            putValue(NAME, tr("Test Access Token"));
363            putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token"));
364            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth-small"));
365
366        }
367
368        @Override
369        public void actionPerformed(ActionEvent evt) {
370            OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken();
371            OAuthParameters parameters = OAuthParameters.createFromPreferences(Main.pref);
372            TestAccessTokenTask task = new TestAccessTokenTask(
373                    OAuthAuthenticationPreferencesPanel.this,
374                    apiUrl,
375                    parameters,
376                    token
377            );
378            Main.worker.submit(task);
379        }
380    }
381
382    @Override
383    public void propertyChange(PropertyChangeEvent evt) {
384        if (!evt.getPropertyName().equals(OsmApiUrlInputPanel.API_URL_PROP))
385            return;
386        setApiUrl((String) evt.getNewValue());
387    }
388}