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