Ticket #20768: 21607.patch
File 21607.patch, 124.2 KB (added by , 3 years ago) |
---|
-
new file core/src/org/openstreetmap/josm/data/oauth/IOAuthAuthorization.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/IOAuthAuthorization.java b/core/src/org/openstreetmap/josm/data/oauth/IOAuthAuthorization.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import java.util.function.Consumer; 5 6 /** 7 * Interface for OAuth authorization classes 8 * @author Taylor Smock 9 * @since xxx 10 */ 11 public interface IOAuthAuthorization { 12 /** 13 * Perform the authorization dance 14 * @param parameters The OAuth parameters 15 * @param consumer The callback for the generated token 16 * @param scopes The scopes to ask for 17 */ 18 void authorize(IOAuthParameters parameters, Consumer<IOAuthToken> consumer, Enum<?>... scopes); 19 } -
new file core/src/org/openstreetmap/josm/data/oauth/IOAuthParameters.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/IOAuthParameters.java b/core/src/org/openstreetmap/josm/data/oauth/IOAuthParameters.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import java.util.stream.Stream; 5 6 /** 7 * A generic interface for OAuth Parameters 8 * @author Taylor Smock 9 * @since xxx 10 */ 11 public interface IOAuthParameters { 12 /** 13 * Get the access token URL 14 * @return The URL to use to switch the code to a token 15 */ 16 String getAccessTokenUrl(); 17 18 /** 19 * Get the base authorization URL to open in a browser 20 * @return The base URL to send to the browser 21 */ 22 String getAuthorizationUrl(); 23 24 /** 25 * Get the authorization URL to open in a browser 26 * @param state The state to prevent attackers from providing their own token 27 * @param scopes The scopes to request 28 * @return The URL to send to the browser 29 */ 30 default String getAuthorizationUrl(String state, Enum<?>... scopes) { 31 return this.getAuthorizationUrl(state, Stream.of(scopes).map(Enum::toString).toArray(String[]::new)); 32 } 33 34 /** 35 * Get the authorization URL to open in a browser 36 * @param state The state to prevent attackers from providing their own token 37 * @param scopes The scopes to request 38 * @return The URL to send to the browser 39 */ 40 default String getAuthorizationUrl(String state, String... scopes) { 41 // response_type = code | token, but token is deprecated in the draft oauth 2.1 spec 42 // 2.1 is adding code_challenge, code_challenge_method 43 // code_challenge requires a code_verifier 44 return this.getAuthorizationUrl() + "?response_type=code&client_id=" + this.getClientId() 45 + "&redirect_uri=" + this.getRedirectUri() 46 + "&scope=" + String.join(" ", scopes) 47 // State is used to detect/prevent cross-site request forgery 48 + "&state=" + state; 49 } 50 51 /** 52 * Get the OAuth version that the API expects 53 * @return The oauth version 54 */ 55 OAuthVersion getOAuthVersion(); 56 57 /** 58 * Get the client id 59 * @return The client id 60 */ 61 String getClientId(); 62 63 /** 64 * Get the client secret 65 * @return The client secret 66 */ 67 String getClientSecret(); 68 69 /** 70 * Get the redirect URI 71 * @return The redirect URI 72 */ 73 default String getRedirectUri() { 74 return null; 75 } 76 77 /** 78 * Convert to a preference string 79 * @return the preference string 80 */ 81 default String toPreferencesString() { 82 return null; 83 } 84 85 /** 86 * Get the actual API URL 87 * @return The API URl 88 */ 89 default String getApiUrl() { 90 return null; 91 } 92 93 void rememberPreferences(); 94 } -
new file core/src/org/openstreetmap/josm/data/oauth/IOAuthToken.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/IOAuthToken.java b/core/src/org/openstreetmap/josm/data/oauth/IOAuthToken.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import org.openstreetmap.josm.tools.HttpClient; 5 6 /** 7 * An interface for oauth tokens 8 * @author Taylor Smock 9 * @since xxx 10 */ 11 public interface IOAuthToken { 12 /** 13 * Sign a client 14 * @param client The client to sign 15 */ 16 void sign(HttpClient client) throws OAuthException; 17 18 /** 19 * Get the preferences string of this auth token. 20 * This should match the expected return body from the authentication server. 21 * For OAuth, this is typically JSON. 22 * @return The preferences string 23 */ 24 String toPreferencesString(); 25 26 /** 27 * Get the auth type of this token 28 * @return The auth type 29 */ 30 OAuthVersion getOAuthType(); 31 32 /** 33 * Get the OAuth parameters 34 * @return The OAuth parameters 35 */ 36 IOAuthParameters getParameters(); 37 } -
new file core/src/org/openstreetmap/josm/data/oauth/OAuth20Authorization.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuth20Authorization.java b/core/src/org/openstreetmap/josm/data/oauth/OAuth20Authorization.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.io.IOException; 7 import java.net.MalformedURLException; 8 import java.net.URL; 9 import java.nio.charset.StandardCharsets; 10 import java.security.MessageDigest; 11 import java.security.NoSuchAlgorithmException; 12 import java.util.Base64; 13 import java.util.Map; 14 import java.util.Objects; 15 import java.util.UUID; 16 import java.util.function.Consumer; 17 18 import org.openstreetmap.josm.io.remotecontrol.handler.AuthorizationHandler; 19 import org.openstreetmap.josm.io.remotecontrol.handler.RequestHandler; 20 import org.openstreetmap.josm.tools.HttpClient; 21 import org.openstreetmap.josm.tools.JosmRuntimeException; 22 import org.openstreetmap.josm.tools.OpenBrowser; 23 24 /** 25 * Authorize the application 26 */ 27 public class OAuth20Authorization implements IOAuthAuthorization { 28 /** 29 * See <a href="https://www.rfc-editor.org/rfc/rfc7636">RFC7636</a>: PKCE 30 * @param cryptographicallyRandomString A cryptographically secure string 31 * @return The S256 bytes 32 */ 33 private static String getPKCES256CodeChallenge(String cryptographicallyRandomString) { 34 // S256: code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) 35 try { 36 byte[] encodedBytes = cryptographicallyRandomString.getBytes(StandardCharsets.US_ASCII); 37 MessageDigest digest = MessageDigest.getInstance("SHA-256"); 38 return new String(Base64.getUrlEncoder().encode(digest.digest(encodedBytes)), StandardCharsets.US_ASCII) 39 .replace("=", "").replace("+", "-").replace("/", "_"); 40 } catch (NoSuchAlgorithmException e) { 41 throw new JosmRuntimeException(e); 42 } 43 } 44 45 @Override 46 public void authorize(IOAuthParameters parameters, Consumer<IOAuthToken> consumer, Enum<?>... scopes) { 47 final String state = UUID.randomUUID().toString(); 48 final String codeVerifier = UUID.randomUUID().toString(); // Cryptographically random string (ASCII) 49 final String s256CodeChallenge = getPKCES256CodeChallenge(codeVerifier); 50 51 // Enable authorization remote control 52 new AuthorizationHandler().getPermissionPreference().put(true); 53 String url = parameters.getAuthorizationUrl(state, scopes) 54 + "&code_challenge_method=S256&code_challenge=" + s256CodeChallenge; 55 AuthorizationHandler.addAuthorizationConsumer(state, new OAuth20AuthorizationHandler(state, codeVerifier, parameters, consumer)); 56 OpenBrowser.displayUrl(url); 57 } 58 59 private static class OAuth20AuthorizationHandler implements AuthorizationHandler.AuthorizationConsumer { 60 61 private final String state; 62 private final IOAuthParameters parameters; 63 private final Consumer<IOAuthToken> consumer; 64 private final String codeVerifier; 65 66 OAuth20AuthorizationHandler(String state, String codeVerifier, IOAuthParameters parameters, Consumer<IOAuthToken> consumer) { 67 this.state = state; 68 this.parameters = parameters; 69 this.consumer = consumer; 70 this.codeVerifier = codeVerifier; 71 } 72 73 @Override 74 public void validateRequest(String sender, String request, Map<String, String> args) 75 throws RequestHandler.RequestHandlerBadRequestException { 76 String argState = args.get("state"); 77 if (!Objects.equals(this.state, argState)) { 78 throw new RequestHandler.RequestHandlerBadRequestException( 79 tr("Mismatched state: Expected {0} but got {1}", this.state, argState)); 80 } 81 } 82 83 @Override 84 public AuthorizationHandler.ResponseRecord handleRequest(String sender, String request, Map<String, String> args) 85 throws RequestHandler.RequestHandlerErrorException, RequestHandler.RequestHandlerBadRequestException { 86 String code = args.get("code"); 87 try { 88 HttpClient tradeCodeForToken = HttpClient.create(new URL(parameters.getAccessTokenUrl()), "POST"); 89 tradeCodeForToken.setRequestBody(("grant_type=authorization_code&client_id=" + parameters.getClientId() 90 + "&redirect_uri=" + parameters.getRedirectUri() 91 + "&code=" + code 92 + (this.codeVerifier != null ? "&code_verifier=" + this.codeVerifier : "") 93 ).getBytes(StandardCharsets.UTF_8)); 94 try { 95 tradeCodeForToken.connect(); 96 HttpClient.Response response = tradeCodeForToken.getResponse(); 97 OAuth20Token oAuth20Token = new OAuth20Token(parameters, response.getContentReader()); 98 consumer.accept(oAuth20Token); 99 } catch (IOException | OAuth20Exception e) { 100 consumer.accept(null); 101 throw new JosmRuntimeException(e); 102 } finally { 103 tradeCodeForToken.disconnect(); 104 } 105 } catch (MalformedURLException e) { 106 throw new JosmRuntimeException(e); 107 } 108 return null; 109 } 110 } 111 } -
new file core/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java b/core/src/org/openstreetmap/josm/data/oauth/OAuth20Exception.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import javax.json.JsonObject; 5 6 /** 7 * A generic OAuth 2.0 exception 8 */ 9 public class OAuth20Exception extends OAuthException { 10 /** 11 * Invalid request types 12 */ 13 public enum Type { 14 invalid_request, 15 invalid_client, 16 invalid_grant, 17 unauthorized_client, 18 unsupported_grant_type, 19 invalid_scope, 20 unknown 21 } 22 private final Type type; 23 24 public OAuth20Exception(Exception cause) { 25 super(cause); 26 this.type = Type.unknown; 27 } 28 29 public OAuth20Exception(String message) { 30 super(message); 31 this.type = Type.unknown; 32 } 33 34 OAuth20Exception(JsonObject serverMessage) { 35 super(serverMessage.getString("error_description", serverMessage.getString("error", "Unknown error"))); 36 if (serverMessage.containsKey("error")) { 37 switch(serverMessage.getString("error")) { 38 case "invalid_request": 39 case "invalid_client": 40 case "invalid_grant": 41 case "unauthorized_client": 42 case "unsupported_grant_type": 43 case "invalid_scope": 44 this.type = Type.valueOf(serverMessage.getString("error")); 45 break; 46 default: 47 this.type = Type.unknown; 48 } 49 } else { 50 this.type = Type.unknown; 51 } 52 } 53 54 @Override 55 OAuthVersion[] getOAuthVersions() { 56 return new OAuthVersion[] {OAuthVersion.OAuth20}; 57 } 58 59 @Override 60 public String getMessage() { 61 String message = super.getMessage(); 62 if (message == null) { 63 return "OAuth error " + this.type; 64 } 65 return "OAuth error " + this.type + ": " + message; 66 } 67 } -
new file core/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java b/core/src/org/openstreetmap/josm/data/oauth/OAuth20Parameters.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import java.io.ByteArrayInputStream; 5 import java.io.IOException; 6 import java.io.UncheckedIOException; 7 import java.nio.charset.StandardCharsets; 8 import java.util.Objects; 9 10 import javax.json.Json; 11 import javax.json.JsonObject; 12 import javax.json.JsonObjectBuilder; 13 import javax.json.JsonReader; 14 import javax.json.JsonStructure; 15 import javax.json.JsonValue; 16 17 import org.openstreetmap.josm.spi.preferences.Config; 18 19 /** 20 * Parameters for OAuth 2.0 21 * @author Taylor Smock 22 * @since xxx 23 */ 24 public final class OAuth20Parameters implements IOAuthParameters { 25 private static final String REDIRECT_URI = "redirect_uri"; 26 private static final String CLIENT_ID = "client_id"; 27 private static final String CLIENT_SECRET = "client_secret"; 28 private static final String TOKEN_URL = "token_url"; 29 private static final String AUTHORIZE_URL = "authorize_url"; 30 private static final String API_URL = "api_url"; 31 private final String redirectUri; 32 private final String clientSecret; 33 private final String clientId; 34 private final String tokenUrl; 35 private final String authorizeUrl; 36 private final String apiUrl; 37 38 /** 39 * Recreate a parameter object from a JSON string 40 * @param jsonString The JSON string with the required data 41 */ 42 public OAuth20Parameters(String jsonString) { 43 try (ByteArrayInputStream bais = new ByteArrayInputStream(jsonString.getBytes(StandardCharsets.UTF_8)); 44 JsonReader reader = Json.createReader(bais)) { 45 JsonStructure structure = reader.read(); 46 if (structure.getValueType() != JsonValue.ValueType.OBJECT) { 47 throw new IllegalArgumentException("Invalid JSON object: " + jsonString); 48 } 49 JsonObject jsonObject = structure.asJsonObject(); 50 this.redirectUri = jsonObject.getString(REDIRECT_URI); 51 this.clientId = jsonObject.getString(CLIENT_ID); 52 this.clientSecret = jsonObject.getString(CLIENT_SECRET, null); 53 this.tokenUrl = jsonObject.getString(TOKEN_URL); 54 this.authorizeUrl = jsonObject.getString(AUTHORIZE_URL); 55 this.apiUrl = jsonObject.getString(API_URL); 56 } catch (IOException e) { 57 // This should literally never happen -- ByteArrayInputStream does not do *anything* in the close method. 58 throw new UncheckedIOException(e); 59 } 60 } 61 62 /** 63 * Create a new OAuth parameter object 64 * @param clientId The client id. May not be {@code null}. 65 * @param clientSecret The client secret. May be {@code null}. Not currently used. 66 * @param baseUrl The base url. This assumes that the endpoints are {@code /token} and {@code /authorize}. 67 * @param apiUrl The API url 68 * @param redirectUri The redirect URI for the client. 69 */ 70 public OAuth20Parameters(String clientId, String clientSecret, String baseUrl, String apiUrl, String redirectUri) { 71 this(clientId, clientSecret, baseUrl + "/token", baseUrl + "/authorize", apiUrl, redirectUri); 72 } 73 74 /** 75 * Create a new OAuth parameter object 76 * @param clientId The client id. 77 * @param clientSecret The client secret. May be {@code null}. Not currently used. 78 * @param tokenUrl The token request URL (RFC6749 4.4.2) 79 * @param authorizeUrl The authorization request URL (RFC6749 4.1.1) 80 * @param apiUrl The API url 81 * @param redirectUri The redirect URI for the client. 82 */ 83 public OAuth20Parameters(String clientId, String clientSecret, String tokenUrl, String authorizeUrl, String apiUrl, String redirectUri) { 84 Objects.requireNonNull(authorizeUrl, "authorizeUrl"); 85 Objects.requireNonNull(clientId, "clientId"); 86 Objects.requireNonNull(redirectUri, "redirectUri"); 87 Objects.requireNonNull(tokenUrl, "tokenUrl"); 88 // Alternatively, we could try using rfc8414 ( /.well-known/oauth-authorization-server ), but OSM doesn't support it. 89 this.redirectUri = redirectUri; 90 this.clientId = clientId; 91 this.clientSecret = clientSecret; 92 this.tokenUrl = tokenUrl; 93 this.authorizeUrl = authorizeUrl; 94 this.apiUrl = apiUrl; 95 } 96 97 @Override 98 public String getAccessTokenUrl() { 99 return this.tokenUrl; 100 } 101 102 @Override 103 public String getAuthorizationUrl() { 104 return this.authorizeUrl; 105 } 106 107 @Override 108 public OAuthVersion getOAuthVersion() { 109 return OAuthVersion.OAuth20; 110 } 111 112 @Override 113 public String getClientId() { 114 return this.clientId; 115 } 116 117 @Override 118 public String getClientSecret() { 119 return this.clientSecret; 120 } 121 122 @Override 123 public String getRedirectUri() { 124 return this.redirectUri; 125 } 126 127 @Override 128 public String getApiUrl() { 129 return this.apiUrl; 130 } 131 132 @Override 133 public void rememberPreferences() { 134 Config.getPref().put("oauth.access-token.parameters." + OAuthVersion.OAuth20 + "." + this.apiUrl, 135 this.toPreferencesString()); 136 } 137 138 @Override 139 public String toPreferencesString() { 140 JsonObjectBuilder builder = Json.createObjectBuilder(); 141 builder.add(CLIENT_ID, this.clientId); 142 builder.add(REDIRECT_URI, this.redirectUri); 143 if (this.apiUrl != null) builder.add(API_URL, this.apiUrl); 144 if (this.authorizeUrl != null) builder.add(AUTHORIZE_URL, this.authorizeUrl); 145 if (this.clientSecret != null) builder.add(CLIENT_SECRET, this.clientSecret); 146 if (this.tokenUrl != null) builder.add(TOKEN_URL, this.tokenUrl); 147 return builder.build().toString(); 148 } 149 } -
new file core/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java b/core/src/org/openstreetmap/josm/data/oauth/OAuth20Token.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 import java.io.ByteArrayInputStream; 5 import java.io.IOException; 6 import java.io.InputStreamReader; 7 import java.io.Reader; 8 import java.net.URI; 9 import java.net.URL; 10 import java.nio.charset.StandardCharsets; 11 import java.time.Instant; 12 13 import javax.json.Json; 14 import javax.json.JsonObject; 15 import javax.json.JsonObjectBuilder; 16 import javax.json.JsonReader; 17 import javax.json.JsonStructure; 18 import javax.json.JsonValue; 19 20 import org.openstreetmap.josm.tools.HttpClient; 21 import org.openstreetmap.josm.tools.JosmRuntimeException; 22 23 /** 24 * Token holder for OAuth 2.0 25 * @author Taylor Smock 26 * @since xxx 27 */ 28 public final class OAuth20Token implements IOAuthToken { 29 private static final String ACCESS_TOKEN = "access_token"; 30 private static final String CREATED_AT = "created_at"; 31 private static final String EXPIRES_IN = "expires_in"; 32 private static final String REFRESH_TOKEN = "refresh_token"; 33 private static final String SCOPE = "scope"; 34 private static final String TOKEN_TYPE = "token_type"; 35 private final String accessToken; 36 private final String tokenType; 37 private final int expiresIn; 38 private final String refreshToken; 39 private final String[] scopes; 40 private final Instant createdAt; 41 private final IOAuthParameters oauthParameters; 42 43 /** 44 * Create a new OAuth token 45 * @param oauthParameters The parameters for the OAuth token 46 * @param json The stored JSON for the token 47 * @throws OAuth20Exception If the JSON creates an invalid token 48 */ 49 public OAuth20Token(IOAuthParameters oauthParameters, String json) throws OAuth20Exception { 50 this(oauthParameters, new InputStreamReader(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8)); 51 } 52 53 OAuth20Token(IOAuthParameters oauthParameters, Reader bufferedReader) throws OAuth20Exception { 54 this.oauthParameters = oauthParameters; 55 try (JsonReader reader = Json.createReader(bufferedReader)) { 56 JsonStructure structure = reader.read(); 57 if (structure.getValueType() != JsonValue.ValueType.OBJECT 58 || !structure.asJsonObject().containsKey(ACCESS_TOKEN) 59 || !structure.asJsonObject().containsKey(TOKEN_TYPE)) { 60 if (structure.getValueType() == JsonValue.ValueType.OBJECT 61 && structure.asJsonObject().containsKey("error")) { 62 throw new OAuth20Exception(structure.asJsonObject()); 63 } else { 64 throw new OAuth20Exception("Either " + ACCESS_TOKEN + " or " + TOKEN_TYPE + " is not present: " + structure); 65 } 66 } 67 JsonObject object = structure.asJsonObject(); 68 this.accessToken = object.getString(ACCESS_TOKEN); 69 this.tokenType = object.getString(TOKEN_TYPE); 70 this.expiresIn = object.getInt(EXPIRES_IN, Integer.MAX_VALUE); 71 this.refreshToken = object.getString(REFRESH_TOKEN, null); 72 this.scopes = object.getString(SCOPE, "").split(" "); 73 if (object.containsKey(CREATED_AT)) { 74 this.createdAt = Instant.ofEpochSecond(object.getJsonNumber(CREATED_AT).longValue()); 75 } else { 76 this.createdAt = Instant.now(); 77 } 78 } 79 } 80 81 @Override 82 public void sign(HttpClient client) throws OAuthException { 83 if (!this.oauthParameters.getApiUrl().contains(client.getURL().getHost())) { 84 String host = URI.create(this.oauthParameters.getAccessTokenUrl()).getHost(); 85 throw new IllegalArgumentException("Cannot sign URL with token for different host: Expected " + host 86 + " but got " + client.getURL().getHost()); 87 } 88 if (this.getBearerToken() != null) { 89 client.setHeader("Authorization", "Bearer " + this.getBearerToken()); 90 return; 91 } 92 throw new OAuth20Exception("Unknown token type: " + this.tokenType); 93 } 94 95 /** 96 * Get the OAuth 2.0 bearer token 97 * @return The bearer token. May return {@code null} if the token type is not a bearer type. 98 */ 99 public String getBearerToken() { 100 if ("bearer".equalsIgnoreCase(this.tokenType)) { 101 return this.accessToken; 102 } 103 return null; 104 } 105 106 @Override 107 public String toPreferencesString() { 108 final OAuth20Token tokenToSave; 109 if (shouldRefresh()) { 110 tokenToSave = refresh(); 111 } else { 112 tokenToSave = this; 113 } 114 JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder(); 115 jsonObjectBuilder.add(ACCESS_TOKEN, tokenToSave.accessToken); 116 jsonObjectBuilder.add(TOKEN_TYPE, tokenToSave.tokenType); 117 if (tokenToSave.createdAt != null) { 118 jsonObjectBuilder.add(CREATED_AT, tokenToSave.createdAt.getEpochSecond()); 119 } 120 if (tokenToSave.expiresIn != Integer.MAX_VALUE) { 121 jsonObjectBuilder.add(EXPIRES_IN, tokenToSave.expiresIn); 122 } 123 if (tokenToSave.refreshToken != null) { 124 jsonObjectBuilder.add(REFRESH_TOKEN, tokenToSave.refreshToken); 125 } 126 if (tokenToSave.scopes.length > 0) { 127 jsonObjectBuilder.add(SCOPE, String.join(" ", tokenToSave.scopes)); 128 } 129 return jsonObjectBuilder.build().toString(); 130 } 131 132 @Override 133 public OAuthVersion getOAuthType() { 134 return OAuthVersion.OAuth20; 135 } 136 137 @Override 138 public IOAuthParameters getParameters() { 139 return this.oauthParameters; 140 } 141 142 /** 143 * Check if the token should be refreshed 144 * @return {@code true} if the token should be refreshed 145 */ 146 boolean shouldRefresh() { 147 return this.refreshToken != null && this.expiresIn != Integer.MAX_VALUE 148 // We should refresh the token when 10% of its lifespan has been spent. 149 // We aren't an application that will be used every day by every user. 150 && this.createdAt.getEpochSecond() + this.expiresIn < Instant.now().getEpochSecond() - this.expiresIn * 9L / 10; 151 } 152 153 /** 154 * Refresh the OAuth 2.0 token 155 * @return The new token to use 156 */ 157 OAuth20Token refresh() { 158 // This bit isn't necessarily OAuth 2.1 compliant. Spec isn't finished yet, but 159 // refresh tokens will either be sender constrained or some kind of rotation or both. 160 // This refresh code handles rotation, mostly by creating a new OAuth20Token. :) 161 // For sender constrained, it will likely allow self-signed certificates (RFC8705). 162 // Note: OSM doesn't have age limits on their tokens, at time of writing. 163 String refresh = "grant_type=refresh_token&refresh_token=" + this.refreshToken; 164 if (this.scopes.length > 0) { 165 refresh += "&scope=" + String.join(" ", this.scopes); 166 } 167 HttpClient client = null; 168 try { 169 client = HttpClient.create(new URL(this.oauthParameters.getAccessTokenUrl()), "POST"); 170 client.setRequestBody(refresh.getBytes(StandardCharsets.UTF_8)); 171 client.connect(); 172 HttpClient.Response response = client.getResponse(); 173 return new OAuth20Token(this.oauthParameters, response.getContentReader()); 174 } catch (IOException | OAuth20Exception e) { 175 throw new JosmRuntimeException(e); 176 } finally { 177 if (client != null) { 178 client.disconnect(); 179 } 180 } 181 } 182 } -
core/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java b/core/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java
a b 3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.net.URI; 7 import java.util.EnumMap; 8 import java.util.HashMap; 9 import java.util.Map; 10 6 11 import org.openstreetmap.josm.io.auth.CredentialsAgent; 7 12 import org.openstreetmap.josm.io.auth.CredentialsAgentException; 8 13 import org.openstreetmap.josm.spi.preferences.Config; … … 31 36 private String accessTokenKey; 32 37 private String accessTokenSecret; 33 38 39 private final Map<String, Map<OAuthVersion, IOAuthToken>> tokenMap = new HashMap<>(); 40 34 41 /** 35 42 * Replies true if current access token should be saved to the preferences file. 36 43 * … … 102 109 return new OAuthToken(accessTokenKey, accessTokenSecret); 103 110 } 104 111 112 /** 113 * Replies the access token. 114 * @param api The api the token is for 115 * @param version The OAuth version the token is for 116 * @return the access token, can be {@code null} 117 */ 118 public IOAuthToken getAccessToken(String api, OAuthVersion version) { 119 api = URI.create(api).getHost(); 120 if (this.tokenMap.containsKey(api)) { 121 Map<OAuthVersion, IOAuthToken> map = this.tokenMap.get(api); 122 return map.get(version); 123 } 124 return null; 125 } 126 105 127 /** 106 128 * Sets the access token hold by this holder. 107 129 * … … 128 150 } 129 151 } 130 152 153 /** 154 * Sets the access token hold by this holder. 155 * 156 * @param api The api the token is for 157 * @param token the access token. Can be null to clear the content in this holder. 158 */ 159 public void setAccessToken(String api, IOAuthToken token) { 160 api = URI.create(api).getHost(); 161 if (token == null) { 162 this.tokenMap.remove(api); 163 } else { 164 this.tokenMap.computeIfAbsent(api, key -> new EnumMap<>(OAuthVersion.class)).put(token.getOAuthType(), token); 165 } 166 } 167 131 168 /** 132 169 * Replies true if this holder contains an complete access token, consisting of an 133 170 * Access Token Key and an Access Token Secret. … … 175 212 try { 176 213 if (!saveToPreferences) { 177 214 cm.storeOAuthAccessToken(null); 215 for (String host : this.tokenMap.keySet()) { 216 cm.storeOAuthAccessToken(host, null); 217 } 178 218 } else { 179 cm.storeOAuthAccessToken(new OAuthToken(accessTokenKey, accessTokenSecret)); 219 if (this.accessTokenKey != null && this.accessTokenSecret != null) { 220 cm.storeOAuthAccessToken(new OAuthToken(accessTokenKey, accessTokenSecret)); 221 } 222 for (Map.Entry<String, Map<OAuthVersion, IOAuthToken>> entry : this.tokenMap.entrySet()) { 223 for (OAuthVersion version : OAuthVersion.values()) { 224 if (entry.getValue().containsKey(version)) { 225 cm.storeOAuthAccessToken(entry.getKey(), entry.getValue().get(version)); 226 } 227 } 228 } 180 229 } 181 230 } catch (CredentialsAgentException e) { 182 231 Logging.error(e); -
new file core/src/org/openstreetmap/josm/data/oauth/OAuthException.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuthException.java b/core/src/org/openstreetmap/josm/data/oauth/OAuthException.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 /** 5 * Base OAuth exception 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public abstract class OAuthException extends Exception { 10 OAuthException(Exception cause) { 11 super(cause); 12 } 13 14 OAuthException(String message) { 15 super(message); 16 } 17 18 abstract OAuthVersion[] getOAuthVersions(); 19 } -
core/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java b/core/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java
a b 1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.oauth; 3 3 4 import java.io.BufferedReader; 5 import java.io.IOException; 6 import java.net.URL; 4 7 import java.util.Objects; 5 8 9 import javax.json.Json; 10 import javax.json.JsonObject; 11 import javax.json.JsonReader; 12 import javax.json.JsonStructure; 13 import javax.json.JsonValue; 14 15 import org.openstreetmap.josm.io.auth.CredentialsAgentException; 16 import org.openstreetmap.josm.io.auth.CredentialsManager; 6 17 import org.openstreetmap.josm.spi.preferences.Config; 7 18 import org.openstreetmap.josm.spi.preferences.IUrls; 8 19 import org.openstreetmap.josm.tools.CheckParameterUtil; 20 import org.openstreetmap.josm.tools.HttpClient; 21 import org.openstreetmap.josm.tools.Logging; 9 22 import org.openstreetmap.josm.tools.Utils; 10 23 11 24 import oauth.signpost.OAuthConsumer; … … 15 28 * This class manages an immutable set of OAuth parameters. 16 29 * @since 2747 17 30 */ 18 public class OAuthParameters {31 public class OAuthParameters implements IOAuthParameters { 19 32 20 33 /** 21 34 * The default JOSM OAuth consumer key (created by user josmeditor). … … 46 59 * @since 5422 47 60 */ 48 61 public static OAuthParameters createDefault(String apiUrl) { 62 return (OAuthParameters) createDefault(apiUrl, OAuthVersion.OAuth10a); 63 } 64 65 /** 66 * Replies a set of default parameters for a consumer accessing an OSM server 67 * at the given API url. URL parameters are only set if the URL equals {@link IUrls#getDefaultOsmApiUrl} 68 * or references the domain "dev.openstreetmap.org", otherwise they may be <code>null</code>. 69 * 70 * @param apiUrl The API URL for which the OAuth default parameters are created. If null or empty, the default OSM API url is used. 71 * @param oAuthVersion The OAuth version to create default parameters for 72 * @return a set of default parameters for the given {@code apiUrl} 73 * @since xxx 74 */ 75 public static IOAuthParameters createDefault(String apiUrl, OAuthVersion oAuthVersion) { 76 if (!Utils.isValidUrl(apiUrl)) { 77 apiUrl = null; 78 } 79 80 switch (oAuthVersion) { 81 case OAuth10a: 82 return getDefaultOAuth10Parameters(apiUrl); 83 case OAuth20: 84 case OAuth21: // For now, OAuth 2.1 (draft) is just OAuth 2.0 with mandatory extensions, which we implement. 85 return getDefaultOAuth20Parameters(apiUrl); 86 default: 87 throw new IllegalArgumentException("Unknown OAuth version: " + oAuthVersion); 88 } 89 } 90 91 /** 92 * Get the default OAuth 2.0 parameters 93 * @param apiUrl The API url 94 * @return The default parameters 95 */ 96 private static OAuth20Parameters getDefaultOAuth20Parameters(String apiUrl) { 97 final String clientId; 98 final String clientSecret; 99 final String redirectUri; 100 final String baseUrl; 101 if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl)) { 102 clientId = ""; 103 clientSecret = ""; 104 baseUrl = apiUrl; 105 HttpClient client = null; 106 redirectUri = ""; 107 // Check if the server is RFC 8414 compliant 108 try { 109 client = HttpClient.create(new URL(apiUrl + (apiUrl.endsWith("/") ? "" : "/") + ".well-known/oauth-authorization-server")); 110 HttpClient.Response response = client.connect(); 111 if (response.getResponseCode() == 200) { 112 try (BufferedReader reader = response.getContentReader(); 113 JsonReader jsonReader = Json.createReader(reader)) { 114 JsonStructure structure = jsonReader.read(); 115 if (structure.getValueType() == JsonValue.ValueType.OBJECT) { 116 return parseAuthorizationServerMetadataResponse(clientId, clientSecret, apiUrl, 117 redirectUri, structure.asJsonObject()); 118 } 119 } 120 } 121 } catch (IOException | OAuthException e) { 122 Logging.trace(e); 123 } finally { 124 if (client != null) client.disconnect(); 125 } 126 } else { 127 clientId = "jo32l-hRbwWni_lnwQxffvSEfW-5agfuzoT8A33IFkY"; 128 clientSecret = "OGH6-yYZHYAbw2J_LFS1yu1QiVFrCONhYWisftr6LRs"; 129 baseUrl = "https://www.openstreetmap.org/oauth2"; 130 redirectUri = "http://127.0.0.1:8111/oauth_authorization"; 131 } 132 return new OAuth20Parameters(clientId, clientSecret, baseUrl, apiUrl, redirectUri); 133 } 134 135 /** 136 * Parse the response from <a href="https://www.rfc-editor.org/rfc/rfc8414.html">RFC 8414</a> 137 * (OAuth 2.0 Authorization Server Metadata) 138 * @return The parameters for the server metadata 139 */ 140 private static OAuth20Parameters parseAuthorizationServerMetadataResponse(String clientId, String clientSecret, 141 String apiUrl, String redirectUri, 142 JsonObject serverMetadata) 143 throws OAuthException { 144 final String authorizationEndpoint = serverMetadata.getString("authorization_endpoint", null); 145 final String tokenEndpoint = serverMetadata.getString("token_endpoint", null); 146 // This may also have additional documentation like what the endpoints allow (e.g. scopes, algorithms, etc.) 147 if (authorizationEndpoint == null || tokenEndpoint == null) { 148 throw new OAuth20Exception("Either token endpoint or authorization endpoints are missing"); 149 } 150 return new OAuth20Parameters(clientId, clientSecret, tokenEndpoint, authorizationEndpoint, apiUrl, redirectUri); 151 } 152 153 /** 154 * Get the default OAuth 1.0a parameters 155 * @param apiUrl The api url 156 * @return The default parameters 157 */ 158 private static OAuthParameters getDefaultOAuth10Parameters(String apiUrl) { 49 159 final String consumerKey; 50 160 final String consumerSecret; 51 161 final String serverUrl; 52 162 53 if (!Utils.isValidUrl(apiUrl)) {54 apiUrl = null;55 }56 57 163 if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl)) { 58 164 consumerKey = ""; // a custom consumer key is required 59 165 consumerSecret = ""; // a custom consumer secret is requireds … … 81 187 * @return the parameters 82 188 */ 83 189 public static OAuthParameters createFromApiUrl(String apiUrl) { 84 OAuthParameters parameters = createDefault(apiUrl); 85 return new OAuthParameters( 86 Config.getPref().get("oauth.settings.consumer-key", parameters.getConsumerKey()), 87 Config.getPref().get("oauth.settings.consumer-secret", parameters.getConsumerSecret()), 88 Config.getPref().get("oauth.settings.request-token-url", parameters.getRequestTokenUrl()), 89 Config.getPref().get("oauth.settings.access-token-url", parameters.getAccessTokenUrl()), 90 Config.getPref().get("oauth.settings.authorise-url", parameters.getAuthoriseUrl()), 91 Config.getPref().get("oauth.settings.osm-login-url", parameters.getOsmLoginUrl()), 92 Config.getPref().get("oauth.settings.osm-logout-url", parameters.getOsmLogoutUrl())); 190 return (OAuthParameters) createFromApiUrl(apiUrl, OAuthVersion.OAuth10a); 191 } 192 193 /** 194 * Replies a set of parameters as defined in the preferences. 195 * 196 * @param oAuthVersion The OAuth version to use. 197 * @param apiUrl the API URL. Must not be {@code null}. 198 * @return the parameters 199 * @since xxx 200 */ 201 public static IOAuthParameters createFromApiUrl(String apiUrl, OAuthVersion oAuthVersion) { 202 IOAuthParameters parameters = createDefault(apiUrl, oAuthVersion); 203 switch (oAuthVersion) { 204 case OAuth10a: 205 OAuthParameters oauth10aParameters = (OAuthParameters) parameters; 206 return new OAuthParameters( 207 Config.getPref().get("oauth.settings.consumer-key", oauth10aParameters.getConsumerKey()), 208 Config.getPref().get("oauth.settings.consumer-secret", oauth10aParameters.getConsumerSecret()), 209 Config.getPref().get("oauth.settings.request-token-url", oauth10aParameters.getRequestTokenUrl()), 210 Config.getPref().get("oauth.settings.access-token-url", oauth10aParameters.getAccessTokenUrl()), 211 Config.getPref().get("oauth.settings.authorise-url", oauth10aParameters.getAuthoriseUrl()), 212 Config.getPref().get("oauth.settings.osm-login-url", oauth10aParameters.getOsmLoginUrl()), 213 Config.getPref().get("oauth.settings.osm-logout-url", oauth10aParameters.getOsmLogoutUrl())); 214 case OAuth20: 215 case OAuth21: // Right now, OAuth 2.1 will work with our OAuth 2.0 implementation 216 OAuth20Parameters oAuth20Parameters = (OAuth20Parameters) parameters; 217 try { 218 IOAuthToken storedToken = CredentialsManager.getInstance().lookupOAuthAccessToken(apiUrl); 219 return storedToken.getParameters(); 220 } catch (CredentialsAgentException e) { 221 Logging.trace(e); 222 } 223 return oAuth20Parameters; 224 default: 225 throw new IllegalArgumentException("Unknown OAuth version: " + oAuthVersion); 226 } 93 227 } 94 228 95 229 /** 96 230 * Remembers the current values in the preferences. 97 231 */ 232 @Override 98 233 public void rememberPreferences() { 99 234 Config.getPref().put("oauth.settings.consumer-key", getConsumerKey()); 100 235 Config.getPref().put("oauth.settings.consumer-secret", getConsumerSecret()); … … 182 317 * Gets the access token URL. 183 318 * @return The access token URL 184 319 */ 320 @Override 185 321 public String getAccessTokenUrl() { 186 322 return accessTokenUrl; 187 323 } 188 324 325 @Override 326 public String getAuthorizationUrl() { 327 return this.authoriseUrl; 328 } 329 330 @Override 331 public OAuthVersion getOAuthVersion() { 332 return OAuthVersion.OAuth10a; 333 } 334 335 @Override 336 public String getClientId() { 337 return this.consumerKey; 338 } 339 340 @Override 341 public String getClientSecret() { 342 return this.consumerSecret; 343 } 344 189 345 /** 190 346 * Gets the authorise URL. 191 347 * @return The authorise URL 192 348 */ 193 349 public String getAuthoriseUrl() { 194 return authoriseUrl;350 return this.getAuthorizationUrl(); 195 351 } 196 352 197 353 /** -
new file core/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java b/core/src/org/openstreetmap/josm/data/oauth/OAuthVersion.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth; 3 4 /** 5 * The OAuth versions ordered oldest to newest 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public enum OAuthVersion { 10 /** <a href="https://oauth.net/core/1.0a/">OAuth 1.0a</a> */ 11 OAuth10a, 12 /** <a href="https://datatracker.ietf.org/doc/html/rfc6749">OAuth 2.0</a> */ 13 OAuth20, 14 /** <a href="https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-06">OAuth 2.1 (draft)</a> */ 15 OAuth21 16 } -
new file core/src/org/openstreetmap/josm/data/oauth/osm/OsmScopes.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/osm/OsmScopes.java b/core/src/org/openstreetmap/josm/data/oauth/osm/OsmScopes.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.oauth.osm; 3 4 /** 5 * The possible scopes for OSM 6 * @author Taylor Smock 7 * @since xxx 8 */ 9 public enum OsmScopes { 10 /** Read user preferences */ 11 read_prefs, 12 /** Modify user preferences */ 13 write_prefs, 14 /** Write diary posts */ 15 write_diary, 16 /** Modify the map */ 17 write_api, 18 /** Read private GPS traces */ 19 read_gpx, 20 /** Upload GPS traces */ 21 write_gpx, 22 /** Modify notes */ 23 write_notes 24 } -
new file core/src/org/openstreetmap/josm/data/oauth/osm/package-info.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/data/oauth/osm/package-info.java b/core/src/org/openstreetmap/josm/data/oauth/osm/package-info.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 3 /** 4 * Provides the classes for OAuth authentication to OSM. 5 */ 6 package org.openstreetmap.josm.data.oauth.osm; -
core/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java b/core/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java
a b 3 3 4 4 import java.util.Objects; 5 5 6 import org.openstreetmap.josm.data.oauth.IOAuthParameters; 6 7 import org.openstreetmap.josm.data.oauth.OAuthParameters; 7 8 import org.openstreetmap.josm.data.oauth.OAuthToken; 9 import org.openstreetmap.josm.data.oauth.OAuthVersion; 8 10 import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 9 11 import org.openstreetmap.josm.tools.CheckParameterUtil; 10 12 … … 20 22 public static final String ACCESS_TOKEN_PROP = AbstractAuthorizationUI.class.getName() + ".accessToken"; 21 23 22 24 private String apiUrl; 23 private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel( );25 private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(OAuthVersion.OAuth10a); 24 26 private transient OAuthToken accessToken; 25 27 26 28 /** … … 79 81 * 80 82 * @return the current set of advanced OAuth parameters in this UI 81 83 */ 82 public OAuthParameters getOAuthParameters() {84 public IOAuthParameters getOAuthParameters() { 83 85 return pnlAdvancedProperties.getAdvancedParameters(); 84 86 } 85 87 -
core/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java b/core/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java
a b 15 15 import javax.swing.JLabel; 16 16 import javax.swing.JOptionPane; 17 17 18 import org.openstreetmap.josm.data.oauth.IOAuthParameters; 19 import org.openstreetmap.josm.data.oauth.OAuth20Parameters; 18 20 import org.openstreetmap.josm.data.oauth.OAuthParameters; 21 import org.openstreetmap.josm.data.oauth.OAuthVersion; 19 22 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 20 23 import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec; 21 24 import org.openstreetmap.josm.gui.help.HelpUtil; … … 51 54 private final JosmTextField tfAuthoriseURL = new JosmTextField(); 52 55 private final JosmTextField tfOsmLoginURL = new JosmTextField(); 53 56 private final JosmTextField tfOsmLogoutURL = new JosmTextField(); 57 private final OAuthVersion oauthVersion; 54 58 private transient UseDefaultItemListener ilUseDefault; 55 59 private String apiUrl; 56 60 57 61 /** 58 62 * Constructs a new {@code AdvancedOAuthPropertiesPanel}. 63 * @param oauthVersion The OAuth version to make the panel for 59 64 */ 60 public AdvancedOAuthPropertiesPanel() { 65 public AdvancedOAuthPropertiesPanel(OAuthVersion oauthVersion) { 66 this.oauthVersion = oauthVersion; 61 67 build(); 62 68 } 63 69 … … 74 80 add(cbUseDefaults, gc); 75 81 76 82 // -- consumer key 77 gc.gridy = 1;83 gc.gridy++; 78 84 gc.weightx = 0.0; 79 85 gc.gridwidth = 1; 80 add(new JLabel(tr("Consumer Key:")), gc); 86 if (this.oauthVersion == OAuthVersion.OAuth10a) { 87 add(new JLabel(tr("Consumer Key:")), gc); 88 } else { 89 add(new JLabel(tr("Client ID:")), gc); 90 } 81 91 82 92 gc.gridx = 1; 83 93 gc.weightx = 1.0; … … 85 95 SelectAllOnFocusGainedDecorator.decorate(tfConsumerKey); 86 96 87 97 // -- consumer secret 88 gc.gridy = 2;98 gc.gridy++; 89 99 gc.gridx = 0; 90 100 gc.weightx = 0.0; 91 add(new JLabel(tr("Consumer Secret:")), gc); 101 if (this.oauthVersion == OAuthVersion.OAuth10a) { 102 add(new JLabel(tr("Consumer Secret:")), gc); 103 } else { 104 add(new JLabel(tr("Client Secret:")), gc); 105 } 92 106 93 107 gc.gridx = 1; 94 108 gc.weightx = 1.0; … … 96 110 SelectAllOnFocusGainedDecorator.decorate(tfConsumerSecret); 97 111 98 112 // -- request token URL 99 gc.gridy = 3;113 gc.gridy++; 100 114 gc.gridx = 0; 101 115 gc.weightx = 0.0; 102 add(new JLabel(tr("Request Token URL:")), gc); 116 if (this.oauthVersion == OAuthVersion.OAuth10a) { 117 add(new JLabel(tr("Request Token URL:")), gc); 118 } else { 119 add(new JLabel(tr("Redirect URL:")), gc); 120 } 103 121 104 122 gc.gridx = 1; 105 123 gc.weightx = 1.0; … … 107 125 SelectAllOnFocusGainedDecorator.decorate(tfRequestTokenURL); 108 126 109 127 // -- access token URL 110 gc.gridy = 4;128 gc.gridy++; 111 129 gc.gridx = 0; 112 130 gc.weightx = 0.0; 113 131 add(new JLabel(tr("Access Token URL:")), gc); … … 118 136 SelectAllOnFocusGainedDecorator.decorate(tfAccessTokenURL); 119 137 120 138 // -- authorise URL 121 gc.gridy = 5;139 gc.gridy++; 122 140 gc.gridx = 0; 123 141 gc.weightx = 0.0; 124 142 add(new JLabel(tr("Authorize URL:")), gc); … … 128 146 add(tfAuthoriseURL, gc); 129 147 SelectAllOnFocusGainedDecorator.decorate(tfAuthoriseURL); 130 148 131 // -- OSM login URL 132 gc.gridy = 6; 133 gc.gridx = 0; 134 gc.weightx = 0.0; 135 add(new JLabel(tr("OSM login URL:")), gc); 149 if (this.oauthVersion == OAuthVersion.OAuth10a) { 150 // -- OSM login URL 151 gc.gridy++; 152 gc.gridx = 0; 153 gc.weightx = 0.0; 154 add(new JLabel(tr("OSM login URL:")), gc); 136 155 137 gc.gridx = 1;138 gc.weightx = 1.0;139 add(tfOsmLoginURL, gc);140 SelectAllOnFocusGainedDecorator.decorate(tfOsmLoginURL);156 gc.gridx = 1; 157 gc.weightx = 1.0; 158 add(tfOsmLoginURL, gc); 159 SelectAllOnFocusGainedDecorator.decorate(tfOsmLoginURL); 141 160 142 // -- OSM logout URL143 gc.gridy = 7;144 gc.gridx = 0;145 gc.weightx = 0.0;146 add(new JLabel(tr("OSM logout URL:")), gc);161 // -- OSM logout URL 162 gc.gridy++; 163 gc.gridx = 0; 164 gc.weightx = 0.0; 165 add(new JLabel(tr("OSM logout URL:")), gc); 147 166 148 gc.gridx = 1; 149 gc.weightx = 1.0; 150 add(tfOsmLogoutURL, gc); 151 SelectAllOnFocusGainedDecorator.decorate(tfOsmLogoutURL); 167 gc.gridx = 1; 168 gc.weightx = 1.0; 169 add(tfOsmLogoutURL, gc); 170 SelectAllOnFocusGainedDecorator.decorate(tfOsmLogoutURL); 171 } 152 172 153 173 ilUseDefault = new UseDefaultItemListener(); 154 174 cbUseDefaults.addItemListener(ilUseDefault); … … 191 211 192 212 protected void resetToDefaultSettings() { 193 213 cbUseDefaults.setSelected(true); 194 OAuthParameters params = OAuthParameters.createDefault(apiUrl); 195 tfConsumerKey.setText(params.getConsumerKey()); 196 tfConsumerSecret.setText(params.getConsumerSecret()); 197 tfRequestTokenURL.setText(params.getRequestTokenUrl()); 198 tfAccessTokenURL.setText(params.getAccessTokenUrl()); 199 tfAuthoriseURL.setText(params.getAuthoriseUrl()); 200 tfOsmLoginURL.setText(params.getOsmLoginUrl()); 201 tfOsmLogoutURL.setText(params.getOsmLogoutUrl()); 214 IOAuthParameters iParams = OAuthParameters.createDefault(apiUrl, this.oauthVersion); 215 switch (this.oauthVersion) { 216 case OAuth10a: 217 OAuthParameters params = (OAuthParameters) iParams; 218 tfConsumerKey.setText(params.getConsumerKey()); 219 tfConsumerSecret.setText(params.getConsumerSecret()); 220 tfRequestTokenURL.setText(params.getRequestTokenUrl()); 221 tfAccessTokenURL.setText(params.getAccessTokenUrl()); 222 tfAuthoriseURL.setText(params.getAuthoriseUrl()); 223 tfOsmLoginURL.setText(params.getOsmLoginUrl()); 224 tfOsmLogoutURL.setText(params.getOsmLogoutUrl()); 225 break; 226 case OAuth20: 227 case OAuth21: 228 OAuth20Parameters params20 = (OAuth20Parameters) iParams; 229 tfConsumerKey.setText(params20.getClientId()); 230 tfConsumerSecret.setText(params20.getClientSecret()); 231 tfAccessTokenURL.setText(params20.getAccessTokenUrl()); 232 tfAuthoriseURL.setText(params20.getAuthorizationUrl()); 233 tfRequestTokenURL.setText(params20.getRedirectUri()); 234 } 202 235 203 236 setChildComponentsEnabled(false); 204 237 } … … 216 249 * 217 250 * @return the OAuth parameters 218 251 */ 219 public OAuthParameters getAdvancedParameters() {252 public IOAuthParameters getAdvancedParameters() { 220 253 if (cbUseDefaults.isSelected()) 221 return OAuthParameters.createDefault(apiUrl); 222 return new OAuthParameters( 223 tfConsumerKey.getText(), 224 tfConsumerSecret.getText(), 225 tfRequestTokenURL.getText(), 226 tfAccessTokenURL.getText(), 227 tfAuthoriseURL.getText(), 228 tfOsmLoginURL.getText(), 229 tfOsmLogoutURL.getText()); 254 return OAuthParameters.createDefault(apiUrl, this.oauthVersion); 255 if (this.oauthVersion == OAuthVersion.OAuth10a) { 256 return new OAuthParameters( 257 tfConsumerKey.getText(), 258 tfConsumerSecret.getText(), 259 tfRequestTokenURL.getText(), 260 tfAccessTokenURL.getText(), 261 tfAuthoriseURL.getText(), 262 tfOsmLoginURL.getText(), 263 tfOsmLogoutURL.getText()); 264 } 265 return new OAuth20Parameters( 266 tfConsumerKey.getText(), 267 tfConsumerSecret.getText(), 268 tfAuthoriseURL.getText(), 269 tfAccessTokenURL.getText(), 270 tfRequestTokenURL.getText() 271 ); 230 272 } 231 273 232 274 /** … … 235 277 * @param parameters the advanced parameters. Must not be null. 236 278 * @throws IllegalArgumentException if parameters is null. 237 279 */ 238 public void setAdvancedParameters( OAuthParameters parameters) {280 public void setAdvancedParameters(IOAuthParameters parameters) { 239 281 CheckParameterUtil.ensureParameterNotNull(parameters, "parameters"); 240 if (parameters.equals(OAuthParameters.createDefault(apiUrl ))) {282 if (parameters.equals(OAuthParameters.createDefault(apiUrl, parameters.getOAuthVersion()))) { 241 283 cbUseDefaults.setSelected(true); 242 284 setChildComponentsEnabled(false); 243 285 } else { 244 286 cbUseDefaults.setSelected(false); 245 287 setChildComponentsEnabled(true); 246 tfConsumerKey.setText(parameters.getConsumerKey() == null ? "" : parameters.getConsumerKey()); 247 tfConsumerSecret.setText(parameters.getConsumerSecret() == null ? "" : parameters.getConsumerSecret()); 248 tfRequestTokenURL.setText(parameters.getRequestTokenUrl() == null ? "" : parameters.getRequestTokenUrl()); 249 tfAccessTokenURL.setText(parameters.getAccessTokenUrl() == null ? "" : parameters.getAccessTokenUrl()); 250 tfAuthoriseURL.setText(parameters.getAuthoriseUrl() == null ? "" : parameters.getAuthoriseUrl()); 251 tfOsmLoginURL.setText(parameters.getOsmLoginUrl() == null ? "" : parameters.getOsmLoginUrl()); 252 tfOsmLogoutURL.setText(parameters.getOsmLogoutUrl() == null ? "" : parameters.getOsmLogoutUrl()); 288 if (parameters instanceof OAuthParameters) { 289 OAuthParameters parameters10 = (OAuthParameters) parameters; 290 tfConsumerKey.setText(parameters10.getConsumerKey() == null ? "" : parameters10.getConsumerKey()); 291 tfConsumerSecret.setText(parameters10.getConsumerSecret() == null ? "" : parameters10.getConsumerSecret()); 292 tfRequestTokenURL.setText(parameters10.getRequestTokenUrl() == null ? "" : parameters10.getRequestTokenUrl()); 293 tfAccessTokenURL.setText(parameters10.getAccessTokenUrl() == null ? "" : parameters10.getAccessTokenUrl()); 294 tfAuthoriseURL.setText(parameters10.getAuthoriseUrl() == null ? "" : parameters10.getAuthoriseUrl()); 295 tfOsmLoginURL.setText(parameters10.getOsmLoginUrl() == null ? "" : parameters10.getOsmLoginUrl()); 296 tfOsmLogoutURL.setText(parameters10.getOsmLogoutUrl() == null ? "" : parameters10.getOsmLogoutUrl()); 297 } else if (parameters instanceof OAuth20Parameters) { 298 OAuth20Parameters parameters20 = (OAuth20Parameters) parameters; 299 tfConsumerKey.setText(parameters20.getClientId()); 300 tfConsumerSecret.setText(parameters20.getClientSecret()); 301 tfAccessTokenURL.setText(parameters20.getAccessTokenUrl()); 302 tfAuthoriseURL.setText(parameters20.getAuthorizationUrl()); 303 tfRequestTokenURL.setText(parameters20.getRedirectUri()); 304 } 253 305 } 254 306 } 255 307 -
core/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java b/core/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java
a b 28 28 import javax.swing.text.JTextComponent; 29 29 import javax.swing.text.html.HTMLEditorKit; 30 30 31 import org.openstreetmap.josm.data.oauth.OAuthParameters; 31 32 import org.openstreetmap.josm.data.oauth.OAuthToken; 32 33 import org.openstreetmap.josm.gui.HelpAwareOptionPane; 33 34 import org.openstreetmap.josm.gui.PleaseWaitRunnable; … … 384 385 executor.execute(new TestAccessTokenTask( 385 386 FullyAutomaticAuthorizationUI.this, 386 387 getApiUrl(), 387 getAdvancedPropertiesPanel().getAdvancedParameters(),388 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), 388 389 getAccessToken() 389 390 )); 390 391 } … … 437 438 + "a valid login URL from the OAuth Authorize Endpoint URL ''{0}''.<br><br>" 438 439 + "Please check your advanced setting and try again." 439 440 + "</html>", 440 getAdvancedPropertiesPanel().getAdvancedParameters().getAuthoriseUrl()),441 ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getAuthoriseUrl()), 441 442 tr("OAuth authorization failed"), 442 443 JOptionPane.ERROR_MESSAGE, 443 444 HelpUtil.ht("/Dialog/OAuthAuthorisationWizard#FullyAutomaticProcessFailed") … … 445 446 } 446 447 447 448 protected void alertLoginFailed() { 448 final String loginUrl = getAdvancedPropertiesPanel().getAdvancedParameters().getOsmLoginUrl();449 final String loginUrl = ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getOsmLoginUrl(); 449 450 HelpAwareOptionPane.showOptionDialog( 450 451 FullyAutomaticAuthorizationUI.this, 451 452 tr("<html>" … … 479 480 try { 480 481 getProgressMonitor().setTicksCount(3); 481 482 OsmOAuthAuthorizationClient authClient = new OsmOAuthAuthorizationClient( 482 getAdvancedPropertiesPanel().getAdvancedParameters()483 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() 483 484 ); 484 485 OAuthToken requestToken = authClient.getRequestToken( 485 486 getProgressMonitor().createSubTaskMonitor(1, false) -
core/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java b/core/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java
a b 25 25 import javax.swing.text.JTextComponent; 26 26 27 27 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 28 import org.openstreetmap.josm.data.oauth.OAuthParameters; 28 29 import org.openstreetmap.josm.data.oauth.OAuthToken; 29 30 import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator; 30 31 import org.openstreetmap.josm.gui.widgets.HtmlPanel; … … 232 233 TestAccessTokenTask task = new TestAccessTokenTask( 233 234 ManualAuthorizationUI.this, 234 235 getApiUrl(), 235 getAdvancedPropertiesPanel().getAdvancedParameters(),236 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), 236 237 getAccessToken() 237 238 ); 238 239 executor.execute(task); -
core/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java b/core/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java
a b 255 255 * @return the current OAuth parameters. 256 256 */ 257 257 public OAuthParameters getOAuthParameters() { 258 return getCurrentAuthorisationUI().getOAuthParameters();258 return (OAuthParameters) getCurrentAuthorisationUI().getOAuthParameters(); 259 259 } 260 260 261 261 /** -
core/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java b/core/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java
a b 22 22 import javax.swing.JPanel; 23 23 24 24 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 25 import org.openstreetmap.josm.data.oauth.OAuthParameters; 25 26 import org.openstreetmap.josm.data.oauth.OAuthToken; 26 27 import org.openstreetmap.josm.gui.util.GuiHelper; 27 28 import org.openstreetmap.josm.gui.widgets.HtmlPanel; … … 78 79 79 80 protected void transitionToRetrieveAccessToken() { 80 81 OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient( 81 getAdvancedPropertiesPanel().getAdvancedParameters()82 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() 82 83 ); 83 84 String authoriseUrl = client.getAuthoriseUrl(requestToken); 84 85 OpenBrowser.displayUrl(authoriseUrl); … … 183 184 + "Please click on <strong>{0}</strong> to retrieve an OAuth Request Token from " 184 185 + "''{1}''.</html>", 185 186 tr("Retrieve Request Token"), 186 getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl()187 ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getRequestTokenUrl() 187 188 )); 188 189 pnl.add(h, gc); 189 190 … … 390 391 public void actionPerformed(ActionEvent evt) { 391 392 final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask( 392 393 SemiAutomaticAuthorizationUI.this, 393 getAdvancedPropertiesPanel().getAdvancedParameters()394 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters() 394 395 ); 395 396 executor.execute(task); 396 397 Runnable r = () -> { … … 418 419 public void actionPerformed(ActionEvent evt) { 419 420 final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask( 420 421 SemiAutomaticAuthorizationUI.this, 421 getAdvancedPropertiesPanel().getAdvancedParameters(),422 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), 422 423 requestToken 423 424 ); 424 425 executor.execute(task); … … 450 451 TestAccessTokenTask task = new TestAccessTokenTask( 451 452 SemiAutomaticAuthorizationUI.this, 452 453 getApiUrl(), 453 getAdvancedPropertiesPanel().getAdvancedParameters(),454 (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(), 454 455 getAccessToken() 455 456 ); 456 457 executor.execute(task); -
core/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java b/core/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java
a b 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.BorderLayout; 7 import java.awt.FlowLayout; 7 8 import java.awt.GridBagConstraints; 8 9 import java.awt.GridBagLayout; 9 10 import java.awt.Insets; … … 17 18 import javax.swing.JRadioButton; 18 19 19 20 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 21 import org.openstreetmap.josm.data.oauth.OAuthVersion; 20 22 import org.openstreetmap.josm.gui.help.HelpUtil; 21 23 import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel; 22 24 import org.openstreetmap.josm.io.OsmApi; 23 25 import org.openstreetmap.josm.io.auth.CredentialsManager; 24 26 import org.openstreetmap.josm.spi.preferences.Config; 27 import org.openstreetmap.josm.tools.GBC; 25 28 import org.openstreetmap.josm.tools.Logging; 26 29 27 30 /** … … 32 35 33 36 /** indicates whether we use basic authentication */ 34 37 private final JRadioButton rbBasicAuthentication = new JRadioButton(); 35 /** indicates whether we use OAuth as authentication scheme */38 /** indicates whether we use OAuth 1.0a as authentication scheme */ 36 39 private final JRadioButton rbOAuth = new JRadioButton(); 40 /** indicates whether we use OAuth 2.0 as authentication scheme */ 41 private final JRadioButton rbOAuth20 = new JRadioButton(); 37 42 /** the panel which contains the authentication parameters for the respective authentication scheme */ 38 43 private final JPanel pnlAuthenticationParameters = new JPanel(new BorderLayout()); 39 44 /** the panel for the basic authentication parameters */ 40 45 private BasicAuthenticationPreferencesPanel pnlBasicAuthPreferences; 41 /** the panel for the OAuth authentication parameters */46 /** the panel for the OAuth 1.0a authentication parameters */ 42 47 private OAuthAuthenticationPreferencesPanel pnlOAuthPreferences; 48 /** the panel for the OAuth 2.0 authentication parameters */ 49 private OAuthAuthenticationPreferencesPanel pnlOAuth20Preferences; 43 50 44 51 /** 45 52 * Constructs a new {@code AuthenticationPreferencesPanel}. … … 55 62 */ 56 63 protected final void build() { 57 64 setLayout(new GridBagLayout()); 58 GridBagConstraints gc = new GridBagConstraints();59 65 60 66 AuthenticationMethodChangeListener authChangeListener = new AuthenticationMethodChangeListener(); 61 67 68 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 62 69 // -- radio button for basic authentication 63 gc.anchor = GridBagConstraints.NORTHWEST; 64 gc.fill = GridBagConstraints.HORIZONTAL; 65 gc.gridx = 1; 66 gc.weightx = 1.0; 67 gc.insets = new Insets(0, 0, 0, 3); 68 add(rbBasicAuthentication, gc); 70 buttonPanel.add(rbBasicAuthentication); 69 71 rbBasicAuthentication.setText(tr("Use Basic Authentication")); 70 72 rbBasicAuthentication.setToolTipText(tr("Select to use HTTP basic authentication with your OSM username and password")); 71 73 rbBasicAuthentication.addItemListener(authChangeListener); 72 74 73 //-- radio button for OAuth 74 gc.gridx = 0; 75 gc.weightx = 0.0; 76 add(rbOAuth, gc); 77 rbOAuth.setText(tr("Use OAuth")); 78 rbOAuth.setToolTipText(tr("Select to use OAuth as authentication mechanism")); 75 //-- radio button for OAuth 1.0a 76 buttonPanel.add(rbOAuth); 77 rbOAuth.setText(tr("Use OAuth {0}", "1.0a")); 78 rbOAuth.setToolTipText(tr("Select to use OAuth {0} as authentication mechanism", "1.0a")); 79 79 rbOAuth.addItemListener(authChangeListener); 80 80 81 //-- radio button for OAuth 2.0 82 buttonPanel.add(rbOAuth20); 83 rbOAuth20.setText(tr("Use OAuth {0}", "2.0")); 84 rbOAuth20.setToolTipText(tr("Select to use OAuth {0} as authentication mechanism", "2.0")); 85 rbOAuth20.addItemListener(authChangeListener); 86 87 add(buttonPanel, GBC.eol()); 81 88 //-- radio button for OAuth 82 89 ButtonGroup bg = new ButtonGroup(); 83 90 bg.add(rbBasicAuthentication); 84 91 bg.add(rbOAuth); 92 bg.add(rbOAuth20); 85 93 86 94 //-- add the panel which will hold the authentication parameters 95 GridBagConstraints gc = new GridBagConstraints(); 96 gc.anchor = GridBagConstraints.NORTHWEST; 97 gc.insets = new Insets(0, 0, 0, 3); 87 98 gc.gridx = 0; 88 99 gc.gridy = 1; 89 100 gc.gridwidth = 2; … … 94 105 95 106 //-- the two panels for authentication parameters 96 107 pnlBasicAuthPreferences = new BasicAuthenticationPreferencesPanel(); 97 pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel(); 108 pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth10a); 109 pnlOAuth20Preferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth20); 98 110 99 111 rbBasicAuthentication.setSelected(true); 100 112 pnlAuthenticationParameters.add(pnlBasicAuthPreferences, BorderLayout.CENTER); … … 109 121 rbBasicAuthentication.setSelected(true); 110 122 } else if ("oauth".equals(authMethod)) { 111 123 rbOAuth.setSelected(true); 124 } else if ("oauth20".equals(authMethod)) { 125 rbOAuth20.setSelected(true); 112 126 } else { 113 127 Logging.warn(tr("Unsupported value in preference ''{0}'', got ''{1}''. Using authentication method ''Basic Authentication''.", 114 128 "osm-server.auth-method", authMethod)); … … 116 130 } 117 131 pnlBasicAuthPreferences.initFromPreferences(); 118 132 pnlOAuthPreferences.initFromPreferences(); 133 pnlOAuth20Preferences.initFromPreferences(); 119 134 } 120 135 121 136 /** … … 126 141 String authMethod; 127 142 if (rbBasicAuthentication.isSelected()) { 128 143 authMethod = "basic"; 129 } else {144 } else if (rbOAuth.isSelected()) { 130 145 authMethod = "oauth"; 146 } else if (rbOAuth20.isSelected()) { 147 authMethod = "oauth20"; 148 } else { 149 throw new IllegalStateException("One of OAuth 2.0, OAuth 1.0a, or Basic authentication must be checked"); 131 150 } 132 151 Config.getPref().put("osm-server.auth-method", authMethod); 133 152 if ("basic".equals(authMethod)) { … … 140 159 pnlBasicAuthPreferences.clearPassword(); 141 160 pnlBasicAuthPreferences.saveToPreferences(); 142 161 pnlOAuthPreferences.saveToPreferences(); 162 } else { // oauth20 163 // clear the password in the preferences 164 pnlBasicAuthPreferences.clearPassword(); 165 pnlBasicAuthPreferences.saveToPreferences(); 166 pnlOAuth20Preferences.saveToPreferences(); 143 167 } 144 168 } 145 169 … … 149 173 class AuthenticationMethodChangeListener implements ItemListener { 150 174 @Override 151 175 public void itemStateChanged(ItemEvent e) { 152 if (rbBasicAuthentication.isSelected()) {153 pnlAuthenticationParameters.removeAll();176 pnlAuthenticationParameters.removeAll(); 177 if (rbBasicAuthentication.isSelected()) { 154 178 pnlAuthenticationParameters.add(pnlBasicAuthPreferences, BorderLayout.CENTER); 155 179 pnlBasicAuthPreferences.revalidate(); 156 } else { 157 pnlAuthenticationParameters.removeAll(); 180 } else if (rbOAuth.isSelected()) { 158 181 pnlAuthenticationParameters.add(pnlOAuthPreferences, BorderLayout.CENTER); 159 182 pnlOAuthPreferences.revalidate(); 183 } else if (rbOAuth20.isSelected()) { 184 pnlAuthenticationParameters.add(pnlOAuth20Preferences, BorderLayout.CENTER); 185 pnlOAuth20Preferences.revalidate(); 160 186 } 161 187 repaint(); 162 188 } -
core/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java b/core/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java
a b 23 23 import javax.swing.JPanel; 24 24 25 25 import org.openstreetmap.josm.actions.ExpertToggleAction; 26 import org.openstreetmap.josm.data.oauth.IOAuthToken; 27 import org.openstreetmap.josm.data.oauth.OAuth20Authorization; 28 import org.openstreetmap.josm.data.oauth.OAuth20Token; 26 29 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 27 30 import org.openstreetmap.josm.data.oauth.OAuthParameters; 28 31 import org.openstreetmap.josm.data.oauth.OAuthToken; 32 import org.openstreetmap.josm.data.oauth.OAuthVersion; 33 import org.openstreetmap.josm.data.oauth.osm.OsmScopes; 29 34 import org.openstreetmap.josm.gui.MainApplication; 30 35 import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel; 31 36 import org.openstreetmap.josm.gui.oauth.AuthorizationProcedure; 32 37 import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard; 33 38 import org.openstreetmap.josm.gui.oauth.TestAccessTokenTask; 39 import org.openstreetmap.josm.gui.util.GuiHelper; 34 40 import org.openstreetmap.josm.gui.widgets.JMultilineLabel; 35 41 import org.openstreetmap.josm.gui.widgets.JosmTextField; 36 42 import org.openstreetmap.josm.io.OsmApi; 37 43 import org.openstreetmap.josm.io.auth.CredentialsManager; 44 import org.openstreetmap.josm.io.remotecontrol.RemoteControl; 38 45 import org.openstreetmap.josm.tools.GBC; 39 46 import org.openstreetmap.josm.tools.ImageProvider; 40 47 import org.openstreetmap.josm.tools.Logging; 41 48 import org.openstreetmap.josm.tools.UserCancelException; 42 49 43 50 /** 44 * The preferences panel for the OAuth preferences. This just a summary panel51 * The preferences panel for the OAuth 1.0a preferences. This just a summary panel 45 52 * showing the current Access Token Key and Access Token Secret, if the 46 53 * user already has an Access Token. 47 * 54 * <br> 48 55 * For initial authorisation see {@link OAuthAuthorizationWizard}. 49 56 * @since 2745 50 57 */ … … 53 60 private final JCheckBox cbShowAdvancedParameters = new JCheckBox(tr("Display Advanced OAuth Parameters")); 54 61 private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save to preferences")); 55 62 private final JPanel pnlAuthorisationMessage = new JPanel(new BorderLayout()); 56 private final NotYetAuthorisedPanel pnlNotYetAuthorised = new NotYetAuthorisedPanel(); 57 private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(); 58 private final AlreadyAuthorisedPanel pnlAlreadyAuthorised = new AlreadyAuthorisedPanel(); 63 private final NotYetAuthorisedPanel pnlNotYetAuthorised; 64 private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties; 65 private final AlreadyAuthorisedPanel pnlAlreadyAuthorised; 66 private final OAuthVersion oAuthVersion; 59 67 private String apiUrl; 60 68 61 69 /** 62 * Create the panel 70 * Create the panel. Uses {@link OAuthVersion#OAuth10a}. 63 71 */ 64 72 public OAuthAuthenticationPreferencesPanel() { 73 this(OAuthVersion.OAuth10a); 74 } 75 76 /** 77 * Create the panel. 78 * @param oAuthVersion The OAuth version to use 79 */ 80 public OAuthAuthenticationPreferencesPanel(OAuthVersion oAuthVersion) { 81 this.oAuthVersion = oAuthVersion; 82 // These must come after we set the oauth version 83 this.pnlNotYetAuthorised = new NotYetAuthorisedPanel(); 84 this.pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(this.oAuthVersion); 85 this.pnlAlreadyAuthorised = new AlreadyAuthorisedPanel(); 65 86 build(); 66 refreshView();67 87 } 68 88 69 89 /** … … 117 137 118 138 protected void refreshView() { 119 139 pnlAuthorisationMessage.removeAll(); 120 if (OAuthAccessTokenHolder.getInstance().containsAccessToken()) { 140 if ((this.oAuthVersion == OAuthVersion.OAuth10a && 141 OAuthAccessTokenHolder.getInstance().containsAccessToken()) 142 || OAuthAccessTokenHolder.getInstance().getAccessToken(this.apiUrl, this.oAuthVersion) != null) { 121 143 pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER); 122 144 pnlAlreadyAuthorised.refreshView(); 123 145 pnlAlreadyAuthorised.revalidate(); … … 180 202 lbl.setFont(lbl.getFont().deriveFont(Font.PLAIN)); 181 203 182 204 // Action for authorising now 183 add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.FULLY_AUTOMATIC)), GBC.eol()); 205 if (oAuthVersion == OAuthVersion.OAuth10a) { 206 add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.FULLY_AUTOMATIC)), GBC.eol()); 207 } 184 208 add(new JButton(new AuthoriseNowAction(AuthorizationProcedure.SEMI_AUTOMATIC)), GBC.eol()); 185 JButton authManually = new JButton(new AuthoriseNowAction(AuthorizationProcedure.MANUALLY)); 186 add(authManually, GBC.eol()); 187 ExpertToggleAction.addVisibilitySwitcher(authManually); 209 if (oAuthVersion == OAuthVersion.OAuth10a) { 210 JButton authManually = new JButton(new AuthoriseNowAction(AuthorizationProcedure.MANUALLY)); 211 add(authManually, GBC.eol()); 212 ExpertToggleAction.addVisibilitySwitcher(authManually); 213 } 188 214 189 215 // filler - grab remaining space 190 216 add(new JPanel(), GBC.std().fill(GBC.BOTH)); … … 253 279 254 280 // -- action buttons 255 281 JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT)); 256 btns.add(new JButton(new RenewAuthorisationAction(AuthorizationProcedure.FULLY_AUTOMATIC))); 257 btns.add(new JButton(new TestAuthorisationAction())); 282 if (oAuthVersion == OAuthVersion.OAuth10a) { 283 // these want the OAuth 1.0 token information 284 btns.add(new JButton(new RenewAuthorisationAction(AuthorizationProcedure.FULLY_AUTOMATIC))); 285 btns.add(new JButton(new TestAuthorisationAction())); 286 } 287 btns.add(new JButton(new RemoveAuthorisationAction())); 258 288 gc.gridy = 4; 259 289 gc.gridx = 0; 260 290 gc.gridwidth = 2; … … 277 307 } 278 308 279 309 protected final void refreshView() { 280 String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey(); 281 tfAccessTokenKey.setText(v == null ? "" : v); 282 v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret(); 283 tfAccessTokenSecret.setText(v == null ? "" : v); 310 switch (oAuthVersion) { 311 case OAuth10a: 312 String v = OAuthAccessTokenHolder.getInstance().getAccessTokenKey(); 313 tfAccessTokenKey.setText(v == null ? "" : v); 314 v = OAuthAccessTokenHolder.getInstance().getAccessTokenSecret(); 315 tfAccessTokenSecret.setText(v == null ? "" : v); 316 tfAccessTokenSecret.setVisible(true); 317 break; 318 case OAuth20: 319 case OAuth21: 320 String token = ""; 321 if (apiUrl != null) { 322 OAuth20Token bearerToken = (OAuth20Token) OAuthAccessTokenHolder.getInstance().getAccessToken(apiUrl, oAuthVersion); 323 token = bearerToken == null ? "" : bearerToken.getBearerToken(); 324 } 325 tfAccessTokenKey.setText(token == null ? "" : token); 326 tfAccessTokenSecret.setVisible(false); 327 } 284 328 cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences()); 285 329 } 286 330 } … … 295 339 this.procedure = procedure; 296 340 putValue(NAME, tr("{0} ({1})", tr("Authorize now"), procedure.getText())); 297 341 putValue(SHORT_DESCRIPTION, procedure.getDescription()); 298 if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC) { 342 if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC 343 || OAuthAuthenticationPreferencesPanel.this.oAuthVersion != OAuthVersion.OAuth10a) { 299 344 new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this); 300 345 } 301 346 } 302 347 303 348 @Override 304 349 public void actionPerformed(ActionEvent arg0) { 305 OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard( 306 OAuthAuthenticationPreferencesPanel.this, 307 procedure, 308 apiUrl, 309 MainApplication.worker); 310 try { 311 wizard.showDialog(); 312 } catch (UserCancelException ignore) { 313 Logging.trace(ignore); 314 return; 315 } 316 pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters()); 350 if (OAuthAuthenticationPreferencesPanel.this.oAuthVersion == OAuthVersion.OAuth10a) { 351 OAuthAuthorizationWizard wizard = new OAuthAuthorizationWizard( 352 OAuthAuthenticationPreferencesPanel.this, 353 procedure, 354 apiUrl, 355 MainApplication.worker); 356 try { 357 wizard.showDialog(); 358 } catch (UserCancelException ignore) { 359 Logging.trace(ignore); 360 return; 361 } 362 pnlAdvancedProperties.setAdvancedParameters(wizard.getOAuthParameters()); 363 refreshView(); 364 } else { 365 final boolean remoteControlIsRunning = Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get()); 366 if (!remoteControlIsRunning) { 367 RemoteControl.start(); 368 } 369 new OAuth20Authorization().authorize(OAuthParameters.createDefault(OsmApi.getOsmApi().getServerUrl(), oAuthVersion), token -> { 370 if (!remoteControlIsRunning) { 371 RemoteControl.stop(); 372 } 373 // Clean up old token/password 374 OAuthAccessTokenHolder.getInstance().setAccessToken(null); 375 OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), token); 376 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); 377 GuiHelper.runInEDT(OAuthAuthenticationPreferencesPanel.this::refreshView); 378 }, OsmScopes.read_gpx, OsmScopes.write_gpx, 379 OsmScopes.read_prefs, OsmScopes.write_prefs, 380 OsmScopes.write_api, OsmScopes.write_notes); 381 } 382 } 383 } 384 385 /** 386 * Remove the OAuth authorization token 387 */ 388 private class RemoveAuthorisationAction extends AbstractAction { 389 RemoveAuthorisationAction() { 390 putValue(NAME, tr("Remove token")); 391 putValue(SHORT_DESCRIPTION, tr("Remove token from JOSM. This does not revoke the token.")); 392 new ImageProvider("cancel").getResource().attachImageIcon(this); 393 } 394 395 @Override 396 public void actionPerformed(ActionEvent e) { 397 if (oAuthVersion == OAuthVersion.OAuth10a) { 398 OAuthAccessTokenHolder.getInstance().setAccessToken(null); 399 } else { 400 OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, (IOAuthToken) null); 401 } 402 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); 317 403 refreshView(); 318 404 } 319 405 } -
core/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java b/core/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java
a b 5 5 import java.net.Authenticator.RequestorType; 6 6 import java.net.PasswordAuthentication; 7 7 8 import org.openstreetmap.josm.data.oauth.IOAuthToken; 8 9 import org.openstreetmap.josm.data.oauth.OAuthToken; 9 10 10 11 /** … … 64 65 */ 65 66 OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException; 66 67 68 /** 69 * Lookup the current OAuth Access Token to access the specifiedserver. Replies null, if no 70 * Access Token is currently managed by this CredentialAgent. 71 * 72 * @param host The host to get OAuth credentials for 73 * @return the current OAuth Access Token to access the specified server. 74 * @throws CredentialsAgentException if something goes wrong 75 */ 76 IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException; 77 67 78 /** 68 79 * Stores the OAuth Access Token <code>accessToken</code>. 69 80 * … … 72 83 */ 73 84 void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException; 74 85 86 /** 87 * Stores the OAuth Access Token <code>accessToken</code>. 88 * 89 * @param host The host the access token is for 90 * @param accessToken the access Token. null, to remove the Access Token. 91 * @throws CredentialsAgentException if something goes wrong 92 */ 93 void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException; 94 75 95 /** 76 96 * Purges the internal credentials cache for the given requestor type. 77 97 * @param requestorType the type of service. -
core/src/org/openstreetmap/josm/io/auth/CredentialsManager.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/auth/CredentialsManager.java b/core/src/org/openstreetmap/josm/io/auth/CredentialsManager.java
a b 7 7 import java.util.Objects; 8 8 9 9 import org.openstreetmap.josm.data.UserIdentityManager; 10 import org.openstreetmap.josm.data.oauth.IOAuthToken; 10 11 import org.openstreetmap.josm.data.oauth.OAuthToken; 11 12 import org.openstreetmap.josm.io.OsmApi; 12 13 import org.openstreetmap.josm.tools.CheckParameterUtil; … … 160 161 return delegate.lookupOAuthAccessToken(); 161 162 } 162 163 164 @Override 165 public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException { 166 return delegate.lookupOAuthAccessToken(host); 167 } 168 163 169 @Override 164 170 public void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException { 165 171 delegate.storeOAuthAccessToken(accessToken); 166 172 } 167 173 174 @Override 175 public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException { 176 delegate.storeOAuthAccessToken(host, accessToken); 177 } 178 168 179 @Override 169 180 public Component getPreferencesDecorationPanel() { 170 181 return delegate.getPreferencesDecorationPanel(); -
core/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java b/core/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java
a b 8 8 import java.net.PasswordAuthentication; 9 9 import java.util.Objects; 10 10 11 import javax.json.JsonException; 11 12 import javax.swing.text.html.HTMLEditorKit; 12 13 14 import org.openstreetmap.josm.data.oauth.IOAuthToken; 15 import org.openstreetmap.josm.data.oauth.OAuth20Exception; 16 import org.openstreetmap.josm.data.oauth.OAuth20Parameters; 17 import org.openstreetmap.josm.data.oauth.OAuth20Token; 13 18 import org.openstreetmap.josm.data.oauth.OAuthToken; 19 import org.openstreetmap.josm.data.oauth.OAuthVersion; 14 20 import org.openstreetmap.josm.gui.widgets.HtmlPanel; 15 21 import org.openstreetmap.josm.io.DefaultProxySelector; 16 22 import org.openstreetmap.josm.io.OsmApi; 17 23 import org.openstreetmap.josm.spi.preferences.Config; 24 import org.openstreetmap.josm.tools.Utils; 18 25 19 26 /** 20 27 * This is the default credentials agent in JOSM. It keeps username and password for both … … 109 116 return new OAuthToken(accessTokenKey, accessTokenSecret); 110 117 } 111 118 119 @Override 120 public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException { 121 for (OAuthVersion oauthType : OAuthVersion.values()) { 122 String token = Config.getPref().get("oauth.access-token.object." + oauthType + "." + host, null); 123 String parameters = Config.getPref().get("oauth.access-token.parameters." + oauthType + "." + host, null); 124 if (!Utils.isBlank(token) && !Utils.isBlank(parameters) && OAuthVersion.OAuth20 == oauthType) { 125 try { 126 OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(parameters); 127 return new OAuth20Token(oAuth20Parameters, token); 128 } catch (OAuth20Exception | JsonException e) { 129 throw new CredentialsAgentException(e); 130 } 131 } 132 } 133 134 throw new CredentialsAgentException("No OAuth token found for " + host); 135 } 136 112 137 /** 113 138 * Stores the OAuth Access Token <code>accessToken</code>. 114 139 * … … 126 151 } 127 152 } 128 153 154 @Override 155 public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException { 156 Objects.requireNonNull(host, "host"); 157 if (accessToken == null) { 158 // Assume we want to remove all access tokens 159 for (OAuthVersion oauthType : OAuthVersion.values()) { 160 Config.getPref().put("oauth.access-token.object." + oauthType + "." + host, null); 161 Config.getPref().put("oauth.access-token.parameters." + oauthType + "." + host, null); 162 } 163 } else { 164 Config.getPref().put("oauth.access-token.object." + accessToken.getOAuthType() + "." + host, 165 accessToken.toPreferencesString()); 166 Config.getPref().put("oauth.access-token.parameters." + accessToken.getOAuthType() + "." + host, 167 accessToken.getParameters().toPreferencesString()); 168 } 169 } 170 129 171 @Override 130 172 public Component getPreferencesDecorationPanel() { 131 173 HtmlPanel pnlMessage = new HtmlPanel(); -
new file core/src/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandler.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandler.java b/core/src/org/openstreetmap/josm/io/remotecontrol/handler/AuthorizationHandler.java new file mode 100644
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.io.remotecontrol.handler; 3 4 import java.util.HashMap; 5 import java.util.Map; 6 import java.util.Optional; 7 8 import org.openstreetmap.josm.data.preferences.BooleanProperty; 9 import org.openstreetmap.josm.io.remotecontrol.PermissionPrefWithDefault; 10 11 /** 12 * Handle authorization requests (mostly OAuth) 13 */ 14 public class AuthorizationHandler extends RequestHandler { 15 16 /** 17 * The actual authorization consumer/handler 18 */ 19 public interface AuthorizationConsumer { 20 /** 21 * Validate the request 22 * @param args The GET request arguments 23 * @param request The request URL without "GET". 24 * @param sender who sent the request? the host from referer header or IP of request sender 25 * @throws RequestHandlerBadRequestException if the request is invalid 26 * @see RequestHandler#validateRequest() 27 */ 28 void validateRequest(String sender, String request, Map<String, String> args) 29 throws RequestHandlerBadRequestException; 30 31 /** 32 * Handle the request. Any time-consuming operation must be performed asynchronously to avoid delaying the HTTP response. 33 * @param args The GET request arguments 34 * @param request The request URL without "GET". 35 * @param sender who sent the request? the host from referer header or IP of request sender 36 * @return The response to show the user. May be {@code null}. 37 * @throws RequestHandlerErrorException if an error occurs while processing the request 38 * @throws RequestHandlerBadRequestException if the request is invalid 39 * @see RequestHandler#handleRequest() 40 */ 41 ResponseRecord handleRequest(String sender, String request, Map<String, String> args) 42 throws RequestHandlerErrorException, RequestHandlerBadRequestException; 43 } 44 45 /** 46 * A basic record for changing responses 47 */ 48 public static final class ResponseRecord { 49 private final String content; 50 private final String type; 51 52 /** 53 * Create a new record 54 * @param content The content to show the user 55 * @param type The content mime type 56 */ 57 public ResponseRecord(String content, String type) { 58 this.content = content; 59 this.type = type; 60 } 61 62 /** 63 * Get the content for the response 64 * @return The content as a string 65 */ 66 public String content() { 67 return this.content; 68 } 69 70 /** 71 * Get the type for the response 72 * @return The response mime type 73 */ 74 public String type() { 75 return this.type; 76 } 77 } 78 79 /** 80 * The remote control command 81 */ 82 public static final String command = "oauth_authorization"; 83 84 private static final BooleanProperty PROPERTY = new BooleanProperty("remotecontrol.permission.authorization", false); 85 private static final Map<String, AuthorizationConsumer> AUTHORIZATION_CONSUMERS = new HashMap<>(); 86 87 private AuthorizationConsumer consumer; 88 /** 89 * Add an authorization consumer. 90 * @param state The unique state for each request (for OAuth, this would be the {@code state} parameter) 91 * @param consumer The consumer of the response 92 */ 93 public static synchronized void addAuthorizationConsumer(String state, AuthorizationConsumer consumer) { 94 if (AUTHORIZATION_CONSUMERS.containsKey(state)) { 95 throw new IllegalArgumentException("Cannot add multiple consumers for one authorization state"); 96 } 97 AUTHORIZATION_CONSUMERS.put(state, consumer); 98 } 99 100 @Override 101 protected void validateRequest() throws RequestHandlerBadRequestException { 102 boolean clearAll = false; 103 for (Map.Entry<String, AuthorizationConsumer> entry : AUTHORIZATION_CONSUMERS.entrySet()) { 104 if (this.request.contains(entry.getKey())) { 105 if (this.consumer == null) { 106 this.consumer = entry.getValue(); 107 } else { 108 // Remove all authorization consumers. Someone might be playing games. 109 clearAll = true; 110 } 111 } 112 } 113 if (clearAll) { 114 AUTHORIZATION_CONSUMERS.clear(); 115 throw new RequestHandlerBadRequestException("Multiple states for authorization"); 116 } 117 118 if (this.consumer == null) { 119 throw new RequestHandlerBadRequestException("Unknown state for authorization"); 120 } 121 this.consumer.validateRequest(this.sender, this.request, this.args); 122 } 123 124 @Override 125 protected void handleRequest() throws RequestHandlerErrorException, RequestHandlerBadRequestException { 126 ResponseRecord response = this.consumer.handleRequest(this.sender, this.request, this.args); 127 if (response != null) { 128 this.content = Optional.ofNullable(response.content()).orElse(this.content); 129 this.contentType = Optional.ofNullable(response.type()).orElse(this.contentType); 130 } 131 } 132 133 @Override 134 public String getPermissionMessage() { 135 return "Allow OAuth remote control to set credentials"; 136 } 137 138 @Override 139 public PermissionPrefWithDefault getPermissionPref() { 140 return null; 141 } 142 143 public BooleanProperty getPermissionPreference() { 144 return PROPERTY; 145 } 146 147 @Override 148 public String[] getMandatoryParams() { 149 return new String[0]; 150 } 151 } -
core/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java b/core/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java
a b 30 30 import org.openstreetmap.josm.gui.help.HelpUtil; 31 31 import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler; 32 32 import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler; 33 import org.openstreetmap.josm.io.remotecontrol.handler.AuthorizationHandler; 33 34 import org.openstreetmap.josm.io.remotecontrol.handler.FeaturesHandler; 34 35 import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler; 35 36 import org.openstreetmap.josm.io.remotecontrol.handler.ImportHandler; … … 171 172 addRequestHandlerClass(VersionHandler.command, VersionHandler.class, true); 172 173 addRequestHandlerClass(FeaturesHandler.command, FeaturesHandler.class, true); 173 174 addRequestHandlerClass(OpenApiHandler.command, OpenApiHandler.class, true); 175 addRequestHandlerClass(AuthorizationHandler.command, AuthorizationHandler.class, true); 174 176 } 175 177 } 176 178 -
core/src/org/openstreetmap/josm/io/MessageNotifier.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/MessageNotifier.java b/core/src/org/openstreetmap/josm/io/MessageNotifier.java
a b 144 144 try { 145 145 if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) { 146 146 if (OsmApi.isUsingOAuth()) { 147 return credManager.lookupOAuthAccessToken() != null; 147 return credManager.lookupOAuthAccessToken() != null 148 || credManager.lookupOAuthAccessToken(OsmApi.getOsmApi().getHost()) != null; 148 149 } else { 149 150 String username = Config.getPref().get("osm-server.username", null); 150 151 String password = Config.getPref().get("osm-server.password", null); -
core/src/org/openstreetmap/josm/io/OsmApi.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/OsmApi.java b/core/src/org/openstreetmap/josm/io/OsmApi.java
a b 29 29 30 30 import org.openstreetmap.josm.data.coor.LatLon; 31 31 import org.openstreetmap.josm.data.notes.Note; 32 import org.openstreetmap.josm.data.oauth.OAuthVersion; 32 33 import org.openstreetmap.josm.data.osm.Changeset; 33 34 import org.openstreetmap.josm.data.osm.IPrimitive; 34 35 import org.openstreetmap.josm.data.osm.OsmPrimitive; … … 645 646 * @since 6349 646 647 */ 647 648 public static boolean isUsingOAuth() { 648 return "oauth".equals(getAuthMethod()); 649 return isUsingOAuth(OAuthVersion.OAuth10a) 650 || isUsingOAuth(OAuthVersion.OAuth20) 651 || isUsingOAuth(OAuthVersion.OAuth21); 652 } 653 654 /** 655 * Determines if JOSM is configured to access OSM API via OAuth 656 * @param version The OAuth version 657 * @return {@code true} if JOSM is configured to access OSM API via OAuth, {@code false} otherwise 658 * @since xxx 659 */ 660 public static boolean isUsingOAuth(OAuthVersion version) { 661 if (version == OAuthVersion.OAuth10a) { 662 return "oauth".equalsIgnoreCase(getAuthMethod()); 663 } else if (version == OAuthVersion.OAuth20 || version == OAuthVersion.OAuth21) { 664 return "oauth20".equalsIgnoreCase(getAuthMethod()); 665 } 666 return false; 649 667 } 650 668 651 669 /** … … 653 671 * @return the authentication method 654 672 */ 655 673 public static String getAuthMethod() { 656 return Config.getPref().get("osm-server.auth-method", "oauth ");674 return Config.getPref().get("osm-server.auth-method", "oauth20"); // fixme switch to oauth20 by default 657 675 } 658 676 659 677 protected final String sendPostRequest(String urlSuffix, String requestBody, ProgressMonitor monitor) throws OsmTransferException { -
core/src/org/openstreetmap/josm/io/OsmConnection.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/OsmConnection.java b/core/src/org/openstreetmap/josm/io/OsmConnection.java
a b 10 10 import java.nio.charset.StandardCharsets; 11 11 import java.util.Base64; 12 12 import java.util.Objects; 13 import java.util.concurrent.TimeUnit; 14 import java.util.concurrent.atomic.AtomicBoolean; 15 import java.util.function.Consumer; 13 16 17 import javax.swing.JOptionPane; 18 19 import org.openstreetmap.josm.data.oauth.IOAuthParameters; 20 import org.openstreetmap.josm.data.oauth.IOAuthToken; 21 import org.openstreetmap.josm.data.oauth.OAuth20Authorization; 14 22 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder; 15 23 import org.openstreetmap.josm.data.oauth.OAuthParameters; 24 import org.openstreetmap.josm.data.oauth.OAuthVersion; 25 import org.openstreetmap.josm.data.oauth.osm.OsmScopes; 26 import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil; 27 import org.openstreetmap.josm.gui.MainApplication; 28 import org.openstreetmap.josm.gui.util.GuiHelper; 16 29 import org.openstreetmap.josm.io.auth.CredentialsAgentException; 17 30 import org.openstreetmap.josm.io.auth.CredentialsAgentResponse; 18 31 import org.openstreetmap.josm.io.auth.CredentialsManager; 32 import org.openstreetmap.josm.io.remotecontrol.RemoteControl; 19 33 import org.openstreetmap.josm.tools.HttpClient; 20 34 import org.openstreetmap.josm.tools.JosmRuntimeException; 21 35 import org.openstreetmap.josm.tools.Logging; … … 36 50 protected boolean cancel; 37 51 protected HttpClient activeConnection; 38 52 protected OAuthParameters oauthParameters; 53 protected IOAuthParameters oAuth20Parameters; 39 54 40 55 /** 41 56 * Retrieves OAuth access token. … … 171 186 fetcher.obtainAccessToken(apiUrl); 172 187 OAuthAccessTokenHolder.getInstance().setSaveToPreferences(true); 173 188 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); 174 } catch (MalformedURLException | In terruptedException | InvocationTargetException e) {189 } catch (MalformedURLException | InvocationTargetException e) { 175 190 throw new MissingOAuthAccessTokenException(e); 191 } catch (InterruptedException e) { 192 Thread.currentThread().interrupt(); 193 throw new MissingOAuthAccessTokenException(e); 194 } 195 } 196 197 /** 198 * Obtains an OAuth access token for the connection. 199 * Afterwards, the token is accessible via {@link OAuthAccessTokenHolder} / {@link CredentialsManager}. 200 * @throws MissingOAuthAccessTokenException if the process cannot be completed successfully 201 */ 202 private void obtainOAuth20Token() throws MissingOAuthAccessTokenException { 203 if (!Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() -> 204 ConditionalOptionPaneUtil.showConfirmationDialog("oauth.oauth20.obtain.automatically", 205 MainApplication.getMainFrame(), 206 tr("Obtain OAuth 2.0 token for authentication?"), 207 tr("Obtain authentication to OSM servers"), 208 JOptionPane.YES_NO_CANCEL_OPTION, 209 JOptionPane.QUESTION_MESSAGE, JOptionPane.YES_OPTION)))) { 210 return; // User doesn't want to perform auth 211 } 212 final boolean remoteControlIsRunning = Boolean.TRUE.equals(RemoteControl.PROP_REMOTECONTROL_ENABLED.get()); 213 if (!remoteControlIsRunning) { 214 RemoteControl.start(); 215 } 216 AtomicBoolean done = new AtomicBoolean(); 217 Consumer<IOAuthToken> consumer = authToken -> { 218 if (!remoteControlIsRunning) { 219 RemoteControl.stop(); 220 } 221 // Clean up old token/password 222 OAuthAccessTokenHolder.getInstance().setAccessToken(null); 223 OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), authToken); 224 OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance()); 225 synchronized (done) { 226 done.set(true); 227 done.notifyAll(); 228 } 229 }; 230 new OAuth20Authorization().authorize(oAuth20Parameters, 231 consumer, OsmScopes.read_gpx, OsmScopes.write_gpx, 232 OsmScopes.read_prefs, OsmScopes.write_prefs, 233 OsmScopes.write_api, OsmScopes.write_notes); 234 synchronized (done) { 235 // Only wait at most 5 minutes 236 int counter = 0; 237 while (!done.get() && counter < 5) { 238 try { 239 done.wait(TimeUnit.MINUTES.toMillis(1)); 240 } catch (InterruptedException e) { 241 Thread.currentThread().interrupt(); 242 Logging.trace(e); 243 consumer.accept(null); 244 throw new MissingOAuthAccessTokenException(e); 245 } 246 counter++; 247 } 176 248 } 177 249 } 250 251 /** 252 * Signs the connection with an OAuth authentication header 253 * 254 * @param connection the connection 255 * 256 * @throws MissingOAuthAccessTokenException if there is currently no OAuth Access Token configured 257 * @throws OsmTransferException if signing fails 258 */ 259 protected void addOAuth20AuthorizationHeader(HttpClient connection) throws OsmTransferException { 260 if (this.oAuth20Parameters == null) { 261 this.oAuth20Parameters = OAuthParameters.createFromApiUrl(OsmApi.getOsmApi().getServerUrl(), OAuthVersion.OAuth20); 262 } 263 OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance(); 264 IOAuthToken token = holder.getAccessToken(connection.getURL().toExternalForm(), OAuthVersion.OAuth20); 265 if (token == null) { 266 obtainOAuth20Token(); 267 token = holder.getAccessToken(connection.getURL().toExternalForm(), OAuthVersion.OAuth20); 268 } 269 if (token == null) { // check if wizard completed 270 throw new MissingOAuthAccessTokenException(); 271 } 272 try { 273 token.sign(connection); 274 } catch (org.openstreetmap.josm.data.oauth.OAuthException e) { 275 throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e); 276 } 277 } 178 278 179 279 protected void addAuth(HttpClient connection) throws OsmTransferException { 180 280 final String authMethod = OsmApi.getAuthMethod(); 181 if ("basic".equals(authMethod)) { 182 addBasicAuthorizationHeader(connection); 183 } else if ("oauth".equals(authMethod)) { 184 addOAuthAuthorizationHeader(connection); 185 } else { 186 String msg = tr("Unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod); 187 Logging.warn(msg); 188 throw new OsmTransferException(msg); 281 switch (authMethod) { 282 case "basic": 283 addBasicAuthorizationHeader(connection); 284 return; 285 case "oauth": 286 addOAuthAuthorizationHeader(connection); 287 return; 288 case "oauth20": 289 addOAuth20AuthorizationHeader(connection); 290 return; 291 default: 292 String msg = tr("Unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod); 293 Logging.warn(msg); 294 throw new OsmTransferException(msg); 189 295 } 190 296 } 191 297 -
core/src/org/openstreetmap/josm/io/OsmServerReader.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/core/src/org/openstreetmap/josm/io/OsmServerReader.java b/core/src/org/openstreetmap/josm/io/OsmServerReader.java
a b 49 49 protected OsmServerReader() { 50 50 try { 51 51 doAuthenticate = OsmApi.isUsingOAuth() 52 && CredentialsManager.getInstance().lookupOAuthAccessToken() != null 52 && (CredentialsManager.getInstance().lookupOAuthAccessToken() != null 53 || CredentialsManager.getInstance().lookupOAuthAccessToken(this.api.getHost()) != null) 53 54 && OsmApi.USE_OAUTH_FOR_ALL_REQUESTS.get(); 54 55 } catch (CredentialsAgentException e) { 55 56 Logging.warn(e); -
plugins/native-password-manager/src/org/openstreetmap/josm/plugins/npm/NPMCredentialsAgent.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 diff --git a/plugins/native-password-manager/src/org/openstreetmap/josm/plugins/npm/NPMCredentialsAgent.java b/plugins/native-password-manager/src/org/openstreetmap/josm/plugins/npm/NPMCredentialsAgent.java
a b 6 6 import java.awt.Component; 7 7 import java.net.Authenticator.RequestorType; 8 8 import java.net.PasswordAuthentication; 9 import java.net.URI; 9 10 import java.nio.charset.StandardCharsets; 10 11 import java.util.ArrayList; 11 import java.util. HashMap;12 import java.util.EnumMap; 12 13 import java.util.List; 13 14 import java.util.Map; 14 15 import java.util.zip.CRC32; … … 17 18 18 19 import org.netbeans.spi.keyring.KeyringProvider; 19 20 import org.openstreetmap.josm.data.Preferences; 21 import org.openstreetmap.josm.data.oauth.IOAuthToken; 22 import org.openstreetmap.josm.data.oauth.OAuth20Exception; 23 import org.openstreetmap.josm.data.oauth.OAuth20Parameters; 24 import org.openstreetmap.josm.data.oauth.OAuth20Token; 20 25 import org.openstreetmap.josm.data.oauth.OAuthToken; 26 import org.openstreetmap.josm.data.oauth.OAuthVersion; 21 27 import org.openstreetmap.josm.gui.widgets.HtmlPanel; 22 28 import org.openstreetmap.josm.io.DefaultProxySelector; 23 29 import org.openstreetmap.josm.io.OsmApi; … … 25 31 import org.openstreetmap.josm.io.auth.CredentialsAgentException; 26 32 import org.openstreetmap.josm.spi.preferences.Config; 27 33 34 /** 35 * The native password manager credentials agent 36 */ 28 37 public class NPMCredentialsAgent extends AbstractCredentialsAgent { 29 38 30 39 private KeyringProvider provider; 31 private NPMType type;40 private final NPMType type; 32 41 33 42 /** 34 * Cache the results since there might be pop 43 * Cache the results since there might be pop-ups and password prompts from 35 44 * the native manager. This can get annoying, if it shows too often. 36 * 45 * <br> 37 46 * Yes, there is another cache in AbstractCredentialsAgent. It is used 38 47 * to avoid prompting the user for login multiple times in one session, 39 48 * when they decide not to save the credentials. 40 49 * In contrast, this cache avoids read request the backend in general. 41 50 */ 42 private Map<RequestorType, PasswordAuthentication> credentialsCache = new HashMap<>();51 private final Map<RequestorType, PasswordAuthentication> credentialsCache = new EnumMap<>(RequestorType.class); 43 52 private OAuthToken oauthCache; 44 53 54 /** 55 * Create a new {@link NPMCredentialsAgent} 56 * @param type The backend storage type 57 */ 45 58 public NPMCredentialsAgent(NPMType type) { 46 59 this.type = type; 47 60 } … … 95 108 } 96 109 97 110 @Override 98 public PasswordAuthentication lookup(RequestorType rt, String host) throws CredentialsAgentException{111 public PasswordAuthentication lookup(RequestorType rt, String host) { 99 112 PasswordAuthentication cache = credentialsCache.get(rt); 100 113 if (cache != null) 101 114 return cache; … … 125 138 } 126 139 127 140 @Override 128 public void store(RequestorType rt, String host, PasswordAuthentication credentials) throws CredentialsAgentException{141 public void store(RequestorType rt, String host, PasswordAuthentication credentials) { 129 142 char[] username, password; 130 143 if (credentials == null) { 131 144 username = null; … … 169 182 } else { 170 183 getProvider().save(prefix+".password", password, passwordDescription); 171 184 } 172 credentialsCache.put(rt, new PasswordAuthentication(stringNotNull(username), password ));185 credentialsCache.put(rt, new PasswordAuthentication(stringNotNull(username), password != null ? password : new char[0])); 173 186 } 174 187 } 175 188 176 189 @Override 177 public OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException{190 public OAuthToken lookupOAuthAccessToken() { 178 191 if (oauthCache != null) 179 192 return oauthCache; 180 193 String prolog = getOAuthDescriptor(); … … 184 197 } 185 198 186 199 @Override 187 public void storeOAuthAccessToken(OAuthToken oat) throws CredentialsAgentException { 200 public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException { 201 String prolog = getOAuthDescriptor(); 202 OAuthVersion[] versions = OAuthVersion.values(); 203 // Prefer newer OAuth protocols 204 for (int i = versions.length - 1; i >= 0; i--) { 205 OAuthVersion version = versions[i]; 206 char[] tokenObject = getProvider().read(prolog + ".object." + version + "." + host); 207 char[] parametersObject = getProvider().read(prolog + ".parameters." + version + "." + host); 208 if (version == OAuthVersion.OAuth20 // There is currently only an OAuth 2.0 path 209 && tokenObject != null && tokenObject.length > 0 210 && parametersObject != null && parametersObject.length > 0) { 211 OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(stringNotNull(parametersObject)); 212 try { 213 return new OAuth20Token(oAuth20Parameters, stringNotNull(tokenObject)); 214 } catch (OAuth20Exception e) { 215 throw new CredentialsAgentException(e); 216 } 217 } 218 } 219 return null; 220 } 221 222 @Override 223 public void storeOAuthAccessToken(OAuthToken oat) { 188 224 String key, secret; 189 225 if (oat == null) { 190 226 key = null; 191 227 secret = null; 192 } 193 else { 228 } else { 194 229 key = oat.getKey(); 195 230 secret = oat.getSecret(); 196 231 } … … 206 241 } 207 242 } 208 243 244 @Override 245 public void storeOAuthAccessToken(String host, IOAuthToken accessToken) { 246 String prolog = getOAuthDescriptor(); 247 if (accessToken == null) { 248 for (OAuthVersion version : OAuthVersion.values()) { 249 getProvider().delete(prolog + ".object." + version + "." + host); 250 getProvider().delete(prolog + ".parameters." + version + "." + host); 251 } 252 } else { 253 OAuthVersion oauthType = accessToken.getOAuthType(); 254 getProvider().save(prolog + ".object." + oauthType + "." + host, 255 accessToken.toPreferencesString().toCharArray(), 256 tr("JOSM/OAuth/{0}/Token", URI.create(host).getHost())); 257 getProvider().save(prolog + ".parameters." + oauthType + "." + host, 258 accessToken.getParameters().toPreferencesString().toCharArray(), 259 tr("JOSM/OAuth/{0}/Parameters", URI.create(host).getHost())); 260 } 261 } 262 209 263 private static String stringNotNull(char[] charData) { 210 264 if (charData == null) 211 265 return ""; … … 216 270 public Component getPreferencesDecorationPanel() { 217 271 HtmlPanel pnlMessage = new HtmlPanel(); 218 272 HTMLEditorKit kit = (HTMLEditorKit)pnlMessage.getEditorPane().getEditorKit(); 219 kit.getStyleSheet().addRule(".warning-body {background-color:rgb(253,255,221);padding: 10pt; border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}"); 273 kit.getStyleSheet().addRule(".warning-body {background-color:rgb(253,255,221);padding: 10pt;" + 274 "border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}"); 220 275 StringBuilder text = new StringBuilder(); 221 text.append("<html><body>" 222 + "<p class=\"warning-body\">" 223 + "<strong>"+tr("Native Password Manager Plugin")+"</strong><br>" 224 + tr("The username and password is protected by {0}.", type.getName()) 225 ); 276 text.append("<html><body><p class=\"warning-body\"><strong>") 277 .append(tr("Native Password Manager Plugin")) 278 .append("</strong><br>") 279 .append(tr("The username and password is protected by {0}.", type.getName())); 226 280 List<String> sensitive = new ArrayList<>(); 227 281 if (Config.getPref().get("osm-server.username", null) != null) { 228 282 sensitive.add(tr("username")); … … 243 297 sensitive.add(tr("oauth secret")); 244 298 } 245 299 if (!sensitive.isEmpty()) { 246 text.append(tr("<br><strong>Warning:</strong> There may be sensitive data left in your preference file. ({0})", String.join(", ", sensitive))); 300 text.append(tr("<br><strong>Warning:</strong> There may be sensitive data left in your preference file. ({0})", 301 String.join(", ", sensitive))); 247 302 } 248 303 pnlMessage.setText(text.toString()); 249 304 return pnlMessage;