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

Last change on this file since 12931 was 12931, checked in by Don-vip, 2 weeks ago

see #14602 - Override digit group separator to be consistent across languages with ISO 80000-1 + checkstyle fixes

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