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

Last change on this file since 12929 was 12928, checked in by bastiK, 7 years 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.