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

Last change on this file was 19008, checked in by taylor.smock, 7 weeks 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.