source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java@ 18786

Last change on this file since 18786 was 18786, checked in by taylor.smock, 10 months ago

Fix #23083: SocketTimeoutException in OAuth20Authorization$OAuth20AuthorizationHandler.handleRequest

  • Property svn:eol-style set to native
File size: 19.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.preferences.server;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.Color;
8import java.awt.FlowLayout;
9import java.awt.Font;
10import java.awt.GridBagConstraints;
11import java.awt.GridBagLayout;
12import java.awt.Insets;
13import java.awt.event.ActionEvent;
14import java.awt.event.ItemEvent;
15import java.beans.PropertyChangeEvent;
16import java.beans.PropertyChangeListener;
17
18import javax.swing.AbstractAction;
19import javax.swing.BorderFactory;
20import javax.swing.JButton;
21import javax.swing.JCheckBox;
22import javax.swing.JLabel;
23import javax.swing.JOptionPane;
24import javax.swing.JPanel;
25
26import org.openstreetmap.josm.actions.ExpertToggleAction;
27import org.openstreetmap.josm.data.oauth.IOAuthToken;
28import org.openstreetmap.josm.data.oauth.OAuth20Authorization;
29import org.openstreetmap.josm.data.oauth.OAuth20Token;
30import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
31import org.openstreetmap.josm.data.oauth.OAuthParameters;
32import org.openstreetmap.josm.data.oauth.OAuthToken;
33import org.openstreetmap.josm.data.oauth.OAuthVersion;
34import org.openstreetmap.josm.data.oauth.osm.OsmScopes;
35import org.openstreetmap.josm.gui.MainApplication;
36import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel;
37import org.openstreetmap.josm.gui.oauth.AuthorizationProcedure;
38import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
39import org.openstreetmap.josm.gui.oauth.TestAccessTokenTask;
40import org.openstreetmap.josm.gui.util.GuiHelper;
41import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
42import org.openstreetmap.josm.gui.widgets.JosmTextField;
43import org.openstreetmap.josm.io.OsmApi;
44import org.openstreetmap.josm.io.auth.CredentialsManager;
45import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
46import org.openstreetmap.josm.tools.GBC;
47import org.openstreetmap.josm.tools.ImageProvider;
48import org.openstreetmap.josm.tools.Logging;
49import org.openstreetmap.josm.tools.UserCancelException;
50
51/**
52 * The preferences panel for the OAuth 1.0a preferences. This just a summary panel
53 * showing the current Access Token Key and Access Token Secret, if the
54 * user already has an Access Token.
55 * <br>
56 * For initial authorisation see {@link OAuthAuthorizationWizard}.
57 * @since 2745
58 */
59public class OAuthAuthenticationPreferencesPanel extends JPanel implements PropertyChangeListener {
60 private final JCheckBox cbUseForAllRequests = new JCheckBox();
61 private final JCheckBox cbShowAdvancedParameters = new JCheckBox(tr("Display Advanced OAuth Parameters"));
62 private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save to preferences"));
63 private final JPanel pnlAuthorisationMessage = new JPanel(new BorderLayout());
64 private final NotYetAuthorisedPanel pnlNotYetAuthorised;
65 private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties;
66 private final AlreadyAuthorisedPanel pnlAlreadyAuthorised;
67 private final OAuthVersion oAuthVersion;
68 private String apiUrl;
69
70 /**
71 * Create the panel. Uses {@link OAuthVersion#OAuth10a}.
72 */
73 public OAuthAuthenticationPreferencesPanel() {
74 this(OAuthVersion.OAuth10a);
75 }
76
77 /**
78 * Create the panel.
79 * @param oAuthVersion The OAuth version to use
80 */
81 public OAuthAuthenticationPreferencesPanel(OAuthVersion oAuthVersion) {
82 this.oAuthVersion = oAuthVersion;
83 // These must come after we set the oauth version
84 this.pnlNotYetAuthorised = new NotYetAuthorisedPanel();
85 this.pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(this.oAuthVersion);
86 this.pnlAlreadyAuthorised = new AlreadyAuthorisedPanel();
87 build();
88 }
89
90 /**
91 * Builds the panel for entering the advanced OAuth parameters
92 *
93 * @return panel with advanced settings
94 */
95 protected JPanel buildAdvancedPropertiesPanel() {
96 JPanel pnl = new JPanel(new GridBagLayout());
97
98 cbUseForAllRequests.setText(tr("Use OAuth for all requests to {0}", OsmApi.getOsmApi().getServerUrl()));
99 cbUseForAllRequests.setToolTipText(tr("For user-based bandwidth limit instead of IP-based one"));
100 pnl.add(cbUseForAllRequests, GBC.eol().fill(GBC.HORIZONTAL));
101
102 pnl.add(cbShowAdvancedParameters, GBC.eol().fill(GBC.HORIZONTAL));
103 cbShowAdvancedParameters.setSelected(false);
104 cbShowAdvancedParameters.addItemListener(
105 evt -> pnlAdvancedProperties.setVisible(evt.getStateChange() == ItemEvent.SELECTED)
106 );
107
108 pnl.add(pnlAdvancedProperties, GBC.eol().fill(GBC.HORIZONTAL).insets(0, 3, 0, 0));
109 pnlAdvancedProperties.initialize(OsmApi.getOsmApi().getServerUrl());
110 pnlAdvancedProperties.setBorder(
111 BorderFactory.createCompoundBorder(
112 BorderFactory.createLineBorder(Color.GRAY, 1),
113 BorderFactory.createEmptyBorder(3, 3, 3, 3)
114 )
115 );
116 pnlAdvancedProperties.setVisible(false);
117 return pnl;
118 }
119
120 /**
121 * builds the UI
122 */
123 protected final void build() {
124 setLayout(new GridBagLayout());
125 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
126 GridBagConstraints gc = new GridBagConstraints();
127
128 // the panel for the OAuth parameters. pnlAuthorisationMessage is an
129 // empty panel. It is going to be filled later, depending on the
130 // current OAuth state in JOSM.
131 gc.fill = GridBagConstraints.BOTH;
132 gc.anchor = GridBagConstraints.NORTHWEST;
133 gc.weighty = 1.0;
134 gc.weightx = 1.0;
135 gc.insets = new Insets(10, 0, 0, 0);
136 add(pnlAuthorisationMessage, gc);
137 }
138
139 protected void refreshView() {
140 pnlAuthorisationMessage.removeAll();
141 if ((this.oAuthVersion == OAuthVersion.OAuth10a &&
142 OAuthAccessTokenHolder.getInstance().containsAccessToken())
143 || OAuthAccessTokenHolder.getInstance().getAccessToken(this.apiUrl, this.oAuthVersion) != null) {
144 pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER);
145 pnlAlreadyAuthorised.refreshView();
146 pnlAlreadyAuthorised.revalidate();
147 } else {
148 pnlAuthorisationMessage.add(pnlNotYetAuthorised, BorderLayout.CENTER);
149 pnlNotYetAuthorised.revalidate();
150 }
151 repaint();
152 }
153
154 /**
155 * Sets the URL of the OSM API for which this panel is currently displaying OAuth properties.
156 *
157 * @param apiUrl the api URL
158 */
159 public void setApiUrl(String apiUrl) {
160 this.apiUrl = apiUrl;
161 pnlAdvancedProperties.setApiUrl(apiUrl);
162 }
163
164 /**
165 * Initializes the panel from preferences
166 */
167 public void initFromPreferences() {
168 setApiUrl(OsmApi.getOsmApi().getServerUrl().trim());
169 cbUseForAllRequests.setSelected(OsmApi.USE_OAUTH_FOR_ALL_REQUESTS.get());
170 refreshView();
171 }
172
173 /**
174 * Saves the current values to preferences
175 */
176 public void saveToPreferences() {
177 OAuthAccessTokenHolder.getInstance().setSaveToPreferences(cbSaveToPreferences.isSelected());
178 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
179 OsmApi.USE_OAUTH_FOR_ALL_REQUESTS.put(cbUseForAllRequests.isSelected());
180 pnlAdvancedProperties.rememberPreferences();
181 }
182
183 /**
184 * The preferences panel displayed if there is currently no Access Token available.
185 * This means that the user didn't run through the OAuth authorisation procedure yet.
186 *
187 */
188 private class NotYetAuthorisedPanel extends JPanel {
189 /**
190 * Constructs a new {@code NotYetAuthorisedPanel}.
191 */
192 NotYetAuthorisedPanel() {
193 build();
194 }
195
196 protected void build() {
197 setLayout(new GridBagLayout());
198
199 // A message explaining that the user isn't authorised yet
200 JMultilineLabel lbl = new JMultilineLabel(
201 tr("You do not have an Access Token yet to access the OSM server using OAuth. Please authorize first."));
202 add(lbl, GBC.eol().anchor(GBC.NORTHWEST).fill(GBC.HORIZONTAL));
203 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
204
205 // Action for authorising now
206 if (oAuthVersion == OAuthVersion.OAuth10a) {
207 add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.FULLY_AUTOMATIC)), GBC.eol());
208 }
209 add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.SEMI_AUTOMATIC)), GBC.eol());
210 if (oAuthVersion == OAuthVersion.OAuth10a) {
211 JButton authManually = new JButton(new AuthoriseNowAction(AuthorizationProcedure.MANUALLY));
212 add(authManually, GBC.eol());
213 ExpertToggleAction.addVisibilitySwitcher(authManually);
214 }
215
216 // filler - grab remaining space
217 add(new JPanel(), GBC.std().fill(GBC.BOTH));
218 }
219 }
220
221 /**
222 * The preferences panel displayed if there is currently an AccessToken available.
223 *
224 */
225 private class AlreadyAuthorisedPanel extends JPanel {
226 private final JosmTextField tfAccessTokenKey = new JosmTextField(null, null, 0, false);
227 private final JosmTextField tfAccessTokenSecret = new JosmTextField(null, null, 0, false);
228
229 /**
230 * Constructs a new {@code AlreadyAuthorisedPanel}.
231 */
232 AlreadyAuthorisedPanel() {
233 build();
234 refreshView();
235 }
236
237 protected void build() {
238 setLayout(new GridBagLayout());
239 GridBagConstraints gc = new GridBagConstraints();
240 gc.anchor = GridBagConstraints.NORTHWEST;
241 gc.insets = new Insets(0, 0, 3, 3);
242 gc.fill = GridBagConstraints.HORIZONTAL;
243 gc.weightx = 1.0;
244 gc.gridwidth = 2;
245 JMultilineLabel lbl = new JMultilineLabel(tr("You already have an Access Token to access the OSM server using OAuth."));
246 add(lbl, gc);
247 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN));
248
249 // -- access token key
250 gc.gridy = 1;
251 gc.gridx = 0;
252 gc.gridwidth = 1;
253 gc.weightx = 0.0;
254 add(new JLabel(tr("Access Token Key:")), gc);
255
256 gc.gridx = 1;
257 gc.weightx = 1.0;
258 add(tfAccessTokenKey, gc);
259 tfAccessTokenKey.setEditable(false);
260
261 // -- access token secret
262 gc.gridy = 2;
263 gc.gridx = 0;
264 gc.gridwidth = 1;
265 gc.weightx = 0.0;
266 add(new JLabel(tr("Access Token Secret:")), gc);
267
268 gc.gridx = 1;
269 gc.weightx = 1.0;
270 add(tfAccessTokenSecret, gc);
271 tfAccessTokenSecret.setEditable(false);
272
273 // -- access token secret
274 gc.gridy = 3;
275 gc.gridx = 0;
276 gc.gridwidth = 2;
277 gc.weightx = 1.0;
278 add(cbSaveToPreferences, gc);
279 cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
280
281 // -- action buttons
282 JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
283 if (oAuthVersion == OAuthVersion.OAuth10a) {
284 // these want the OAuth 1.0 token information
285 btns.add(new JButton(new RenewAuthorisationAction(AuthorizationProcedure.FULLY_AUTOMATIC)));
286 }
287 btns.add(new JButton(new TestAuthorisationAction(oAuthVersion)));
288 btns.add(new JButton(new RemoveAuthorisationAction()));
289 gc.gridy = 4;
290 gc.gridx = 0;
291 gc.gridwidth = 2;
292 gc.weightx = 1.0;
293 add(btns, gc);
294
295 // the panel with the advanced options
296 gc.gridy = 5;
297 gc.gridx = 0;
298 gc.gridwidth = 2;
299 gc.weightx = 1.0;
300 add(buildAdvancedPropertiesPanel(), gc);
301
302 // filler - grab the remaining space
303 gc.gridy = 6;
304 gc.fill = GridBagConstraints.BOTH;
305 gc.weightx = 1.0;
306 gc.weighty = 1.0;
307 add(new JPanel(), gc);
308 }
309
310 protected final void refreshView() {
311 switch (oAuthVersion) {
312 case OAuth10a:
313 String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey();
314 tfAccessTokenKey.setText(v == null ? "" : v);
315 v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret();
316 tfAccessTokenSecret.setText(v == null ? "" : v);
317 tfAccessTokenSecret.setVisible(true);
318 break;
319 case OAuth20:
320 case OAuth21:
321 String token = "";
322 if (apiUrl != null) {
323 OAuth20Token bearerToken = (OAuth20Token) OAuthAccessTokenHolder.getInstance().getAccessToken(apiUrl, oAuthVersion);
324 token = bearerToken == null ? "" : bearerToken.getBearerToken();
325 }
326 tfAccessTokenKey.setText(token == null ? "" : token);
327 tfAccessTokenSecret.setVisible(false);
328 }
329 cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
330 }
331 }
332
333 /**
334 * Action to authorise the current user
335 */
336 private class AuthoriseNowAction extends AbstractAction {
337 private final AuthorizationProcedure procedure;
338
339 AuthoriseNowAction(AuthorizationProcedure procedure) {
340 this.procedure = procedure;
341 putValue(NAME, tr("{0} ({1})", tr("Authorize now"), procedure.getText()));
342 putValue(SHORT_DESCRIPTION, procedure.getDescription());
343 if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC
344 || OAuthAuthenticationPreferencesPanel.this.oAuthVersion != OAuthVersion.OAuth10a) {
345 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
346 }
347 }
348
349 @Override
350 public void actionPerformed(ActionEvent arg0) {
351 if (OAuthAuthenticationPreferencesPanel.this.oAuthVersion == OAuthVersion.OAuth10a) {
352 OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard(
353 OAuthAuthenticationPreferencesPanel.this,
354 procedure,
355 apiUrl,
356 MainApplication.worker);
357 try {
358 wizard.showDialog();
359 } catch (UserCancelException ignore) {
360 Logging.trace(ignore);
361 return;
362 }
363 pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters());
364 refreshView();
365 } else {
366 final boolean remoteControlIsRunning = Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get());
367 // TODO: Ask user if they want to start remote control?
368 if (!remoteControlIsRunning) {
369 RemoteControl.start();
370 }
371 new OAuth20Authorization().authorize(OAuthParameters.createDefault(OsmApi.getOsmApi().getServerUrl(), oAuthVersion), token -> {
372 if (!remoteControlIsRunning) {
373 RemoteControl.stop();
374 }
375 OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), token.orElse(null));
376 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
377 GuiHelper.runInEDT(OAuthAuthenticationPreferencesPanel.this::refreshView);
378 if (!token.isPresent()) {
379 GuiHelper.runInEDT(() -> JOptionPane.showMessageDialog(MainApplication.getMainPanel(),
380 tr("Authentication failed, please check browser for details."),
381 tr("OAuth Authentication Failed"),
382 JOptionPane.ERROR_MESSAGE));
383 }
384 }, OsmScopes.read_gpx, OsmScopes.write_gpx,
385 OsmScopes.read_prefs, OsmScopes.write_prefs,
386 OsmScopes.write_api, OsmScopes.write_notes);
387 }
388 }
389 }
390
391 /**
392 * Remove the OAuth authorization token
393 */
394 private class RemoveAuthorisationAction extends AbstractAction {
395 RemoveAuthorisationAction() {
396 putValue(NAME, tr("Remove token"));
397 putValue(SHORT_DESCRIPTION, tr("Remove token from JOSM. This does not revoke the token."));
398 new ImageProvider("cancel").getResource().attachImageIcon(this);
399 }
400
401 @Override
402 public void actionPerformed(ActionEvent e) {
403 if (oAuthVersion == OAuthVersion.OAuth10a) {
404 OAuthAccessTokenHolder.getInstance().setAccessToken(null);
405 } else {
406 OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, (IOAuthToken) null);
407 }
408 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
409 refreshView();
410 }
411 }
412
413 /**
414 * Launches the OAuthAuthorisationWizard to generate a new Access Token
415 */
416 private class RenewAuthorisationAction extends AuthoriseNowAction {
417 /**
418 * Constructs a new {@code RenewAuthorisationAction}.
419 */
420 RenewAuthorisationAction(AuthorizationProcedure procedure) {
421 super(procedure);
422 putValue(NAME, tr("New Access Token"));
423 putValue(SHORT_DESCRIPTION, tr("Click to step through the OAuth authorization process and generate a new Access Token"));
424 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
425 }
426 }
427
428 /**
429 * Runs a test whether we can access the OSM server with the current Access Token
430 */
431 private class TestAuthorisationAction extends AbstractAction {
432 private final OAuthVersion oAuthVersion;
433
434 /**
435 * Constructs a new {@code TestAuthorisationAction}.
436 */
437 TestAuthorisationAction(OAuthVersion oAuthVersion) {
438 this.oAuthVersion = oAuthVersion;
439 putValue(NAME, tr("Test Access Token"));
440 putValue(SHORT_DESCRIPTION, tr("Click test access to the OSM server with the current access token"));
441 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
442 }
443
444 @Override
445 public void actionPerformed(ActionEvent evt) {
446 if (this.oAuthVersion == OAuthVersion.OAuth10a) {
447 OAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken();
448 OAuthParameters parameters = OAuthParameters.createFromApiUrl(OsmApi.getOsmApi().getServerUrl());
449 TestAccessTokenTask task = new TestAccessTokenTask(
450 OAuthAuthenticationPreferencesPanel.this,
451 apiUrl,
452 parameters,
453 token
454 );
455 MainApplication.worker.submit(task);
456 } else {
457 IOAuthToken token = OAuthAccessTokenHolder.getInstance().getAccessToken(OsmApi.getOsmApi().getBaseUrl(), OAuthVersion.OAuth20);
458 TestAccessTokenTask task = new TestAccessTokenTask(
459 OAuthAuthenticationPreferencesPanel.this,
460 apiUrl,
461 token.getParameters(),
462 token
463 );
464 MainApplication.worker.submit(task);
465 }
466 }
467 }
468
469 @Override
470 public void propertyChange(PropertyChangeEvent evt) {
471 if (!evt.getPropertyName().equals(OsmApiUrlInputPanel.API_URL_PROP))
472 return;
473 setApiUrl((String) evt.getNewValue());
474 }
475}
Note: See TracBrowser for help on using the repository browser.