source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java@ 18991

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

Fix #22810: OSM OAuth 1.0a/Basic auth deprecation and removal

As of 2024-02-15, something changed in the OSM server configuration. This broke
our OAuth 1.0a implementation (see #23475). As such, we are removing OAuth 1.0a
from JOSM now instead of when the OSM server removes support in June 2024.

For third-party OpenStreetMap servers, the Basic Authentication method has been
kept. However, they should be made aware that it may be removed if a non-trivial
bug occurs with it. We highly recommend that the third-party servers update to
the current OpenStreetMap website implementation (if only for their own security).

Failing that, the third-party server can implement RFC8414. As of this commit,
we currently use the authorization_endpoint and token_endpoint fields.
To check and see if their third-party server implements RFC8414, they can go
to <server host>/.well-known/oauth-authorization-server.

Prominent third-party OpenStreetMap servers may give us a client id for their
specific server. That client id may be added to the hard-coded client id list
at maintainer discretion. At a minimum, the server must be publicly
available and have a significant user base.

  • Property svn:eol-style set to native
File size: 8.8 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.oauth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.BorderLayout;
7import java.awt.FlowLayout;
8import java.awt.GridBagConstraints;
9import java.awt.GridBagLayout;
10import java.awt.Insets;
11import java.awt.event.ActionEvent;
12import java.beans.PropertyChangeEvent;
13import java.beans.PropertyChangeListener;
14import java.util.concurrent.Executor;
15
16import javax.swing.AbstractAction;
17import javax.swing.BorderFactory;
18import javax.swing.JButton;
19import javax.swing.JCheckBox;
20import javax.swing.JLabel;
21import javax.swing.JPanel;
22import javax.swing.JTabbedPane;
23import javax.swing.event.DocumentEvent;
24import javax.swing.event.DocumentListener;
25import javax.swing.text.JTextComponent;
26
27import org.openstreetmap.josm.data.oauth.OAuth20Exception;
28import org.openstreetmap.josm.data.oauth.OAuth20Token;
29import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
30import org.openstreetmap.josm.data.oauth.OAuthVersion;
31import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator;
32import org.openstreetmap.josm.gui.widgets.HtmlPanel;
33import org.openstreetmap.josm.gui.widgets.JosmTextField;
34import org.openstreetmap.josm.gui.widgets.SelectAllOnFocusGainedDecorator;
35import org.openstreetmap.josm.tools.ImageProvider;
36import org.openstreetmap.josm.tools.JosmRuntimeException;
37
38/**
39 * This is an UI which supports a JOSM user to get an OAuth Access Token in a fully manual process.
40 *
41 * @since 2746
42 */
43public class ManualAuthorizationUI extends AbstractAuthorizationUI {
44
45 private final JosmTextField tfAccessTokenKey = new JosmTextField();
46 private transient AccessTokenKeyValidator valAccessTokenKey;
47 private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save Access Token in preferences"));
48 private final HtmlPanel pnlMessage = new HtmlPanel();
49 private final transient Executor executor;
50
51 /**
52 * Constructs a new {@code ManualAuthorizationUI} for the given API URL.
53 * @param apiUrl The OSM API URL
54 * @param executor the executor used for running the HTTP requests for the authorization
55 * @since 5422
56 * @deprecated since 18991, use {@link ManualAuthorizationUI#ManualAuthorizationUI(String, Executor, OAuthVersion)}
57 * instead.
58 */
59 @Deprecated
60 public ManualAuthorizationUI(String apiUrl, Executor executor) {
61 this(apiUrl, executor, OAuthVersion.OAuth10a);
62 }
63
64 /**
65 * Constructs a new {@code ManualAuthorizationUI} for the given API URL.
66 * @param apiUrl The OSM API URL
67 * @param executor the executor used for running the HTTP requests for the authorization
68 * @param oAuthVersion The OAuthVersion to use for this UI
69 * @since 18991
70 */
71 public ManualAuthorizationUI(String apiUrl, Executor executor, OAuthVersion oAuthVersion) {
72 super(null /* don't pass apiURL because setApiUrl is overridden and references a local field */,
73 oAuthVersion);
74 setApiUrl(apiUrl);
75 this.executor = executor;
76 build();
77 }
78
79 protected JPanel buildAccessTokenPanel() {
80 JPanel pnl = new JPanel(new GridBagLayout());
81 pnl.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
82 GridBagConstraints gc = new GridBagConstraints();
83 AccessTokenBuilder accessTokenBuilder = new AccessTokenBuilder();
84
85 // the access token key input field
86 gc.anchor = GridBagConstraints.NORTHWEST;
87 gc.fill = GridBagConstraints.HORIZONTAL;
88 gc.weightx = 0.0;
89 gc.gridwidth = 2;
90 gc.insets = new Insets(0, 0, 5, 0);
91 pnlMessage.setText("<html><body>"
92 + tr("Please enter an OAuth Access Token which is authorized to access the OSM server "
93 + "''{0}''.",
94 getApiUrl()) + "</body></html>");
95 pnl.add(pnlMessage, gc);
96
97 // the access token key input field
98 gc.gridy = 1;
99 gc.weightx = 0.0;
100 gc.gridwidth = 1;
101 gc.insets = new Insets(0, 0, 0, 3);
102 pnl.add(new JLabel(tr("Access Token Key:")), gc);
103
104 gc.gridx = 1;
105 gc.weightx = 1.0;
106 pnl.add(tfAccessTokenKey, gc);
107 SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenKey);
108 valAccessTokenKey = new AccessTokenKeyValidator(tfAccessTokenKey);
109 valAccessTokenKey.validate();
110 tfAccessTokenKey.getDocument().addDocumentListener(accessTokenBuilder);
111
112 // the checkbox for saving to preferences
113 gc.gridy = 3;
114 gc.gridx = 0;
115 gc.gridwidth = 2;
116 gc.weightx = 1.0;
117 pnl.add(cbSaveToPreferences, gc);
118 cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
119
120 // filler - grab remaining space
121 gc.gridy = 3;
122 gc.gridx = 0;
123 gc.gridwidth = 2;
124 gc.weightx = 1.0;
125 gc.weighty = 1.0;
126 gc.fill = GridBagConstraints.BOTH;
127 pnl.add(new JPanel(), gc);
128 return pnl;
129 }
130
131 protected JPanel buildTabbedPreferencesPanel() {
132 JPanel pnl = new JPanel(new BorderLayout());
133
134 JTabbedPane tp = new JTabbedPane();
135 tp.add(buildAccessTokenPanel());
136 tp.add(getAdvancedPropertiesPanel());
137
138 tp.setTitleAt(0, tr("Access Token"));
139 tp.setTitleAt(1, tr("Advanced OAuth parameters"));
140
141 tp.setToolTipTextAt(0, tr("Enter the OAuth Access Token"));
142 tp.setToolTipTextAt(1, tr("Enter advanced OAuth properties"));
143
144 pnl.add(tp, BorderLayout.CENTER);
145 return pnl;
146 }
147
148 protected JPanel buildActionsPanel() {
149 JPanel pnl = new JPanel(new FlowLayout(FlowLayout.LEFT));
150 TestAccessTokenAction actTestAccessToken = new TestAccessTokenAction();
151 pnl.add(new JButton(actTestAccessToken));
152 this.addPropertyChangeListener(actTestAccessToken);
153 return pnl;
154 }
155
156 @Override
157 public void setApiUrl(String apiUrl) {
158 super.setApiUrl(apiUrl);
159 if (pnlMessage != null) {
160 pnlMessage.setText(tr("<html><body>"
161 + "Please enter an OAuth Access Token which is authorized to access the OSM server "
162 + "''{0}''."
163 + "</body></html>",
164 getApiUrl()
165 ));
166 }
167 }
168
169 protected final void build() {
170 setLayout(new BorderLayout());
171 setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
172 add(buildTabbedPreferencesPanel(), BorderLayout.CENTER);
173 add(buildActionsPanel(), BorderLayout.SOUTH);
174 }
175
176 @Override
177 public boolean isSaveAccessTokenToPreferences() {
178 return cbSaveToPreferences.isSelected();
179 }
180
181 private static class AccessTokenKeyValidator extends DefaultTextComponentValidator {
182 AccessTokenKeyValidator(JTextComponent tc) {
183 super(tc, tr("Please enter an Access Token Key"),
184 tr("The Access Token Key must not be empty. Please enter an Access Token Key"));
185 }
186 }
187
188 class AccessTokenBuilder implements DocumentListener {
189
190 public void build() {
191 if (!valAccessTokenKey.isValid()) {
192 setAccessToken(null);
193 } else {
194 try {
195 setAccessToken(new OAuth20Token(getOAuthParameters(), "{\"token_type\":\"bearer\", \"access_token\""
196 + tfAccessTokenKey.getText().trim() + "\"}"));
197 } catch (OAuth20Exception e) {
198 throw new JosmRuntimeException(e);
199 }
200 }
201 }
202
203 @Override
204 public void changedUpdate(DocumentEvent e) {
205 build();
206 }
207
208 @Override
209 public void insertUpdate(DocumentEvent e) {
210 build();
211 }
212
213 @Override
214 public void removeUpdate(DocumentEvent e) {
215 build();
216 }
217 }
218
219 /**
220 * Action for testing an Access Token
221 */
222 class TestAccessTokenAction extends AbstractAction implements PropertyChangeListener {
223 TestAccessTokenAction() {
224 putValue(NAME, tr("Test Access Token"));
225 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
226 putValue(SHORT_DESCRIPTION, tr("Click to test the Access Token"));
227 updateEnabledState();
228 }
229
230 @Override
231 public void actionPerformed(ActionEvent evt) {
232 TestAccessTokenTask task = new TestAccessTokenTask(
233 ManualAuthorizationUI.this,
234 getApiUrl(),
235 getAccessToken()
236 );
237 executor.execute(task);
238 }
239
240 protected final void updateEnabledState() {
241 setEnabled(hasAccessToken());
242 }
243
244 @Override
245 public void propertyChange(PropertyChangeEvent evt) {
246 if (!evt.getPropertyName().equals(AbstractAuthorizationUI.ACCESS_TOKEN_PROP))
247 return;
248 updateEnabledState();
249 }
250 }
251}
Note: See TracBrowser for help on using the repository browser.