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

Last change on this file was 19008, checked in by taylor.smock, 16 months ago

Fix an issue with custom OAuth2 parameters where the custom parameters would be replaced by default parameters

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