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.io.OsmApi;
033import org.openstreetmap.josm.io.auth.CredentialsManager;
034import org.openstreetmap.josm.tools.ImageProvider;
035import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
036import org.openstreetmap.josm.gui.widgets.JosmTextField;
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        protected void build() {
183            setLayout(new GridBagLayout());
184            GridBagConstraints gc = new GridBagConstraints();
185
186            // A message explaining that the user isn't authorised yet
187            gc.anchor = GridBagConstraints.NORTHWEST;
188            gc.insets = new Insets(0,0,3,0);
189            gc.fill = GridBagConstraints.HORIZONTAL;
190            gc.weightx = 1.0;
191            JMultilineLabel lbl;
192            add(lbl = new JMultilineLabel(tr("You do not have an Access Token yet to access the OSM server using OAuth. Please authorize first.")), gc);
193            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
194
195            // Action for authorising now
196            gc.gridy = 1;
197            gc.fill = GridBagConstraints.NONE;
198            gc.weightx = 0.0;
199            add(new SideButton(new AuthoriseNowAction()), gc);
200
201            // filler - grab remaining space
202            gc.gridy = 2;
203            gc.fill = GridBagConstraints.BOTH;
204            gc.weightx = 1.0;
205            gc.weighty = 1.0;
206            add(new JPanel(), gc);
207        }
208
209        public NotYetAuthorisedPanel() {
210            build();
211        }
212    }
213
214    /**
215     * The preferences panel displayed if there is currently an AccessToken available.
216     *
217     */
218    private class AlreadyAuthorisedPanel extends JPanel {
219        private JosmTextField tfAccessTokenKey;
220        private JosmTextField tfAccessTokenSecret;
221
222        protected void build() {
223            setLayout(new GridBagLayout());
224            GridBagConstraints gc = new GridBagConstraints();
225            gc.anchor = GridBagConstraints.NORTHWEST;
226            gc.insets = new Insets(0,0,3,3);
227            gc.fill = GridBagConstraints.HORIZONTAL;
228            gc.weightx = 1.0;
229            gc.gridwidth = 2;
230            JMultilineLabel lbl;
231            add(lbl = new JMultilineLabel(tr("You already have an Access Token to access the OSM server using OAuth.")), gc);
232            lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
233
234            // -- access token key
235            gc.gridy = 1;
236            gc.gridx = 0;
237            gc.gridwidth = 1;
238            gc.weightx = 0.0;
239            add(new JLabel(tr("Access Token Key:")), gc);
240
241            gc.gridx = 1;
242            gc.weightx = 1.0;
243            add(tfAccessTokenKey = new JosmTextField(), gc);
244            tfAccessTokenKey.setEditable(false);
245
246            // -- access token secret
247            gc.gridy = 2;
248            gc.gridx = 0;
249            gc.gridwidth = 1;
250            gc.weightx = 0.0;
251            add(new JLabel(tr("Access Token Secret:")), gc);
252
253            gc.gridx = 1;
254            gc.weightx = 1.0;
255            add(tfAccessTokenSecret = new JosmTextField(), gc);
256            tfAccessTokenSecret.setEditable(false);
257
258            // -- access token secret
259            gc.gridy = 3;
260            gc.gridx = 0;
261            gc.gridwidth = 2;
262            gc.weightx = 1.0;
263            add(cbSaveToPreferences = new JCheckBox(tr("Save to preferences")), gc);
264            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
265
266            // -- action buttons
267            JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
268            btns.add(new SideButton(new RenewAuthorisationAction()));
269            btns.add(new SideButton(new TestAuthorisationAction()));
270            gc.gridy = 4;
271            gc.gridx = 0;
272            gc.gridwidth = 2;
273            gc.weightx = 1.0;
274            add(btns, gc);
275
276            // the panel with the advanced options
277            gc.gridy = 5;
278            gc.gridx = 0;
279            gc.gridwidth = 2;
280            gc.weightx = 1.0;
281            add(buildAdvancedPropertiesPanel(), gc);
282
283            // filler - grab the remaining space
284            gc.gridy = 6;
285            gc.fill = GridBagConstraints.BOTH;
286            gc.weightx = 1.0;
287            gc.weighty = 1.0;
288            add(new JPanel(), gc);
289
290        }
291
292        public final void refreshView() {
293            String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey();
294            tfAccessTokenKey.setText(v == null? "" : v);
295            v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret();
296            tfAccessTokenSecret.setText(v == null? "" : v);
297            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
298        }
299
300        public AlreadyAuthorisedPanel() {
301            build();
302            refreshView();
303        }
304    }
305
306    /**
307     * Action to authorise the current user
308     */
309    private class AuthoriseNowAction extends AbstractAction {
310        public AuthoriseNowAction() {
311            putValue(NAME, tr("Authorize now"));
312            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process"));
313            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth"));
314
315        }
316        @Override
317        public void actionPerformed(ActionEvent arg0) {
318            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
319                    OAuthAuthenticationPreferencesPanel.this,
320                    apiUrl
321            );
322            wizard.setVisible(true);
323            if (wizard.isCanceled()) return;
324            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
325            holder.setAccessToken(wizard.getAccessToken());
326            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
327            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
328            refreshView();
329        }
330    }
331
332    /**
333     * Launches the OAuthAuthorisationWizard to generate a new Access Token
334     */
335    private class RenewAuthorisationAction extends AbstractAction {
336        public RenewAuthorisationAction() {
337            putValue(NAME, tr("New Access Token"));
338            putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process and generate a new Access Token"));
339            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth"));
340
341        }
342        @Override
343        public void actionPerformed(ActionEvent arg0) {
344            OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
345                    OAuthAuthenticationPreferencesPanel.this,
346                    apiUrl
347            );
348            wizard.setVisible(true);
349            if (wizard.isCanceled()) return;
350            OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
351            holder.setAccessToken(wizard.getAccessToken());
352            holder.setSaveToPreferences(wizard.isSaveAccessTokenToPreferences());
353            pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
354            refreshView();
355        }
356    }
357
358    /**
359     * Runs a test whether we can access the OSM server with the current Access Token
360     */
361    private class TestAuthorisationAction extends AbstractAction {
362        public TestAuthorisationAction() {
363            putValue(NAME, tr("Test Access Token"));
364            putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token"));
365            putValue(SMALL_ICON, ImageProvider.get("oauth", "oauth"));
366
367        }
368
369        @Override
370        public void actionPerformed(ActionEvent evt) {
371            OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken();
372            OAuthParameters parameters = OAuthParameters.createFromPreferences(Main.pref);
373            TestAccessTokenTask task = new TestAccessTokenTask(
374                    OAuthAuthenticationPreferencesPanel.this,
375                    apiUrl,
376                    parameters,
377                    token
378            );
379            Main.worker.submit(task);
380        }
381    }
382
383    @Override
384    public void propertyChange(PropertyChangeEvent evt) {
385        if (! evt.getPropertyName().equals(OsmApiUrlInputPanel.API_URL_PROP))
386            return;
387        setApiUrl((String)evt.getNewValue());
388    }
389}