Changeset 18650 in josm for trunk


Ignore:
Timestamp:
2023-02-08T18:31:58+01:00 (22 months ago)
Author:
taylor.smock
Message:

Fix #20768: Add OAuth 2.0 support

This also fixes #21607: authentication buttons are unavailable when credentials
are set.

Location:
trunk
Files:
18 added
23 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/UserIdentityManager.java

    r18211 r18650  
    66import java.text.MessageFormat;
    77
    8 import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
    98import org.openstreetmap.josm.data.osm.User;
    109import org.openstreetmap.josm.data.osm.UserInfo;
     
    6968        if (instance == null) {
    7069            instance = new UserIdentityManager();
    71             if (OsmApi.isUsingOAuth() && OAuthAccessTokenHolder.getInstance().containsAccessToken() &&
    72                     !NetworkManager.isOffline(OnlineResource.OSM_API)) {
     70            if (OsmApi.isUsingOAuthAndOAuthSetUp(OsmApi.getOsmApi()) && !NetworkManager.isOffline(OnlineResource.OSM_API)) {
    7371                try {
    7472                    instance.initFromOAuth();
     
    306304            break;
    307305        default: // Do nothing
    308         }
     306            if (evt.getKey() != null && evt.getKey().equals("oauth.access-token.parameters.OAuth20." + OsmApi.getOsmApi().getHost())) {
     307                accessTokenKeyChanged = true;
     308                accessTokenSecretChanged = true;
     309            }
     310        }
     311        // oauth.access-token.parameters.OAuth20.api.openstreetmap.org
    309312
    310313        if (accessTokenKeyChanged && accessTokenSecretChanged) {
  • trunk/src/org/openstreetmap/josm/data/oauth/OAuthAccessTokenHolder.java

    r13173 r18650  
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.net.URI;
     7import java.util.EnumMap;
     8import java.util.HashMap;
     9import java.util.Map;
     10import java.util.Objects;
     11import java.util.Optional;
     12
    613import org.openstreetmap.josm.io.auth.CredentialsAgent;
    714import org.openstreetmap.josm.io.auth.CredentialsAgentException;
     15import org.openstreetmap.josm.io.auth.CredentialsManager;
    816import org.openstreetmap.josm.spi.preferences.Config;
    917import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    3240    private String accessTokenSecret;
    3341
     42    private final Map<String, Map<OAuthVersion, IOAuthToken>> tokenMap = new HashMap<>();
     43
    3444    /**
    3545     * Replies true if current access token should be saved to the preferences file.
     
    101111            return null;
    102112        return new OAuthToken(accessTokenKey, accessTokenSecret);
     113    }
     114
     115    /**
     116     * Replies the access token.
     117     * @param api The api the token is for
     118     * @param version The OAuth version the token is for
     119     * @return the access token, can be {@code null}
     120     * @since 18650
     121     */
     122    public IOAuthToken getAccessToken(String api, OAuthVersion version) {
     123        api = URI.create(api).getHost();
     124        if (this.tokenMap.containsKey(api)) {
     125            Map<OAuthVersion, IOAuthToken> map = this.tokenMap.get(api);
     126            return map.get(version);
     127        }
     128        try {
     129            IOAuthToken token = CredentialsManager.getInstance().lookupOAuthAccessToken(api);
     130            // We *do* want to set the API token to null, if it doesn't exist. Just to avoid unnecessary lookups.
     131            this.setAccessToken(api, token);
     132            return token;
     133        } catch (CredentialsAgentException exception) {
     134            Logging.trace(exception);
     135        }
     136        return null;
    103137    }
    104138
     
    126160            this.accessTokenKey = token.getKey();
    127161            this.accessTokenSecret = token.getSecret();
     162        }
     163    }
     164
     165    /**
     166     * Sets the access token hold by this holder.
     167     *
     168     * @param api The api the token is for
     169     * @param token the access token. Can be null to clear the content in this holder.
     170     * @since 18650
     171     */
     172    public void setAccessToken(String api, IOAuthToken token) {
     173        Objects.requireNonNull(api, "api url");
     174        // Sometimes the api might be sent as the host
     175        api = Optional.ofNullable(URI.create(api).getHost()).orElse(api);
     176        if (token == null) {
     177            if (this.tokenMap.containsKey(api)) {
     178                this.tokenMap.get(api).clear();
     179            }
     180        } else {
     181            this.tokenMap.computeIfAbsent(api, key -> new EnumMap<>(OAuthVersion.class)).put(token.getOAuthType(), token);
    128182        }
    129183    }
     
    176230            if (!saveToPreferences) {
    177231                cm.storeOAuthAccessToken(null);
     232                for (String host : this.tokenMap.keySet()) {
     233                    cm.storeOAuthAccessToken(host, null);
     234                }
    178235            } else {
    179                 cm.storeOAuthAccessToken(new OAuthToken(accessTokenKey, accessTokenSecret));
     236                if (this.accessTokenKey != null && this.accessTokenSecret != null) {
     237                    cm.storeOAuthAccessToken(new OAuthToken(accessTokenKey, accessTokenSecret));
     238                }
     239                for (Map.Entry<String, Map<OAuthVersion, IOAuthToken>> entry : this.tokenMap.entrySet()) {
     240                    if (entry.getValue().isEmpty()) {
     241                        cm.storeOAuthAccessToken(entry.getKey(), null);
     242                        continue;
     243                    }
     244                    for (OAuthVersion version : OAuthVersion.values()) {
     245                        if (entry.getValue().containsKey(version)) {
     246                            cm.storeOAuthAccessToken(entry.getKey(), entry.getValue().get(version));
     247                        }
     248                    }
     249                }
    180250            }
    181251        } catch (CredentialsAgentException e) {
  • trunk/src/org/openstreetmap/josm/data/oauth/OAuthParameters.java

    r15009 r18650  
    22package org.openstreetmap.josm.data.oauth;
    33
     4import java.io.BufferedReader;
     5import java.io.IOException;
     6import java.net.URL;
    47import java.util.Objects;
    58
     9import javax.json.Json;
     10import javax.json.JsonObject;
     11import javax.json.JsonReader;
     12import javax.json.JsonStructure;
     13import javax.json.JsonValue;
     14
     15import org.openstreetmap.josm.io.OsmApi;
     16import org.openstreetmap.josm.io.auth.CredentialsAgentException;
     17import org.openstreetmap.josm.io.auth.CredentialsManager;
    618import org.openstreetmap.josm.spi.preferences.Config;
    719import org.openstreetmap.josm.spi.preferences.IUrls;
    820import org.openstreetmap.josm.tools.CheckParameterUtil;
     21import org.openstreetmap.josm.tools.HttpClient;
     22import org.openstreetmap.josm.tools.Logging;
    923import org.openstreetmap.josm.tools.Utils;
    1024
     
    1630 * @since 2747
    1731 */
    18 public class OAuthParameters {
     32public class OAuthParameters implements IOAuthParameters {
    1933
    2034    /**
     
    4761     */
    4862    public static OAuthParameters createDefault(String apiUrl) {
     63        return (OAuthParameters) createDefault(apiUrl, OAuthVersion.OAuth10a);
     64    }
     65
     66    /**
     67     * Replies a set of default parameters for a consumer accessing an OSM server
     68     * at the given API url. URL parameters are only set if the URL equals {@link IUrls#getDefaultOsmApiUrl}
     69     * or references the domain "dev.openstreetmap.org", otherwise they may be <code>null</code>.
     70     *
     71     * @param apiUrl The API URL for which the OAuth default parameters are created. If null or empty, the default OSM API url is used.
     72     * @param oAuthVersion The OAuth version to create default parameters for
     73     * @return a set of default parameters for the given {@code apiUrl}
     74     * @since 18650
     75     */
     76    public static IOAuthParameters createDefault(String apiUrl, OAuthVersion oAuthVersion) {
     77        if (!Utils.isValidUrl(apiUrl)) {
     78            apiUrl = null;
     79        }
     80
     81        switch (oAuthVersion) {
     82            case OAuth10a:
     83                return getDefaultOAuth10Parameters(apiUrl);
     84            case OAuth20:
     85            case OAuth21: // For now, OAuth 2.1 (draft) is just OAuth 2.0 with mandatory extensions, which we implement.
     86                return getDefaultOAuth20Parameters(apiUrl);
     87            default:
     88                throw new IllegalArgumentException("Unknown OAuth version: " + oAuthVersion);
     89        }
     90    }
     91
     92    /**
     93     * Get the default OAuth 2.0 parameters
     94     * @param apiUrl The API url
     95     * @return The default parameters
     96     */
     97    private static OAuth20Parameters getDefaultOAuth20Parameters(String apiUrl) {
     98        final String clientId;
     99        final String clientSecret;
     100        final String redirectUri;
     101        final String baseUrl;
     102        if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl)) {
     103            clientId = "";
     104            clientSecret = "";
     105            baseUrl = apiUrl;
     106            HttpClient client = null;
     107            redirectUri = "";
     108            // Check if the server is RFC 8414 compliant
     109            try {
     110                client = HttpClient.create(new URL(apiUrl + (apiUrl.endsWith("/") ? "" : "/") + ".well-known/oauth-authorization-server"));
     111                HttpClient.Response response = client.connect();
     112                if (response.getResponseCode() == 200) {
     113                    try (BufferedReader reader = response.getContentReader();
     114                         JsonReader jsonReader = Json.createReader(reader)) {
     115                        JsonStructure structure = jsonReader.read();
     116                        if (structure.getValueType() == JsonValue.ValueType.OBJECT) {
     117                            return parseAuthorizationServerMetadataResponse(clientId, clientSecret, apiUrl,
     118                                    redirectUri, structure.asJsonObject());
     119                        }
     120                    }
     121                }
     122            } catch (IOException | OAuthException e) {
     123                Logging.trace(e);
     124            } finally {
     125                if (client != null) client.disconnect();
     126            }
     127        } else {
     128            clientId = "edPII614Lm0_0zEpc_QzEltA9BUll93-Y-ugRQUoHMI";
     129            // We don't actually use the client secret in our authorization flow.
     130            clientSecret = null;
     131            baseUrl = "https://www.openstreetmap.org/oauth2";
     132            redirectUri = "http://127.0.0.1:8111/oauth_authorization";
     133            apiUrl = OsmApi.getOsmApi().getBaseUrl();
     134        }
     135        return new OAuth20Parameters(clientId, clientSecret, baseUrl, apiUrl, redirectUri);
     136    }
     137
     138    /**
     139     * Parse the response from <a href="https://www.rfc-editor.org/rfc/rfc8414.html">RFC 8414</a>
     140     * (OAuth 2.0 Authorization Server Metadata)
     141     * @return The parameters for the server metadata
     142     */
     143    private static OAuth20Parameters parseAuthorizationServerMetadataResponse(String clientId, String clientSecret,
     144                                                                              String apiUrl, String redirectUri,
     145                                                                              JsonObject serverMetadata)
     146            throws OAuthException {
     147        final String authorizationEndpoint = serverMetadata.getString("authorization_endpoint", null);
     148        final String tokenEndpoint = serverMetadata.getString("token_endpoint", null);
     149        // This may also have additional documentation like what the endpoints allow (e.g. scopes, algorithms, etc.)
     150        if (authorizationEndpoint == null || tokenEndpoint == null) {
     151            throw new OAuth20Exception("Either token endpoint or authorization endpoints are missing");
     152        }
     153        return new OAuth20Parameters(clientId, clientSecret, tokenEndpoint, authorizationEndpoint, apiUrl, redirectUri);
     154    }
     155
     156    /**
     157     * Get the default OAuth 1.0a parameters
     158     * @param apiUrl The api url
     159     * @return The default parameters
     160     */
     161    private static OAuthParameters getDefaultOAuth10Parameters(String apiUrl) {
    49162        final String consumerKey;
    50163        final String consumerSecret;
    51164        final String serverUrl;
    52 
    53         if (!Utils.isValidUrl(apiUrl)) {
    54             apiUrl = null;
    55         }
    56165
    57166        if (apiUrl != null && !Config.getUrls().getDefaultOsmApiUrl().equals(apiUrl)) {
     
    82191     */
    83192    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()));
     193        return (OAuthParameters) createFromApiUrl(apiUrl, OAuthVersion.OAuth10a);
     194    }
     195
     196    /**
     197     * Replies a set of parameters as defined in the preferences.
     198     *
     199     * @param oAuthVersion The OAuth version to use.
     200     * @param apiUrl the API URL. Must not be {@code null}.
     201     * @return the parameters
     202     * @since 18650
     203     */
     204    public static IOAuthParameters createFromApiUrl(String apiUrl, OAuthVersion oAuthVersion) {
     205        IOAuthParameters parameters = createDefault(apiUrl, oAuthVersion);
     206        switch (oAuthVersion) {
     207            case OAuth10a:
     208                OAuthParameters oauth10aParameters = (OAuthParameters) parameters;
     209                return new OAuthParameters(
     210                    Config.getPref().get("oauth.settings.consumer-key", oauth10aParameters.getConsumerKey()),
     211                    Config.getPref().get("oauth.settings.consumer-secret", oauth10aParameters.getConsumerSecret()),
     212                    Config.getPref().get("oauth.settings.request-token-url", oauth10aParameters.getRequestTokenUrl()),
     213                    Config.getPref().get("oauth.settings.access-token-url", oauth10aParameters.getAccessTokenUrl()),
     214                    Config.getPref().get("oauth.settings.authorise-url", oauth10aParameters.getAuthoriseUrl()),
     215                    Config.getPref().get("oauth.settings.osm-login-url", oauth10aParameters.getOsmLoginUrl()),
     216                    Config.getPref().get("oauth.settings.osm-logout-url", oauth10aParameters.getOsmLogoutUrl()));
     217            case OAuth20:
     218            case OAuth21: // Right now, OAuth 2.1 will work with our OAuth 2.0 implementation
     219                OAuth20Parameters oAuth20Parameters = (OAuth20Parameters) parameters;
     220                try {
     221                    IOAuthToken storedToken = CredentialsManager.getInstance().lookupOAuthAccessToken(apiUrl);
     222                    return storedToken != null ? storedToken.getParameters() : oAuth20Parameters;
     223                } catch (CredentialsAgentException e) {
     224                    Logging.trace(e);
     225                }
     226                return oAuth20Parameters;
     227            default:
     228                throw new IllegalArgumentException("Unknown OAuth version: " + oAuthVersion);
     229        }
    93230    }
    94231
     
    96233     * Remembers the current values in the preferences.
    97234     */
     235    @Override
    98236    public void rememberPreferences() {
    99237        Config.getPref().put("oauth.settings.consumer-key", getConsumerKey());
     
    183321     * @return The access token URL
    184322     */
     323    @Override
    185324    public String getAccessTokenUrl() {
    186325        return accessTokenUrl;
    187326    }
    188327
     328    @Override
     329    public String getAuthorizationUrl() {
     330        return this.authoriseUrl;
     331    }
     332
     333    @Override
     334    public OAuthVersion getOAuthVersion() {
     335        return OAuthVersion.OAuth10a;
     336    }
     337
     338    @Override
     339    public String getClientId() {
     340        return this.consumerKey;
     341    }
     342
     343    @Override
     344    public String getClientSecret() {
     345        return this.consumerSecret;
     346    }
     347
    189348    /**
    190349     * Gets the authorise URL.
     
    192351     */
    193352    public String getAuthoriseUrl() {
    194         return authoriseUrl;
     353        return this.getAuthorizationUrl();
    195354    }
    196355
  • trunk/src/org/openstreetmap/josm/gui/oauth/AbstractAuthorizationUI.java

    r16553 r18650  
    44import java.util.Objects;
    55
     6import org.openstreetmap.josm.data.oauth.IOAuthParameters;
    67import org.openstreetmap.josm.data.oauth.OAuthParameters;
    78import org.openstreetmap.josm.data.oauth.OAuthToken;
     9import org.openstreetmap.josm.data.oauth.OAuthVersion;
    810import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
    911import org.openstreetmap.josm.tools.CheckParameterUtil;
     
    2123
    2224    private String apiUrl;
    23     private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel();
     25    private final AdvancedOAuthPropertiesPanel pnlAdvancedProperties = new AdvancedOAuthPropertiesPanel(OAuthVersion.OAuth10a);
    2426    private transient OAuthToken accessToken;
    2527
     
    8082     * @return the current set of advanced OAuth parameters in this UI
    8183     */
    82     public OAuthParameters getOAuthParameters() {
     84    public IOAuthParameters getOAuthParameters() {
    8385        return pnlAdvancedProperties.getAdvancedParameters();
    8486    }
  • trunk/src/org/openstreetmap/josm/gui/oauth/AdvancedOAuthPropertiesPanel.java

    r15586 r18650  
    1616import javax.swing.JOptionPane;
    1717
     18import org.openstreetmap.josm.data.oauth.IOAuthParameters;
     19import org.openstreetmap.josm.data.oauth.OAuth20Parameters;
    1820import org.openstreetmap.josm.data.oauth.OAuthParameters;
     21import org.openstreetmap.josm.data.oauth.OAuthVersion;
    1922import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    2023import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
     
    5255    private final JosmTextField tfOsmLoginURL = new JosmTextField();
    5356    private final JosmTextField tfOsmLogoutURL = new JosmTextField();
     57    private final OAuthVersion oauthVersion;
    5458    private transient UseDefaultItemListener ilUseDefault;
    5559    private String apiUrl;
     
    5761    /**
    5862     * Constructs a new {@code AdvancedOAuthPropertiesPanel}.
    59      */
    60     public AdvancedOAuthPropertiesPanel() {
     63     * @param oauthVersion The OAuth version to make the panel for
     64     */
     65    public AdvancedOAuthPropertiesPanel(OAuthVersion oauthVersion) {
     66        this.oauthVersion = oauthVersion;
    6167        build();
    6268    }
     
    7177        gc.weightx = 1.0;
    7278        gc.insets = new Insets(0, 0, 3, 3);
    73         gc.gridwidth = 2;
     79        gc.gridwidth = 3;
    7480        add(cbUseDefaults, gc);
    7581
     
    7884        gc.weightx = 0.0;
    7985        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        }
    8191
    8292        gc.gridx = 1;
     
    8696
    8797        // -- consumer secret
    88         gc.gridy = 2;
     98        gc.gridy++;
    8999        gc.gridx = 0;
    90100        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        }
    92106
    93107        gc.gridx = 1;
     
    97111
    98112        // -- request token URL
    99         gc.gridy = 3;
     113        gc.gridy++;
    100114        gc.gridx = 0;
    101115        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        }
    103121
    104122        gc.gridx = 1;
     
    108126
    109127        // -- access token URL
    110         gc.gridy = 4;
     128        gc.gridy++;
    111129        gc.gridx = 0;
    112130        gc.weightx = 0.0;
     
    119137
    120138        // -- authorise URL
    121         gc.gridy = 5;
     139        gc.gridy++;
    122140        gc.gridx = 0;
    123141        gc.weightx = 0.0;
     
    129147        SelectAllOnFocusGainedDecorator.decorate(tfAuthoriseURL);
    130148
    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);
    136 
    137         gc.gridx = 1;
    138         gc.weightx = 1.0;
    139         add(tfOsmLoginURL, gc);
    140         SelectAllOnFocusGainedDecorator.decorate(tfOsmLoginURL);
    141 
    142         // -- OSM logout URL
    143         gc.gridy = 7;
    144         gc.gridx = 0;
    145         gc.weightx = 0.0;
    146         add(new JLabel(tr("OSM logout URL:")), gc);
    147 
    148         gc.gridx = 1;
    149         gc.weightx = 1.0;
    150         add(tfOsmLogoutURL, gc);
    151         SelectAllOnFocusGainedDecorator.decorate(tfOsmLogoutURL);
     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);
     155
     156            gc.gridx = 1;
     157            gc.weightx = 1.0;
     158            add(tfOsmLoginURL, gc);
     159            SelectAllOnFocusGainedDecorator.decorate(tfOsmLoginURL);
     160
     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);
     166
     167            gc.gridx = 1;
     168            gc.weightx = 1.0;
     169            add(tfOsmLogoutURL, gc);
     170            SelectAllOnFocusGainedDecorator.decorate(tfOsmLogoutURL);
     171        }
    152172
    153173        ilUseDefault = new UseDefaultItemListener();
     
    192212    protected void resetToDefaultSettings() {
    193213        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        }
    202235
    203236        setChildComponentsEnabled(false);
     
    217250     * @return the OAuth parameters
    218251     */
    219     public OAuthParameters getAdvancedParameters() {
     252    public IOAuthParameters getAdvancedParameters() {
    220253        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                );
    230272    }
    231273
     
    236278     * @throws IllegalArgumentException if parameters is null.
    237279     */
    238     public void setAdvancedParameters(OAuthParameters parameters) {
     280    public void setAdvancedParameters(IOAuthParameters parameters) {
    239281        CheckParameterUtil.ensureParameterNotNull(parameters, "parameters");
    240         if (parameters.equals(OAuthParameters.createDefault(apiUrl))) {
     282        if (parameters.equals(OAuthParameters.createDefault(apiUrl, parameters.getOAuthVersion()))) {
    241283            cbUseDefaults.setSelected(true);
    242284            setChildComponentsEnabled(false);
     
    244286            cbUseDefaults.setSelected(false);
    245287            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            }
    253305        }
    254306    }
  • trunk/src/org/openstreetmap/josm/gui/oauth/FullyAutomaticAuthorizationUI.java

    r17333 r18650  
    2929import javax.swing.text.html.HTMLEditorKit;
    3030
     31import org.openstreetmap.josm.data.oauth.OAuthParameters;
    3132import org.openstreetmap.josm.data.oauth.OAuthToken;
    3233import org.openstreetmap.josm.gui.HelpAwareOptionPane;
     
    385386                    FullyAutomaticAuthorizationUI.this,
    386387                    getApiUrl(),
    387                     getAdvancedPropertiesPanel().getAdvancedParameters(),
     388                    (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(),
    388389                    getAccessToken()
    389390            ));
     
    438439                            + "Please check your advanced setting and try again."
    439440                            + "</html>",
    440                             getAdvancedPropertiesPanel().getAdvancedParameters().getAuthoriseUrl()),
     441                            ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getAuthoriseUrl()),
    441442                    tr("OAuth authorization failed"),
    442443                    JOptionPane.ERROR_MESSAGE,
     
    446447
    447448        protected void alertLoginFailed() {
    448             final String loginUrl = getAdvancedPropertiesPanel().getAdvancedParameters().getOsmLoginUrl();
     449            final String loginUrl = ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getOsmLoginUrl();
    449450            HelpAwareOptionPane.showOptionDialog(
    450451                    FullyAutomaticAuthorizationUI.this,
     
    480481                getProgressMonitor().setTicksCount(3);
    481482                OsmOAuthAuthorizationClient authClient = new OsmOAuthAuthorizationClient(
    482                         getAdvancedPropertiesPanel().getAdvancedParameters()
     483                        (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()
    483484                );
    484485                OAuthToken requestToken = authClient.getRequestToken(
  • trunk/src/org/openstreetmap/josm/gui/oauth/ManualAuthorizationUI.java

    r15662 r18650  
    2626
    2727import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
     28import org.openstreetmap.josm.data.oauth.OAuthParameters;
    2829import org.openstreetmap.josm.data.oauth.OAuthToken;
    2930import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator;
     
    233234                    ManualAuthorizationUI.this,
    234235                    getApiUrl(),
    235                     getAdvancedPropertiesPanel().getAdvancedParameters(),
     236                    (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(),
    236237                    getAccessToken()
    237238            );
  • trunk/src/org/openstreetmap/josm/gui/oauth/OAuthAuthorizationWizard.java

    r17600 r18650  
    256256     */
    257257    public OAuthParameters getOAuthParameters() {
    258         return getCurrentAuthorisationUI().getOAuthParameters();
     258        return (OAuthParameters) getCurrentAuthorisationUI().getOAuthParameters();
    259259    }
    260260
  • trunk/src/org/openstreetmap/josm/gui/oauth/SemiAutomaticAuthorizationUI.java

    r17333 r18650  
    2323
    2424import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
     25import org.openstreetmap.josm.data.oauth.OAuthParameters;
    2526import org.openstreetmap.josm.data.oauth.OAuthToken;
    2627import org.openstreetmap.josm.gui.util.GuiHelper;
     
    7980    protected void transitionToRetrieveAccessToken() {
    8081        OsmOAuthAuthorizationClient client = new OsmOAuthAuthorizationClient(
    81                 getAdvancedPropertiesPanel().getAdvancedParameters()
     82                (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()
    8283        );
    8384        String authoriseUrl = client.getAuthoriseUrl(requestToken);
     
    184185                    + "''{1}''.</html>",
    185186                    tr("Retrieve Request Token"),
    186                     getAdvancedPropertiesPanel().getAdvancedParameters().getRequestTokenUrl()
     187                    ((OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()).getRequestTokenUrl()
    187188            ));
    188189            pnl.add(h, gc);
     
    391392            final RetrieveRequestTokenTask task = new RetrieveRequestTokenTask(
    392393                    SemiAutomaticAuthorizationUI.this,
    393                     getAdvancedPropertiesPanel().getAdvancedParameters()
     394                    (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters()
    394395            );
    395396            executor.execute(task);
     
    419420            final RetrieveAccessTokenTask task = new RetrieveAccessTokenTask(
    420421                    SemiAutomaticAuthorizationUI.this,
    421                     getAdvancedPropertiesPanel().getAdvancedParameters(),
     422                    (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(),
    422423                    requestToken
    423424            );
     
    451452                    SemiAutomaticAuthorizationUI.this,
    452453                    getApiUrl(),
    453                     getAdvancedPropertiesPanel().getAdvancedParameters(),
     454                    (OAuthParameters) getAdvancedPropertiesPanel().getAdvancedParameters(),
    454455                    getAccessToken()
    455456            );
  • trunk/src/org/openstreetmap/josm/gui/preferences/advanced/AdvancedPreference.java

    r17768 r18650  
    236236        Map<String, Setting<?>> orig = Preferences.main().getAllSettings();
    237237        Map<String, Setting<?>> defaults = tmpPrefs.getAllDefaults();
    238         orig.remove("osm-server.password");
    239         defaults.remove("osm-server.password");
     238        Preferences.main().getSensitive().forEach(orig::remove);
     239        tmpPrefs.getSensitive().forEach(defaults::remove);
    240240        if (tmpPrefs != Preferences.main()) {
    241241            loaded = tmpPrefs.getAllSettings();
  • trunk/src/org/openstreetmap/josm/gui/preferences/server/AuthenticationPreferencesPanel.java

    r17333 r18650  
    55
    66import java.awt.BorderLayout;
     7import java.awt.FlowLayout;
    78import java.awt.GridBagConstraints;
    89import java.awt.GridBagLayout;
     
    1819
    1920import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
     21import org.openstreetmap.josm.data.oauth.OAuthVersion;
    2022import org.openstreetmap.josm.gui.help.HelpUtil;
    2123import org.openstreetmap.josm.gui.widgets.VerticallyScrollablePanel;
     
    2325import org.openstreetmap.josm.io.auth.CredentialsManager;
    2426import org.openstreetmap.josm.spi.preferences.Config;
     27import org.openstreetmap.josm.tools.GBC;
    2528import org.openstreetmap.josm.tools.Logging;
    2629
     
    3336    /** indicates whether we use basic authentication */
    3437    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 */
    3639    private final JRadioButton rbOAuth = new JRadioButton();
     40    /** indicates whether we use OAuth 2.0 as authentication scheme */
     41    private final JRadioButton rbOAuth20 = new JRadioButton();
    3742    /** the panel which contains the authentication parameters for the respective authentication scheme */
    3843    private final JPanel pnlAuthenticationParameters = new JPanel(new BorderLayout());
    3944    /** the panel for the basic authentication parameters */
    4045    private BasicAuthenticationPreferencesPanel pnlBasicAuthPreferences;
    41     /** the panel for the OAuth authentication parameters */
     46    /** the panel for the OAuth 1.0a authentication parameters */
    4247    private OAuthAuthenticationPreferencesPanel pnlOAuthPreferences;
     48    /** the panel for the OAuth 2.0 authentication parameters */
     49    private OAuthAuthenticationPreferencesPanel pnlOAuth20Preferences;
    4350
    4451    /**
     
    5663    protected final void build() {
    5764        setLayout(new GridBagLayout());
    58         GridBagConstraints gc = new GridBagConstraints();
    5965
    6066        AuthenticationMethodChangeListener authChangeListener = new AuthenticationMethodChangeListener();
    6167
     68        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
    6269        // -- 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);
    6971        rbBasicAuthentication.setText(tr("Use Basic Authentication"));
    7072        rbBasicAuthentication.setToolTipText(tr("Select to use HTTP basic authentication with your OSM username and password"));
    7173        rbBasicAuthentication.addItemListener(authChangeListener);
    7274
    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"));
    7979        rbOAuth.addItemListener(authChangeListener);
    8080
     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());
    8188        //-- radio button for OAuth
    8289        ButtonGroup bg = new ButtonGroup();
    8390        bg.add(rbBasicAuthentication);
    8491        bg.add(rbOAuth);
     92        bg.add(rbOAuth20);
    8593
    8694        //-- 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);
    8798        gc.gridx = 0;
    8899        gc.gridy = 1;
     
    95106        //-- the two panels for authentication parameters
    96107        pnlBasicAuthPreferences = new BasicAuthenticationPreferencesPanel();
    97         pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel();
     108        pnlOAuthPreferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth10a);
     109        pnlOAuth20Preferences = new OAuthAuthenticationPreferencesPanel(OAuthVersion.OAuth20);
    98110
    99111        rbBasicAuthentication.setSelected(true);
     
    110122        } else if ("oauth".equals(authMethod)) {
    111123            rbOAuth.setSelected(true);
     124        } else if ("oauth20".equals(authMethod)) {
     125            rbOAuth20.setSelected(true);
    112126        } else {
    113127            Logging.warn(tr("Unsupported value in preference ''{0}'', got ''{1}''. Using authentication method ''Basic Authentication''.",
     
    117131        pnlBasicAuthPreferences.initFromPreferences();
    118132        pnlOAuthPreferences.initFromPreferences();
     133        pnlOAuth20Preferences.initFromPreferences();
    119134    }
    120135
     
    127142        if (rbBasicAuthentication.isSelected()) {
    128143            authMethod = "basic";
     144        } else if (rbOAuth.isSelected()) {
     145            authMethod = "oauth";
     146        } else if (rbOAuth20.isSelected()) {
     147            authMethod = "oauth20";
    129148        } else {
    130             authMethod = "oauth";
     149            throw new IllegalStateException("One of OAuth 2.0, OAuth 1.0a, or Basic authentication must be checked");
    131150        }
    132151        Config.getPref().put("osm-server.auth-method", authMethod);
     
    141160            pnlBasicAuthPreferences.saveToPreferences();
    142161            pnlOAuthPreferences.saveToPreferences();
     162        } else { // oauth20
     163            // clear the password in the preferences
     164            pnlBasicAuthPreferences.clearPassword();
     165            pnlBasicAuthPreferences.saveToPreferences();
     166            pnlOAuth20Preferences.saveToPreferences();
    143167        }
    144168    }
     
    150174        @Override
    151175        public void itemStateChanged(ItemEvent e) {
     176            pnlAuthenticationParameters.removeAll();
    152177            if (rbBasicAuthentication.isSelected()) {
    153                 pnlAuthenticationParameters.removeAll();
    154178                pnlAuthenticationParameters.add(pnlBasicAuthPreferences, BorderLayout.CENTER);
    155179                pnlBasicAuthPreferences.revalidate();
    156             } else {
    157                 pnlAuthenticationParameters.removeAll();
     180            } else if (rbOAuth.isSelected()) {
    158181                pnlAuthenticationParameters.add(pnlOAuthPreferences, BorderLayout.CENTER);
    159182                pnlOAuthPreferences.revalidate();
     183            } else if (rbOAuth20.isSelected()) {
     184                pnlAuthenticationParameters.add(pnlOAuth20Preferences, BorderLayout.CENTER);
     185                pnlOAuth20Preferences.revalidate();
    160186            }
    161187            repaint();
  • trunk/src/org/openstreetmap/josm/gui/preferences/server/OAuthAuthenticationPreferencesPanel.java

    r17607 r18650  
    2424
    2525import org.openstreetmap.josm.actions.ExpertToggleAction;
     26import org.openstreetmap.josm.data.oauth.IOAuthToken;
     27import org.openstreetmap.josm.data.oauth.OAuth20Authorization;
     28import org.openstreetmap.josm.data.oauth.OAuth20Token;
    2629import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
    2730import org.openstreetmap.josm.data.oauth.OAuthParameters;
    2831import org.openstreetmap.josm.data.oauth.OAuthToken;
     32import org.openstreetmap.josm.data.oauth.OAuthVersion;
     33import org.openstreetmap.josm.data.oauth.osm.OsmScopes;
    2934import org.openstreetmap.josm.gui.MainApplication;
    3035import org.openstreetmap.josm.gui.oauth.AdvancedOAuthPropertiesPanel;
     
    3237import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
    3338import org.openstreetmap.josm.gui.oauth.TestAccessTokenTask;
     39import org.openstreetmap.josm.gui.util.GuiHelper;
    3440import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
    3541import org.openstreetmap.josm.gui.widgets.JosmTextField;
    3642import org.openstreetmap.josm.io.OsmApi;
    3743import org.openstreetmap.josm.io.auth.CredentialsManager;
     44import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
    3845import org.openstreetmap.josm.tools.GBC;
    3946import org.openstreetmap.josm.tools.ImageProvider;
     
    4249
    4350/**
    44  * The preferences panel for the OAuth preferences. This just a summary panel
     51 * The preferences panel for the OAuth 1.0a preferences. This just a summary panel
    4552 * showing the current Access Token Key and Access Token Secret, if the
    4653 * user already has an Access Token.
    47  *
     54 * <br>
    4855 * For initial authorisation see {@link OAuthAuthorizationWizard}.
    4956 * @since 2745
     
    5461    private final JCheckBox cbSaveToPreferences = new JCheckBox(tr("Save to preferences"));
    5562    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;
    5967    private String apiUrl;
    6068
    6169    /**
    62      * Create the panel
     70     * Create the panel. Uses {@link OAuthVersion#OAuth10a}.
    6371     */
    6472    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();
    6586        build();
    66         refreshView();
    6787    }
    6888
     
    118138    protected void refreshView() {
    119139        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) {
    121143            pnlAuthorisationMessage.add(pnlAlreadyAuthorised, BorderLayout.CENTER);
    122144            pnlAlreadyAuthorised.refreshView();
     
    181203
    182204            // 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            }
    184208            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            }
    188214
    189215            // filler - grab remaining space
     
    254280            // -- action buttons
    255281            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()));
    258288            gc.gridy = 4;
    259289            gc.gridx = 0;
     
    278308
    279309        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            }
    284328            cbSaveToPreferences.setSelected(OAuthAccessTokenHolder.getInstance().isSaveToPreferences());
    285329        }
     
    296340            putValue(NAME, tr("{0} ({1})", tr("Authorize now"), procedure.getText()));
    297341            putValue(SHORT_DESCRIPTION, procedure.getDescription());
    298             if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC) {
     342            if (procedure == AuthorizationProcedure.FULLY_AUTOMATIC
     343            || OAuthAuthenticationPreferencesPanel.this.oAuthVersion != OAuthVersion.OAuth10a) {
    299344                new ImageProvider("oauth", "oauth-small").getResource().attachImageIcon(this);
    300345            }
     
    303348        @Override
    304349        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                // TODO: Ask user if they want to start remote control?
     367                if (!remoteControlIsRunning) {
     368                    RemoteControl.start();
     369                }
     370                new OAuth20Authorization().authorize(OAuthParameters.createDefault(OsmApi.getOsmApi().getServerUrl(), oAuthVersion), token -> {
     371                    if (!remoteControlIsRunning) {
     372                        RemoteControl.stop();
     373                    }
     374                    // Clean up old token/password
     375                    OAuthAccessTokenHolder.getInstance().setAccessToken(null);
     376                    OAuthAccessTokenHolder.getInstance().setAccessToken(OsmApi.getOsmApi().getServerUrl(), token);
     377                    OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
     378                    GuiHelper.runInEDT(OAuthAuthenticationPreferencesPanel.this::refreshView);
     379                }, OsmScopes.read_gpx, OsmScopes.write_gpx,
     380                        OsmScopes.read_prefs, OsmScopes.write_prefs,
     381                        OsmScopes.write_api, OsmScopes.write_notes);
     382            }
     383        }
     384    }
     385
     386    /**
     387     * Remove the OAuth authorization token
     388     */
     389    private class RemoveAuthorisationAction extends AbstractAction {
     390        RemoveAuthorisationAction() {
     391            putValue(NAME, tr("Remove token"));
     392            putValue(SHORT_DESCRIPTION, tr("Remove token from JOSM. This does not revoke the token."));
     393            new ImageProvider("cancel").getResource().attachImageIcon(this);
     394        }
     395
     396        @Override
     397        public void actionPerformed(ActionEvent e) {
     398            if (oAuthVersion == OAuthVersion.OAuth10a) {
     399                OAuthAccessTokenHolder.getInstance().setAccessToken(null);
     400            } else {
     401                OAuthAccessTokenHolder.getInstance().setAccessToken(apiUrl, (IOAuthToken) null);
     402            }
     403            OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
    317404            refreshView();
    318405        }
  • trunk/src/org/openstreetmap/josm/io/MessageNotifier.java

    r18211 r18650  
    1111
    1212import org.openstreetmap.josm.data.UserIdentityManager;
     13import org.openstreetmap.josm.data.oauth.OAuthVersion;
    1314import org.openstreetmap.josm.data.osm.UserInfo;
    1415import org.openstreetmap.josm.data.preferences.BooleanProperty;
     
    144145            try {
    145146                if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) {
    146                     if (OsmApi.isUsingOAuth()) {
     147                    if (OsmApi.isUsingOAuth(OAuthVersion.OAuth10a)) {
    147148                        return credManager.lookupOAuthAccessToken() != null;
     149                    } else if (OsmApi.isUsingOAuth(OAuthVersion.OAuth20) || OsmApi.isUsingOAuth(OAuthVersion.OAuth21)) {
     150                        return credManager.lookupOAuthAccessToken(OsmApi.getOsmApi().getHost()) != null;
     151                    } else if (OsmApi.isUsingOAuth()) {
     152                        // Ensure we do not forget to update this section
     153                        throw new IllegalStateException("Unknown oauth version: " + OsmApi.getAuthMethod());
    148154                    } else {
    149155                        String username = Config.getPref().get("osm-server.username", null);
  • trunk/src/org/openstreetmap/josm/io/OsmApi.java

    r18532 r18650  
    3030import org.openstreetmap.josm.data.coor.LatLon;
    3131import org.openstreetmap.josm.data.notes.Note;
     32import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
     33import org.openstreetmap.josm.data.oauth.OAuthVersion;
    3234import org.openstreetmap.josm.data.osm.Changeset;
    3335import org.openstreetmap.josm.data.osm.IPrimitive;
     
    8385
    8486    private static final ListenerList<OsmApiInitializationListener> listeners = ListenerList.create();
     87    /** This is used to make certain we have set osm-server.auth-method to the "right" default */
     88    private static boolean oauthCompatibilitySwitch;
    8589
    8690    private URL url;
     
    646650     */
    647651    public static boolean isUsingOAuth() {
    648         return "oauth".equals(getAuthMethod());
     652        return isUsingOAuth(OAuthVersion.OAuth10a)
     653                || isUsingOAuth(OAuthVersion.OAuth20)
     654                || isUsingOAuth(OAuthVersion.OAuth21);
     655    }
     656
     657    /**
     658     * Determines if JOSM is configured to access OSM API via OAuth
     659     * @param version The OAuth version
     660     * @return {@code true} if JOSM is configured to access OSM API via OAuth, {@code false} otherwise
     661     * @since 18650
     662     */
     663    public static boolean isUsingOAuth(OAuthVersion version) {
     664        if (version == OAuthVersion.OAuth10a) {
     665            return "oauth".equalsIgnoreCase(getAuthMethod());
     666        } else if (version == OAuthVersion.OAuth20 || version == OAuthVersion.OAuth21) {
     667            return "oauth20".equalsIgnoreCase(getAuthMethod());
     668        }
     669        return false;
     670    }
     671
     672    /**
     673     * Ensure that OAuth is set up
     674     * @param api The api for which we need OAuth keys
     675     * @return {@code true} if we are using OAuth and there are keys for the specified API
     676     */
     677    public static boolean isUsingOAuthAndOAuthSetUp(OsmApi api) {
     678        if (OsmApi.isUsingOAuth()) {
     679            if (OsmApi.isUsingOAuth(OAuthVersion.OAuth10a)) {
     680                return OAuthAccessTokenHolder.getInstance().containsAccessToken();
     681            }
     682            if (OsmApi.isUsingOAuth(OAuthVersion.OAuth20)) {
     683                return OAuthAccessTokenHolder.getInstance().getAccessToken(api.getBaseUrl(), OAuthVersion.OAuth20) != null;
     684            }
     685            if (OsmApi.isUsingOAuth(OAuthVersion.OAuth21)) {
     686                return OAuthAccessTokenHolder.getInstance().getAccessToken(api.getBaseUrl(), OAuthVersion.OAuth21) != null;
     687            }
     688        }
     689        return false;
    649690    }
    650691
     
    654695     */
    655696    public static String getAuthMethod() {
    656         return Config.getPref().get("osm-server.auth-method", "oauth");
     697        setCurrentAuthMethod();
     698        return Config.getPref().get("osm-server.auth-method", "oauth20");
     699    }
     700
     701    /**
     702     * This is a compatibility method for users who currently use OAuth 1.0 -- we are changing the default from oauth to oauth20,
     703     * but since oauth was the default, pre-existing users will suddenly be switched to oauth20.
     704     * This should be removed whenever {@link OAuthVersion#OAuth10a} support is removed.
     705     */
     706    private static void setCurrentAuthMethod() {
     707        if (!oauthCompatibilitySwitch) {
     708            oauthCompatibilitySwitch = true;
     709            final String prefKey = "osm-server.auth-method";
     710            if ("oauth20".equals(Config.getPref().get(prefKey, "oauth20"))
     711                && !isUsingOAuthAndOAuthSetUp(OsmApi.getOsmApi())
     712                && OAuthAccessTokenHolder.getInstance().containsAccessToken()) {
     713                Config.getPref().put(prefKey, "oauth");
     714            }
     715        }
    657716    }
    658717
  • trunk/src/org/openstreetmap/josm/io/OsmConnection.java

    r16643 r18650  
    1111import java.util.Base64;
    1212import java.util.Objects;
    13 
     13import java.util.concurrent.TimeUnit;
     14import java.util.concurrent.atomic.AtomicBoolean;
     15import java.util.function.Consumer;
     16
     17import javax.swing.JOptionPane;
     18
     19import org.openstreetmap.josm.data.oauth.IOAuthParameters;
     20import org.openstreetmap.josm.data.oauth.IOAuthToken;
     21import org.openstreetmap.josm.data.oauth.OAuth20Authorization;
    1422import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
    1523import org.openstreetmap.josm.data.oauth.OAuthParameters;
     24import org.openstreetmap.josm.data.oauth.OAuthVersion;
     25import org.openstreetmap.josm.data.oauth.osm.OsmScopes;
     26import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
     27import org.openstreetmap.josm.gui.MainApplication;
     28import org.openstreetmap.josm.gui.util.GuiHelper;
    1629import org.openstreetmap.josm.io.auth.CredentialsAgentException;
    1730import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
    1831import org.openstreetmap.josm.io.auth.CredentialsManager;
     32import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
    1933import org.openstreetmap.josm.tools.HttpClient;
    2034import org.openstreetmap.josm.tools.JosmRuntimeException;
     
    3751    protected HttpClient activeConnection;
    3852    protected OAuthParameters oauthParameters;
     53    protected IOAuthParameters oAuth20Parameters;
    3954
    4055    /**
     
    172187            OAuthAccessTokenHolder.getInstance().setSaveToPreferences(true);
    173188            OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
    174         } catch (MalformedURLException | InterruptedException | InvocationTargetException e) {
     189        } catch (MalformedURLException | InvocationTargetException e) {
    175190            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            }
     248        }
     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(connection.getURL().getHost(), 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);
    176276        }
    177277    }
     
    179279    protected void addAuth(HttpClient connection) throws OsmTransferException {
    180280        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);
    189295        }
    190296    }
  • trunk/src/org/openstreetmap/josm/io/OsmServerReader.java

    r16553 r18650  
    1111import java.net.URL;
    1212import java.util.List;
     13import java.util.Objects;
    1314
    1415import javax.xml.parsers.ParserConfigurationException;
     
    1819import org.openstreetmap.josm.data.osm.DataSet;
    1920import org.openstreetmap.josm.gui.progress.ProgressMonitor;
    20 import org.openstreetmap.josm.io.auth.CredentialsAgentException;
    2121import org.openstreetmap.josm.io.auth.CredentialsManager;
    2222import org.openstreetmap.josm.tools.HttpClient;
     
    3939 */
    4040public abstract class OsmServerReader extends OsmConnection {
    41     private final OsmApi api = OsmApi.getOsmApi();
     41    private final OsmApi api;
    4242    private boolean doAuthenticate;
    4343    protected boolean gpxParsedProperly;
     
    4848     */
    4949    protected OsmServerReader() {
    50         try {
    51             doAuthenticate = OsmApi.isUsingOAuth()
    52                     && CredentialsManager.getInstance().lookupOAuthAccessToken() != null
    53                     && OsmApi.USE_OAUTH_FOR_ALL_REQUESTS.get();
    54         } catch (CredentialsAgentException e) {
    55             Logging.warn(e);
    56         }
     50        this(OsmApi.getOsmApi());
     51    }
     52
     53    /**
     54     * Constructs a new {@code OsmServerReader}.
     55     * @param osmApi The API to use for this call
     56     * @since 18650
     57     */
     58    protected OsmServerReader(OsmApi osmApi) {
     59        this.api = osmApi;
     60        this.doAuthenticate = OsmApi.isUsingOAuthAndOAuthSetUp(osmApi) && OsmApi.USE_OAUTH_FOR_ALL_REQUESTS.get();
    5761    }
    5862
     
    185189            activeConnection = client;
    186190            adaptRequest(client);
    187             if (doAuthenticate) {
     191            if (doAuthenticate && Objects.equals(this.api.getHost(), client.getURL().getHost())) {
    188192                addAuth(client);
    189193            }
  • trunk/src/org/openstreetmap/josm/io/auth/CredentialsAgent.java

    r12992 r18650  
    66import java.net.PasswordAuthentication;
    77
     8import javax.annotation.Nullable;
     9
     10import org.openstreetmap.josm.data.oauth.IOAuthToken;
    811import org.openstreetmap.josm.data.oauth.OAuthToken;
    912
     
    6669
    6770    /**
     71     * Lookup the current OAuth Access Token to access the specified server. Replies null, if no
     72     * Access Token is currently managed by this CredentialAgent.
     73     *
     74     * @param host The host to get OAuth credentials for
     75     * @return the current OAuth Access Token to access the specified server.
     76     * @throws CredentialsAgentException if something goes wrong
     77     * @since 18650
     78     */
     79    @Nullable
     80    IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException;
     81
     82    /**
    6883     * Stores the OAuth Access Token <code>accessToken</code>.
    6984     *
     
    7287     */
    7388    void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException;
     89
     90    /**
     91     * Stores the OAuth Access Token <code>accessToken</code>.
     92     *
     93     * @param host The host the access token is for
     94     * @param accessToken the access Token. null, to remove the Access Token. This will remove all IOAuthTokens <i>not</i> managed by
     95     *                    {@link #storeOAuthAccessToken(OAuthToken)}.
     96     * @throws CredentialsAgentException if something goes wrong
     97     * @since 18650
     98     */
     99    void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException;
    74100
    75101    /**
  • trunk/src/org/openstreetmap/josm/io/auth/CredentialsManager.java

    r18211 r18650  
    88
    99import org.openstreetmap.josm.data.UserIdentityManager;
     10import org.openstreetmap.josm.data.oauth.IOAuthToken;
    1011import org.openstreetmap.josm.data.oauth.OAuthToken;
    1112import org.openstreetmap.josm.io.OsmApi;
     
    162163
    163164    @Override
     165    public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException {
     166        return delegate.lookupOAuthAccessToken(host);
     167    }
     168
     169    @Override
    164170    public void storeOAuthAccessToken(OAuthToken accessToken) throws CredentialsAgentException {
    165171        delegate.storeOAuthAccessToken(accessToken);
     172    }
     173
     174    @Override
     175    public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException {
     176        delegate.storeOAuthAccessToken(host, accessToken);
    166177    }
    167178
  • trunk/src/org/openstreetmap/josm/io/auth/JosmPreferencesCredentialAgent.java

    r14698 r18650  
    77import java.net.Authenticator.RequestorType;
    88import java.net.PasswordAuthentication;
     9import java.util.HashSet;
    910import java.util.Objects;
    10 
     11import java.util.Set;
     12
     13import javax.json.JsonException;
    1114import javax.swing.text.html.HTMLEditorKit;
    1215
     16import org.openstreetmap.josm.data.oauth.IOAuthToken;
     17import org.openstreetmap.josm.data.oauth.OAuth20Exception;
     18import org.openstreetmap.josm.data.oauth.OAuth20Parameters;
     19import org.openstreetmap.josm.data.oauth.OAuth20Token;
    1320import org.openstreetmap.josm.data.oauth.OAuthToken;
     21import org.openstreetmap.josm.data.oauth.OAuthVersion;
    1422import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    1523import org.openstreetmap.josm.io.DefaultProxySelector;
    1624import org.openstreetmap.josm.io.OsmApi;
    1725import org.openstreetmap.josm.spi.preferences.Config;
     26import org.openstreetmap.josm.tools.Utils;
    1827
    1928/**
     
    6978            if (Objects.equals(OsmApi.getOsmApi().getHost(), host)) {
    7079                Config.getPref().put("osm-server.username", credentials.getUserName());
    71                 if (credentials.getPassword() == null) {
     80                if (credentials.getPassword().length == 0) { // PasswordAuthentication#getPassword cannot be null
    7281                    Config.getPref().put("osm-server.password", null);
    7382                } else {
     
    7685            } else if (host != null) {
    7786                Config.getPref().put("server.username."+host, credentials.getUserName());
    78                 if (credentials.getPassword() == null) {
     87                if (credentials.getPassword().length == 0) {
    7988                    Config.getPref().put("server.password."+host, null);
    8089                } else {
     
    8594        case PROXY:
    8695            Config.getPref().put(DefaultProxySelector.PROXY_USER, credentials.getUserName());
    87             if (credentials.getPassword() == null) {
     96            if (credentials.getPassword().length == 0) {
    8897                Config.getPref().put(DefaultProxySelector.PROXY_PASS, null);
    8998            } else {
     
    108117            return null;
    109118        return new OAuthToken(accessTokenKey, accessTokenSecret);
     119    }
     120
     121    @Override
     122    public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException {
     123        Set<String> keySet = new HashSet<>(Config.getPref().getKeySet());
     124        keySet.addAll(Config.getPref().getSensitive()); // Just in case we decide to not return sensitive keys in getKeySet
     125        for (OAuthVersion oauthType : OAuthVersion.values()) {
     126            final String hostKey = "oauth.access-token.object." + oauthType + "." + host;
     127            final String parametersKey = "oauth.access-token.parameters." + oauthType + "." + host;
     128            if (!keySet.contains(hostKey) || !keySet.contains(parametersKey)) {
     129                continue; // Avoid adding empty temporary entries to preferences
     130            }
     131            String token = Config.getPref().get(hostKey, null);
     132            String parameters = Config.getPref().get(parametersKey, null);
     133            if (!Utils.isBlank(token) && !Utils.isBlank(parameters) && OAuthVersion.OAuth20 == oauthType) {
     134                try {
     135                    OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(parameters);
     136                    return new OAuth20Token(oAuth20Parameters, token);
     137                } catch (OAuth20Exception | JsonException e) {
     138                    throw new CredentialsAgentException(e);
     139                }
     140            }
     141        }
     142        return null;
    110143    }
    111144
     
    124157            Config.getPref().put("oauth.access-token.key", accessToken.getKey());
    125158            Config.getPref().put("oauth.access-token.secret", accessToken.getSecret());
     159        }
     160    }
     161
     162    @Override
     163    public void storeOAuthAccessToken(String host, IOAuthToken accessToken) throws CredentialsAgentException {
     164        Objects.requireNonNull(host, "host");
     165        if (accessToken == null) {
     166            Set<String> keySet = new HashSet<>(Config.getPref().getKeySet());
     167            keySet.addAll(Config.getPref().getSensitive()); // Just in case we decide to not return sensitive keys in getKeySet
     168            // Assume we want to remove all access tokens
     169            for (OAuthVersion oauthType : OAuthVersion.values()) {
     170                final String hostKey = "oauth.access-token.parameters." + oauthType + "." + host;
     171                final String parametersKey = "oauth.access-token.parameters." + oauthType + "." + host;
     172                if (keySet.contains(hostKey)) {
     173                    Config.getPref().removeSensitive(hostKey);
     174                }
     175                if (keySet.contains(parametersKey)) {
     176                    Config.getPref().removeSensitive(parametersKey);
     177                }
     178            }
     179        } else {
     180            final String hostKey = "oauth.access-token.object." + accessToken.getOAuthType() + "." + host;
     181            final String parametersKey = "oauth.access-token.parameters." + accessToken.getOAuthType() + "." + host;
     182            Config.getPref().put(hostKey, accessToken.toPreferencesString());
     183            Config.getPref().put(parametersKey, accessToken.getParameters().toPreferencesString());
     184            Config.getPref().addSensitive(this, hostKey);
     185            Config.getPref().addSensitive(this, parametersKey);
    126186        }
    127187    }
  • trunk/src/org/openstreetmap/josm/io/remotecontrol/RequestProcessor.java

    r18211 r18650  
    3131import org.openstreetmap.josm.io.remotecontrol.handler.AddNodeHandler;
    3232import org.openstreetmap.josm.io.remotecontrol.handler.AddWayHandler;
     33import org.openstreetmap.josm.io.remotecontrol.handler.AuthorizationHandler;
    3334import org.openstreetmap.josm.io.remotecontrol.handler.FeaturesHandler;
    3435import org.openstreetmap.josm.io.remotecontrol.handler.ImageryHandler;
     
    172173            addRequestHandlerClass(FeaturesHandler.command, FeaturesHandler.class, true);
    173174            addRequestHandlerClass(OpenApiHandler.command, OpenApiHandler.class, true);
     175            addRequestHandlerClass(AuthorizationHandler.command, AuthorizationHandler.class, true);
    174176        }
    175177    }
  • trunk/src/org/openstreetmap/josm/spi/preferences/AbstractPreferences.java

    r18209 r18650  
    22package org.openstreetmap.josm.spi.preferences;
    33
     4import java.util.Arrays;
     5import java.util.Collection;
     6import java.util.Collections;
     7import java.util.HashSet;
    48import java.util.LinkedList;
    59import java.util.List;
    610import java.util.Map;
    711import java.util.Map.Entry;
     12import java.util.Set;
    813import java.util.TreeMap;
    914import java.util.stream.Collectors;
    1015
     16import org.openstreetmap.josm.io.DefaultProxySelector;
     17import org.openstreetmap.josm.io.auth.CredentialsAgent;
     18import org.openstreetmap.josm.io.auth.CredentialsManager;
    1119import org.openstreetmap.josm.tools.Logging;
    1220import org.openstreetmap.josm.tools.Utils;
     
    1725 */
    1826public abstract class AbstractPreferences implements IPreferences {
     27    /** The preference key for sensitive keys */
     28    private static final String KEY_SENSITIVE_KEYS = "sensitive.keys";
     29
     30    /** A set of sensitive keys that should not be seen/distributed outside of specific callers (like a {@link CredentialsAgent}) */
     31    private static final Set<String> SENSITIVE_KEYS = new HashSet<>();
    1932
    2033    @Override
     
    176189                .collect(Collectors.toCollection(LinkedList::new));
    177190    }
     191
     192    @Override
     193    public void addSensitive(CredentialsAgent caller, String key) {
     194        if (SENSITIVE_KEYS.isEmpty()) {
     195            populateSensitiveKeys();
     196        }
     197        if (CredentialsManager.getInstance().getCredentialsAgentClass().equals(caller.getClass())) {
     198            SENSITIVE_KEYS.add(key);
     199            putList("sensitive.keys", SENSITIVE_KEYS.stream().sorted().collect(Collectors.toList()));
     200        }
     201    }
     202
     203    @Override
     204    public Collection<String> getSensitive() {
     205        if (SENSITIVE_KEYS.isEmpty()) {
     206            populateSensitiveKeys();
     207        }
     208        return Collections.unmodifiableSet(SENSITIVE_KEYS);
     209    }
     210
     211    @Override
     212    public void removeSensitive(String key) {
     213        if (KEY_SENSITIVE_KEYS.equals(key)) {
     214            throw new IllegalArgumentException(KEY_SENSITIVE_KEYS + " cannot be removed from the sensitive key list.");
     215        }
     216        // Reset the key first -- avoid race conditions where a sensitive value might be visible if we start restricting access in the future.
     217        put(key, null);
     218        SENSITIVE_KEYS.remove(key);
     219        putList(KEY_SENSITIVE_KEYS, SENSITIVE_KEYS.stream().sorted().collect(Collectors.toList()));
     220    }
     221
     222    /**
     223     * Populate the sensitive key set from preferences
     224     */
     225    private void populateSensitiveKeys() {
     226        SENSITIVE_KEYS.addAll(getList(KEY_SENSITIVE_KEYS, Arrays.asList("sensitive.keys", "osm-server.username", "osm-server.password",
     227                DefaultProxySelector.PROXY_USER, DefaultProxySelector.PROXY_PASS,
     228                "oauth.access-token.key", "oauth.access-token.secret")));
     229    }
    178230}
  • trunk/src/org/openstreetmap/josm/spi/preferences/IPreferences.java

    r12987 r18650  
    22package org.openstreetmap.josm.spi.preferences;
    33
     4import java.util.Collection;
    45import java.util.Collections;
    56import java.util.List;
    67import java.util.Map;
    78import java.util.Set;
     9
     10import org.openstreetmap.josm.io.auth.CredentialsAgent;
    811
    912/**
     
    240243     */
    241244    Set<String> getKeySet();
     245
     246    /**
     247     * Add sensitive keys
     248     * @param caller The calling agent
     249     * @param key The key that may contain sensitive information
     250     * @since 18650
     251     */
     252    void addSensitive(CredentialsAgent caller, String key);
     253
     254    /**
     255     * Get sensitive keys
     256     * @return The sensitive keys
     257     * @since 18650
     258     */
     259    Collection<String> getSensitive();
     260
     261    /**
     262     * Remove sensitive keys. This removes the key from the sensitive list <i>and</i>
     263     * removes the stored preference value.
     264     * @param key The key to remove
     265     * @since 18650
     266     */
     267    void removeSensitive(String key);
    242268}
  • trunk/test/unit/org/openstreetmap/josm/gui/io/DownloadOpenChangesetsTaskTest.java

    r17275 r18650  
    1414import javax.swing.JPanel;
    1515
     16import org.junit.jupiter.api.BeforeEach;
    1617import org.junit.jupiter.api.extension.RegisterExtension;
    1718import org.junit.jupiter.api.Test;
     
    1920import org.openstreetmap.josm.data.UserIdentityManager;
    2021import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
     22import org.openstreetmap.josm.spi.preferences.Config;
    2123import org.openstreetmap.josm.testutils.JOSMTestRules;
    2224import org.openstreetmap.josm.testutils.mockers.JOptionPaneSimpleMocker;
     
    6668            invocation.proceed(serverUrl);
    6769        }
     70    }
     71
     72    /**
     73     * These tests were written with {@link org.openstreetmap.josm.data.oauth.OAuthVersion#OAuth10a} as the default auth method.
     74     */
     75    @BeforeEach
     76    void setup() {
     77        Config.getPref().put("osm-server.auth-method", "oauth");
    6878    }
    6979
Note: See TracChangeset for help on using the changeset viewer.