Ignore:
Timestamp:
2023-02-08T18:32:01+01:00 (21 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:
applications/editors/josm/plugins/native-password-manager
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/native-password-manager/build.xml

    r34472 r36049  
    55    <property name="commit.message" value="Commit message"/>
    66    <!-- enter the *lowest* JOSM version this plugin is currently compatible with -->
    7     <property name="plugin.main.version" value="14153"/>
     7    <property name="plugin.main.version" value="18650"/>
    88    <property name="plugin.author" value="Paul Hartmann"/>
    99    <property name="plugin.class" value="org.openstreetmap.josm.plugins.npm.NPMPlugin"/>
  • applications/editors/josm/plugins/native-password-manager/src/org/openstreetmap/josm/plugins/npm/NPMCredentialsAgent.java

    r35665 r36049  
    77import java.net.Authenticator.RequestorType;
    88import java.net.PasswordAuthentication;
     9import java.net.URI;
    910import java.nio.charset.StandardCharsets;
    1011import java.util.ArrayList;
    11 import java.util.HashMap;
     12import java.util.Arrays;
     13import java.util.EnumMap;
    1214import java.util.List;
    1315import java.util.Map;
     16import java.util.stream.Collectors;
    1417import java.util.zip.CRC32;
    1518
     
    1821import org.netbeans.spi.keyring.KeyringProvider;
    1922import org.openstreetmap.josm.data.Preferences;
     23import org.openstreetmap.josm.data.oauth.IOAuthToken;
     24import org.openstreetmap.josm.data.oauth.OAuth20Exception;
     25import org.openstreetmap.josm.data.oauth.OAuth20Parameters;
     26import org.openstreetmap.josm.data.oauth.OAuth20Token;
    2027import org.openstreetmap.josm.data.oauth.OAuthToken;
     28import org.openstreetmap.josm.data.oauth.OAuthVersion;
    2129import org.openstreetmap.josm.gui.widgets.HtmlPanel;
    2230import org.openstreetmap.josm.io.DefaultProxySelector;
     
    2634import org.openstreetmap.josm.spi.preferences.Config;
    2735
     36/**
     37 * The native password manager credentials agent
     38 */
    2839public class NPMCredentialsAgent extends AbstractCredentialsAgent {
    2940
    3041    private KeyringProvider provider;
    31     private NPMType type;
     42    private final NPMType type;
    3243   
    3344    /**
    34      * Cache the results since there might be pop ups and password prompts from
     45     * Cache the results since there might be pop-ups and password prompts from
    3546     * the native manager. This can get annoying, if it shows too often.
    36      *
     47     * <br>
    3748     * Yes, there is another cache in AbstractCredentialsAgent. It is used
    3849     * to avoid prompting the user for login multiple times in one session,
     
    4051     * In contrast, this cache avoids read request the backend in general.
    4152     */
    42     private Map<RequestorType, PasswordAuthentication> credentialsCache = new HashMap<>();
     53    private final Map<RequestorType, PasswordAuthentication> credentialsCache = new EnumMap<>(RequestorType.class);
    4354    private OAuthToken oauthCache;
    44    
     55
     56    /**
     57     * Create a new {@link NPMCredentialsAgent}
     58     * @param type The backend storage type
     59     */
    4560    public NPMCredentialsAgent(NPMType type) {
    4661        this.type = type;
     
    96111   
    97112    @Override
    98     public PasswordAuthentication lookup(RequestorType rt, String host) throws CredentialsAgentException {
     113    public PasswordAuthentication lookup(RequestorType rt, String host) {
    99114        PasswordAuthentication cache = credentialsCache.get(rt);
    100115        if (cache != null)
     
    126141
    127142    @Override
    128     public void store(RequestorType rt, String host, PasswordAuthentication credentials) throws CredentialsAgentException {
     143    public void store(RequestorType rt, String host, PasswordAuthentication credentials) {
    129144        char[] username, password;
    130145        if (credentials == null) {
     
    170185                getProvider().save(prefix+".password", password, passwordDescription);
    171186            }
    172             credentialsCache.put(rt, new PasswordAuthentication(stringNotNull(username), password));
    173         }
    174     }
    175 
    176     @Override
    177     public OAuthToken lookupOAuthAccessToken() throws CredentialsAgentException {
     187            credentialsCache.put(rt, new PasswordAuthentication(stringNotNull(username), password != null ? password : new char[0]));
     188        }
     189    }
     190
     191    @Override
     192    public OAuthToken lookupOAuthAccessToken() {
    178193        if (oauthCache != null)
    179194            return oauthCache;
     
    185200
    186201    @Override
    187     public void storeOAuthAccessToken(OAuthToken oat) throws CredentialsAgentException {
     202    public IOAuthToken lookupOAuthAccessToken(String host) throws CredentialsAgentException {
     203        String prolog = getOAuthDescriptor();
     204        OAuthVersion[] versions = OAuthVersion.values();
     205        // Prefer newer OAuth protocols
     206        for (int i = versions.length - 1; i >= 0; i--) {
     207            OAuthVersion version = versions[i];
     208            char[] tokenObject = getProvider().read(prolog + ".object." + version + "." + host);
     209            char[] parametersObject = getProvider().read(prolog + ".parameters." + version + "." + host);
     210            if (version == OAuthVersion.OAuth20 // There is currently only an OAuth 2.0 path
     211                    && tokenObject != null && tokenObject.length > 0
     212                    && parametersObject != null && parametersObject.length > 0) {
     213                OAuth20Parameters oAuth20Parameters = new OAuth20Parameters(stringNotNull(parametersObject));
     214                try {
     215                    return new OAuth20Token(oAuth20Parameters, stringNotNull(tokenObject));
     216                } catch (OAuth20Exception e) {
     217                    throw new CredentialsAgentException(e);
     218                }
     219            }
     220        }
     221        return null;
     222    }
     223
     224    @Override
     225    public void storeOAuthAccessToken(OAuthToken oat) {
    188226        String key, secret;
    189227        if (oat == null) {
    190228            key = null;
    191229            secret = null;
    192         }
    193         else {
     230        } else {
    194231            key = oat.getKey();
    195232            secret = oat.getSecret();
     
    207244    }
    208245
     246    @Override
     247    public void storeOAuthAccessToken(String host, IOAuthToken accessToken) {
     248        String prolog = getOAuthDescriptor();
     249        if (accessToken == null) {
     250            // Assume all oauth tokens must be removed
     251            for (OAuthVersion version : OAuthVersion.values()) {
     252                getProvider().delete(prolog + ".object." + version + "." + host);
     253                getProvider().delete(prolog + ".parameters." + version + "." + host);
     254            }
     255        } else {
     256            OAuthVersion oauthType = accessToken.getOAuthType();
     257            getProvider().save(prolog + ".object." + oauthType + "." + host,
     258                    accessToken.toPreferencesString().toCharArray(),
     259                    tr("JOSM/OAuth/{0}/Token", URI.create(host).getHost()));
     260            getProvider().save(prolog + ".parameters." + oauthType + "." + host,
     261                    accessToken.getParameters().toPreferencesString().toCharArray(),
     262                    tr("JOSM/OAuth/{0}/Parameters", URI.create(host).getHost()));
     263        }
     264    }
     265
    209266    private static String stringNotNull(char[] charData) {
    210267        if (charData == null)
     
    217274        HtmlPanel pnlMessage = new HtmlPanel();
    218275        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;}");
     276        kit.getStyleSheet().addRule(".warning-body {background-color:rgb(253,255,221);padding: 10pt;" +
     277                "border-color:rgb(128,128,128);border-style: solid;border-width: 1px;}");
    220278        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         );
     279        text.append("<html><body><p class=\"warning-body\"><strong>")
     280                .append(tr("Native Password Manager Plugin"))
     281                .append("</strong><br>")
     282                .append(tr("The username and password is protected by {0}.", type.getName()));
    226283        List<String> sensitive = new ArrayList<>();
    227284        if (Config.getPref().get("osm-server.username", null) != null) {
     
    243300            sensitive.add(tr("oauth secret"));
    244301        }
     302        List<String> otherKeys = Config.getPref().getSensitive().stream().sorted().collect(Collectors.toList());
     303        // Remove keys for which we have specific translated texts
     304        otherKeys.removeAll(Arrays.asList("osm-server.username", "osm-server.password",
     305                DefaultProxySelector.PROXY_USER, DefaultProxySelector.PROXY_PASS,
     306                "oauth.access-token.key", "oauth.access-token.secret"));
     307        sensitive.addAll(otherKeys);
    245308        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)));
     309            text.append(tr("<br><strong>Warning:</strong> There may be sensitive data left in your preference file. ({0})",
     310                    String.join(", ", sensitive)));
    247311        }
    248312        pnlMessage.setText(text.toString());
Note: See TracChangeset for help on using the changeset viewer.