source: josm/trunk/src/org/openstreetmap/josm/io/OsmConnection.java @ 12928

Last change on this file since 12928 was 12928, checked in by bastiK, 2 months ago

see #15229 - do not copy the entire preferences list, just to set a custom server API in OAuth wizard

  • Property svn:eol-style set to native
File size: 7.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.lang.reflect.InvocationTargetException;
7import java.net.Authenticator.RequestorType;
8import java.net.MalformedURLException;
9import java.net.URL;
10import java.nio.charset.StandardCharsets;
11import java.util.Base64;
12import java.util.Objects;
13
14import org.openstreetmap.josm.Main;
15import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
16import org.openstreetmap.josm.data.oauth.OAuthParameters;
17import org.openstreetmap.josm.io.auth.CredentialsAgentException;
18import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
19import org.openstreetmap.josm.io.auth.CredentialsManager;
20import org.openstreetmap.josm.tools.HttpClient;
21import org.openstreetmap.josm.tools.JosmRuntimeException;
22import org.openstreetmap.josm.tools.Logging;
23
24import oauth.signpost.OAuthConsumer;
25import oauth.signpost.exception.OAuthException;
26
27/**
28 * Base class that handles common things like authentication for the reader and writer
29 * to the osm server.
30 *
31 * @author imi
32 */
33public class OsmConnection {
34    protected boolean cancel;
35    protected HttpClient activeConnection;
36    protected OAuthParameters oauthParameters;
37
38    /**
39     * Retrieves OAuth access token.
40     * @since 12803
41     */
42    public interface OAuthAccessTokenFetcher {
43        /**
44         * Obtains an OAuth access token for the connection. Afterwards, the token is accessible via {@link OAuthAccessTokenHolder}.
45         * @param serverUrl the URL to OSM server
46         * @throws InterruptedException if we're interrupted while waiting for the event dispatching thread to finish OAuth authorization task
47         * @throws InvocationTargetException if an exception is thrown while running OAuth authorization task
48         */
49        void obtainAccessToken(URL serverUrl) throws InvocationTargetException, InterruptedException;
50    }
51
52    static volatile OAuthAccessTokenFetcher fetcher = u -> {
53        throw new JosmRuntimeException("OsmConnection.setOAuthAccessTokenFetcher() has not been called");
54    };
55
56    /**
57     * Sets the OAuth access token fetcher.
58     * @param tokenFetcher new OAuth access token fetcher. Cannot be null
59     * @since 12803
60     */
61    public static void setOAuthAccessTokenFetcher(OAuthAccessTokenFetcher tokenFetcher) {
62        fetcher = Objects.requireNonNull(tokenFetcher, "tokenFetcher");
63    }
64
65    /**
66     * Cancels the connection.
67     */
68    public void cancel() {
69        cancel = true;
70        synchronized (this) {
71            if (activeConnection != null) {
72                activeConnection.disconnect();
73            }
74        }
75    }
76
77    /**
78     * Adds an authentication header for basic authentication
79     *
80     * @param con the connection
81     * @throws OsmTransferException if something went wrong. Check for nested exceptions
82     */
83    protected void addBasicAuthorizationHeader(HttpClient con) throws OsmTransferException {
84        CredentialsAgentResponse response;
85        try {
86            synchronized (CredentialsManager.getInstance()) {
87                response = CredentialsManager.getInstance().getCredentials(RequestorType.SERVER,
88                con.getURL().getHost(), false /* don't know yet whether the credentials will succeed */);
89            }
90        } catch (CredentialsAgentException e) {
91            throw new OsmTransferException(e);
92        }
93        if (response != null) {
94            if (response.isCanceled()) {
95                cancel = true;
96                return;
97            } else {
98                String username = response.getUsername() == null ? "" : response.getUsername();
99                String password = response.getPassword() == null ? "" : String.valueOf(response.getPassword());
100                String token = username + ':' + password;
101                con.setHeader("Authorization", "Basic "+Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8)));
102            }
103        }
104    }
105
106    /**
107     * Signs the connection with an OAuth authentication header
108     *
109     * @param connection the connection
110     *
111     * @throws MissingOAuthAccessTokenException if there is currently no OAuth Access Token configured
112     * @throws OsmTransferException if signing fails
113     */
114    protected void addOAuthAuthorizationHeader(HttpClient connection) throws OsmTransferException {
115        if (oauthParameters == null) {
116            oauthParameters = OAuthParameters.createFromApiUrl(OsmApi.getOsmApi().getServerUrl());
117        }
118        OAuthConsumer consumer = oauthParameters.buildConsumer();
119        OAuthAccessTokenHolder holder = OAuthAccessTokenHolder.getInstance();
120        if (!holder.containsAccessToken()) {
121            obtainAccessToken(connection);
122        }
123        if (!holder.containsAccessToken()) { // check if wizard completed
124            throw new MissingOAuthAccessTokenException();
125        }
126        consumer.setTokenWithSecret(holder.getAccessTokenKey(), holder.getAccessTokenSecret());
127        try {
128            consumer.sign(connection);
129        } catch (OAuthException e) {
130            throw new OsmTransferException(tr("Failed to sign a HTTP connection with an OAuth Authentication header"), e);
131        }
132    }
133
134    /**
135     * Obtains an OAuth access token for the connection.
136     * Afterwards, the token is accessible via {@link OAuthAccessTokenHolder} / {@link CredentialsManager}.
137     * @param connection connection for which the access token should be obtained
138     * @throws MissingOAuthAccessTokenException if the process cannot be completed successfully
139     */
140    protected void obtainAccessToken(final HttpClient connection) throws MissingOAuthAccessTokenException {
141        try {
142            final URL apiUrl = new URL(OsmApi.getOsmApi().getServerUrl());
143            if (!Objects.equals(apiUrl.getHost(), connection.getURL().getHost())) {
144                throw new MissingOAuthAccessTokenException();
145            }
146            fetcher.obtainAccessToken(apiUrl);
147            OAuthAccessTokenHolder.getInstance().setSaveToPreferences(true);
148            OAuthAccessTokenHolder.getInstance().save(CredentialsManager.getInstance());
149        } catch (MalformedURLException | InterruptedException | InvocationTargetException e) {
150            throw new MissingOAuthAccessTokenException(e);
151        }
152    }
153
154    protected void addAuth(HttpClient connection) throws OsmTransferException {
155        final String authMethod = OsmApi.getAuthMethod();
156        if ("basic".equals(authMethod)) {
157            addBasicAuthorizationHeader(connection);
158        } else if ("oauth".equals(authMethod)) {
159            addOAuthAuthorizationHeader(connection);
160        } else {
161            String msg = tr("Unexpected value for preference ''{0}''. Got ''{1}''.", "osm-server.auth-method", authMethod);
162            Logging.warn(msg);
163            throw new OsmTransferException(msg);
164        }
165    }
166
167    /**
168     * Replies true if this connection is canceled
169     *
170     * @return true if this connection is canceled
171     */
172    public boolean isCanceled() {
173        return cancel;
174    }
175}
Note: See TracBrowser for help on using the repository browser.