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