source: josm/trunk/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java@ 19010

Last change on this file since 19010 was 18991, checked in by taylor.smock, 4 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.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io.auth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.net.Authenticator.RequestorType;
8import java.net.PasswordAuthentication;
9import java.util.HashSet;
10import java.util.Objects;
11import java.util.Set;
12
13import jakarta.json.JsonException;
14import javax.swing.text.html.HTMLEditorKit;
15
16import org.openstreetmap.josm.data.oauth.IOAuthToken;
17import org.openstreetmap.josm.data.oauth.OAuth20Exception;
18import org.openstreetmap.josm.data.oauth.OAuth20Parameters;
19import org.openstreetmap.josm.data.oauth.OAuth20Token;
20import org.openstreetmap.josm.data.oauth.OAuthVersion;
21import org.openstreetmap.josm.gui.widgets.HtmlPanel;
22import org.openstreetmap.josm.io.DefaultProxySelector;
23import org.openstreetmap.josm.io.OsmApi;
24import org.openstreetmap.josm.spi.preferences.Config;
25import org.openstreetmap.josm.tools.Utils;
26
27/**
28 * This is the default credentials agent in JOSM. It keeps username and password for both
29 * the OSM API and an optional HTTP proxy in the JOSM preferences file.
30 * @since 2641
31 */
32public class JosmPreferencesCredentialAgent extends AbstractCredentialsAgent {
33
34 /**
35 * @see CredentialsAgent#lookup
36 */
37 @Override
38 public PasswordAuthentication lookup(RequestorType requestorType, String host) throws CredentialsAgentException {
39 if (requestorType == null)
40 return null;
41 String user;
42 String password;
43 switch(requestorType) {
44 case SERVER:
45 if (Objects.equals(OsmApi.getOsmApi().getHost(), host)) {
46 user = Config.getPref().get("osm-server.username", null);
47 password = Config.getPref().get("osm-server.password", null);
48 } else if (host != null) {
49 user = Config.getPref().get("server.username."+host, null);
50 password = Config.getPref().get("server.password."+host, null);
51 } else {
52 user = null;
53 password = null;
54 }
55 if (user == null)
56 return null;
57 return new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
58 case PROXY:
59 user = Config.getPref().get(DefaultProxySelector.PROXY_USER, null);
60 password = Config.getPref().get(DefaultProxySelector.PROXY_PASS, null);
61 if (user == null)
62 return null;
63 return new PasswordAuthentication(user, password == null ? new char[0] : password.toCharArray());
64 }
65 return null;
66 }
67
68 /**
69 * @see CredentialsAgent#store
70 */
71 @Override
72 public void store(RequestorType requestorType, String host, PasswordAuthentication credentials) throws CredentialsAgentException {
73 if (requestorType == null)
74 return;
75 switch(requestorType) {
76 case SERVER:
77 if (Objects.equals(OsmApi.getOsmApi().getHost(), host)) {
78 Config.getPref().put("osm-server.username", credentials.getUserName());
79 if (credentials.getPassword().length == 0) { // PasswordAuthentication#getPassword cannot be null
80 Config.getPref().put("osm-server.password", null);
81 } else {
82 Config.getPref().put("osm-server.password", String.valueOf(credentials.getPassword()));
83 }
84 } else if (host != null) {
85 Config.getPref().put("server.username."+host, credentials.getUserName());
86 if (credentials.getPassword().length == 0) {
87 Config.getPref().put("server.password."+host, null);
88 } else {
89 Config.getPref().put("server.password."+host, String.valueOf(credentials.getPassword()));
90 }
91 }
92 break;
93 case PROXY:
94 Config.getPref().put(DefaultProxySelector.PROXY_USER, credentials.getUserName());
95 if (credentials.getPassword().length == 0) {
96 Config.getPref().put(DefaultProxySelector.PROXY_PASS, null);
97 } else {
98 Config.getPref().put(DefaultProxySelector.PROXY_PASS, String.valueOf(credentials.getPassword()));
99 }
100 break;
101 }
102 }
103
104 @Override
105 public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException {
106 Set<String> keySet = new HashSet<>(Config.getPref().getKeySet());
107 keySet.addAll(Config.getPref().getSensitive()); // Just in case we decide to not return sensitive keys in getKeySet
108 for (OAuthVersion oauthType : OAuthVersion.values()) {
109 final String hostKey = "oauth.access-token.object." + oauthType + "." + host;
110 final String parametersKey = "oauth.access-token.parameters." + oauthType + "." + host;
111 if (!keySet.contains(hostKey) || !keySet.contains(parametersKey)) {
112 continue; // Avoid adding empty temporary entries to preferences
113 }
114 String token = Config.getPref().get(hostKey, null);
115 String parameters = Config.getPref().get(parametersKey, null);
116 if (!Utils.isBlank(token) && !Utils.isBlank(parameters) && OAuthVersion.OAuth20 == oauthType) {
117 try {
118 OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(parameters);
119 return new OAuth20Token(oAuth20Parameters, token);
120 } catch (OAuth20Exception | JsonException e) {
121 throw new CredentialsAgentException(e);
122 }
123 }
124 }
125 return null;
126 }
127
128 @Override
129 public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException {
130 Objects.requireNonNull(host, "host");
131 if (accessToken == null) {
132 Set<String> keySet = new HashSet<>(Config.getPref().getKeySet());
133 keySet.addAll(Config.getPref().getSensitive()); // Just in case we decide to not return sensitive keys in getKeySet
134 // Assume we want to remove all access tokens
135 for (OAuthVersion oauthType : OAuthVersion.values()) {
136 final String hostKey = "oauth.access-token.parameters." + oauthType + "." + host;
137 final String parametersKey = "oauth.access-token.parameters." + oauthType + "." + host;
138 if (keySet.contains(hostKey)) {
139 Config.getPref().removeSensitive(hostKey);
140 }
141 if (keySet.contains(parametersKey)) {
142 Config.getPref().removeSensitive(parametersKey);
143 }
144 }
145 } else {
146 final String hostKey = "oauth.access-token.object." + accessToken.getOAuthType() + "." + host;
147 final String parametersKey = "oauth.access-token.parameters." + accessToken.getOAuthType() + "." + host;
148 Config.getPref().put(hostKey, accessToken.toPreferencesString());
149 Config.getPref().put(parametersKey, accessToken.getParameters().toPreferencesString());
150 Config.getPref().addSensitive(this, hostKey);
151 Config.getPref().addSensitive(this, parametersKey);
152 }
153 }
154
155 @Override
156 public Component getPreferencesDecorationPanel() {
157 HtmlPanel pnlMessage = new HtmlPanel();
158 HTMLEditorKit kit = (HTMLEditorKit) pnlMessage.getEditorPane().getEditorKit();
159 kit.getStyleSheet().addRule(
160 ".warning-body {background-color:rgb(253,255,221);padding: 10pt; " +
161 "border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
162 pnlMessage.setText(tr(
163 "<html><body>"
164 + "<p class=\"warning-body\">"
165 + "<strong>Note:</strong> The password is stored in plain text in the JOSM preferences file on your computer. "
166 + "</p>"
167 + "</body></html>"
168 )
169 );
170 return pnlMessage;
171 }
172
173 @Override
174 public String getSaveUsernameAndPasswordCheckboxText() {
175 return tr("Save user and password (unencrypted)");
176 }
177}
Note: See TracBrowser for help on using the repository browser.