source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorisationClient.java@ 2801

Last change on this file since 2801 was 2801, checked in by stoecker, 14 years ago

fixed line endings of recent checkins

File size: 21.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.oauth;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.io.DataOutputStream;
7import java.io.IOException;
8import java.io.UnsupportedEncodingException;
9import java.lang.reflect.Field;
10import java.net.HttpURLConnection;
11import java.net.MalformedURLException;
12import java.net.URL;
13import java.net.URLEncoder;
14import java.util.HashMap;
15import java.util.Iterator;
16import java.util.List;
17import java.util.Map;
18import java.util.Map.Entry;
19import java.util.logging.Logger;
20
21import oauth.signpost.OAuth;
22import oauth.signpost.OAuthConsumer;
23import oauth.signpost.OAuthProvider;
24import oauth.signpost.basic.DefaultOAuthProvider;
25import oauth.signpost.exception.OAuthCommunicationException;
26import oauth.signpost.exception.OAuthException;
27
28import org.openstreetmap.josm.data.Version;
29import org.openstreetmap.josm.data.oauth.OAuthParameters;
30import org.openstreetmap.josm.data.oauth.OAuthToken;
31import org.openstreetmap.josm.data.oauth.OsmPrivileges;
32import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
33import org.openstreetmap.josm.gui.progress.ProgressMonitor;
34import org.openstreetmap.josm.io.OsmTransferCancelledException;
35import org.openstreetmap.josm.tools.CheckParameterUtil;
36
37public class OsmOAuthAuthorisationClient {
38 static private final Logger logger = Logger.getLogger(OsmOAuthAuthorisationClient.class.getName());
39
40 private OAuthParameters oauthProviderParameters;
41 private OAuthConsumer consumer;
42 private OAuthProvider provider;
43 private boolean canceled;
44 private HttpURLConnection connection;
45
46 /**
47 * Creates a new authorisation client with default OAuth parameters
48 *
49 */
50 public OsmOAuthAuthorisationClient() {
51 oauthProviderParameters = OAuthParameters.createDefault();
52 consumer = oauthProviderParameters.buildConsumer();
53 provider = oauthProviderParameters.buildProvider(consumer);
54 }
55
56 /**
57 * Creates a new authorisation client with the parameters <code>parameters</code>.
58 *
59 * @param parameters the OAuth parameters. Must not be null.
60 * @throws IllegalArgumentException thrown if parameters is null
61 */
62 public OsmOAuthAuthorisationClient(OAuthParameters parameters) throws IllegalArgumentException {
63 CheckParameterUtil.ensureParameterNotNull(parameters, "parameters");
64 oauthProviderParameters = new OAuthParameters(parameters);
65 consumer = oauthProviderParameters.buildConsumer();
66 provider = oauthProviderParameters.buildProvider(consumer);
67 }
68
69 /**
70 * Creates a new authorisation client with the parameters <code>parameters</code>
71 * and an already known Request Token.
72 *
73 * @param parameters the OAuth parameters. Must not be null.
74 * @param requestToken the request token. Must not be null.
75 * @throws IllegalArgumentException thrown if parameters is null
76 * @throws IllegalArgumentException thrown if requestToken is null
77 */
78 public OsmOAuthAuthorisationClient(OAuthParameters parameters, OAuthToken requestToken) throws IllegalArgumentException {
79 CheckParameterUtil.ensureParameterNotNull(parameters, "parameters");
80 oauthProviderParameters = new OAuthParameters(parameters);
81 consumer = oauthProviderParameters.buildConsumer();
82 provider = oauthProviderParameters.buildProvider(consumer);
83 consumer.setTokenWithSecret(requestToken.getKey(), requestToken.getSecret());
84 }
85
86 public void cancel() {
87 DefaultOAuthProvider p = (DefaultOAuthProvider)provider;
88 canceled = true;
89 if (p != null) {
90 try {
91 Field f = p.getClass().getDeclaredField("connection");
92 f.setAccessible(true);
93 HttpURLConnection con = (HttpURLConnection)f.get(p);
94 if (con != null) {
95 con.disconnect();
96 }
97 } catch(NoSuchFieldException e) {
98 e.printStackTrace();
99 System.err.println(tr("Warning: failed to cancel running OAuth operation"));
100 } catch(SecurityException e) {
101 e.printStackTrace();
102 System.err.println(tr("Warning: failed to cancel running OAuth operation"));
103 } catch(IllegalAccessException e) {
104 e.printStackTrace();
105 System.err.println(tr("Warning: failed to cancel running OAuth operation"));
106 }
107 }
108 synchronized(this) {
109 if (connection != null) {
110 connection.disconnect();
111 }
112 }
113 }
114
115 /**
116 * Submits a request for a Request Token to the Request Token Endpoint Url of the OAuth Service
117 * Provider and replies the request token.
118 *
119 * @param monitor a progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null
120 * @return the OAuth Request Token
121 * @throws OsmOAuthAuthorisationException thrown if something goes wrong when retrieving the request token
122 */
123 public OAuthToken getRequestToken(ProgressMonitor monitor) throws OsmOAuthAuthorisationException, OsmTransferCancelledException {
124 if (monitor == null) {
125 monitor = NullProgressMonitor.INSTANCE;
126 }
127 try {
128 monitor.beginTask("");
129 monitor.indeterminateSubTask(tr("Retrieving OAuth Request Token from ''{0}''", oauthProviderParameters.getRequestTokenUrl()));
130 provider.retrieveRequestToken(null);
131 return OAuthToken.createToken(consumer);
132 } catch(OAuthCommunicationException e){
133 if (canceled)
134 throw new OsmTransferCancelledException();
135 throw new OsmOAuthAuthorisationException(e);
136 } catch(OAuthException e){
137 if (canceled)
138 throw new OsmTransferCancelledException();
139 throw new OsmOAuthAuthorisationException(e);
140 } finally {
141 monitor.finishTask();
142 }
143 }
144
145 /**
146 * Submits a request for an Access Token to the Access Token Endpoint Url of the OAuth Service
147 * Provider and replies the request token.
148 *
149 * You must have requested a Request Token using {@see #getRequestToken(ProgressMonitor)} first.
150 *
151 * @param monitor a progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null
152 * @return the OAuth Access Token
153 * @throws OsmOAuthAuthorisationException thrown if something goes wrong when retrieving the request token
154 * @see #getRequestToken(ProgressMonitor)
155 */
156 public OAuthToken getAccessToken(ProgressMonitor monitor) throws OsmOAuthAuthorisationException, OsmTransferCancelledException {
157 if (monitor == null) {
158 monitor = NullProgressMonitor.INSTANCE;
159 }
160 try {
161 monitor.beginTask("");
162 monitor.indeterminateSubTask(tr("Retrieving OAuth Access Token from ''{0}''", oauthProviderParameters.getAccessTokenUrl()));
163 provider.retrieveAccessToken(null);
164 return OAuthToken.createToken(consumer);
165 } catch(OAuthCommunicationException e){
166 if (canceled)
167 throw new OsmTransferCancelledException();
168 throw new OsmOAuthAuthorisationException(e);
169 } catch(OAuthException e){
170 if (canceled)
171 throw new OsmTransferCancelledException();
172 throw new OsmOAuthAuthorisationException(e);
173 } finally {
174 monitor.finishTask();
175 }
176 }
177
178 /**
179 * Builds the authorise URL for a given Request Token. Users can be redirected to this URL.
180 * There they can login to OSM and authorise the request.
181 *
182 * @param requestToken the request token
183 * @return the authorise URL for this request
184 */
185 public String getAuthoriseUrl(OAuthToken requestToken) {
186 StringBuilder sb = new StringBuilder();
187
188 // OSM is an OAuth 1.0 provider and JOSM isn't a web app. We just add the oauth request token to
189 // the authorisation request, no callback parameter.
190 //
191 sb.append(oauthProviderParameters.getAuthoriseUrl()).append("?")
192 .append(OAuth.OAUTH_TOKEN).append("=").append(requestToken.getKey());
193 return sb.toString();
194 }
195
196 protected String extractOsmSession(HttpURLConnection connection) {
197 List<String> setCookies = connection.getHeaderFields().get("Set-Cookie");
198 if (setCookies == null)
199 // no cookies set
200 return null;
201
202 for (String setCookie: setCookies) {
203 String[] kvPairs = setCookie.split(";");
204 if (kvPairs == null || kvPairs.length == 0) {
205 continue;
206 }
207 for (String kvPair : kvPairs) {
208 kvPair = kvPair.trim();
209 String [] kv = kvPair.split("=");
210 if (kv == null || kv.length != 2) {
211 continue;
212 }
213 if (kv[0].equals("_osm_session"))
214 // osm session cookie found
215 return kv[1];
216 }
217 }
218 return null;
219 }
220
221 protected String buildPostRequest(Map<String,String> parameters) throws OsmOAuthAuthorisationException {
222 try {
223 StringBuilder sb = new StringBuilder();
224
225 for(Iterator<Entry<String,String>> it = parameters.entrySet().iterator(); it.hasNext();) {
226 Entry<String,String> entry = it.next();
227 String value = entry.getValue();
228 value = (value == null) ? "" : value;
229 sb.append(entry.getKey()).append("=").append(URLEncoder.encode(value, "UTF-8"));
230 if (it.hasNext()) {
231 sb.append("&");
232 }
233 }
234 return sb.toString();
235 } catch(UnsupportedEncodingException e) {
236 throw new OsmOAuthAuthorisationException(e);
237 }
238 }
239
240 /**
241 * Derives the OSM login URL from the OAuth Authorization Website URL
242 *
243 * @return the OSM login URL
244 * @throws OsmOAuthAuthorisationException thrown if something went wrong, in particular if the
245 * URLs are malformed
246 */
247 public String buildOsmLoginUrl() throws OsmOAuthAuthorisationException{
248 try {
249 URL autUrl = new URL(oauthProviderParameters.getAuthoriseUrl());
250 // FIXME: as soon as the OSM website provides HTTPS protected access to the login
251 // page we can replace the protocol with https
252 //
253 URL url = new URL("http", autUrl.getHost(), autUrl.getPort(), "/login");
254 return url.toString();
255 } catch(MalformedURLException e) {
256 throw new OsmOAuthAuthorisationException(e);
257 }
258 }
259
260 /**
261 * Derives the OSM logout URL from the OAuth Authorization Website URL
262 *
263 * @return the OSM logout URL
264 * @throws OsmOAuthAuthorisationException thrown if something went wrong, in particular if the
265 * URLs are malformed
266 */
267 protected String buildOsmLogoutUrl() throws OsmOAuthAuthorisationException{
268 try {
269 URL autUrl = new URL(oauthProviderParameters.getAuthoriseUrl());
270 URL url = new URL("http", autUrl.getHost(), autUrl.getPort(), "/logout");
271 return url.toString();
272 } catch(MalformedURLException e) {
273 throw new OsmOAuthAuthorisationException(e);
274 }
275 }
276
277 /**
278 * Submits a request to the OSM website for a login form. The OSM website replies a session ID in
279 * a cookie.
280 *
281 * @return the session ID
282 * @throws OsmOAuthAuthorisationException thrown if something went wrong
283 */
284 protected String fetchOsmWebsiteSessionId() throws OsmOAuthAuthorisationException {
285 try {
286 StringBuilder sb = new StringBuilder();
287 sb.append(buildOsmLoginUrl()).append("?cookie_test=true");
288 URL url = new URL(sb.toString());
289 synchronized(this) {
290 connection = (HttpURLConnection)url.openConnection();
291 }
292 connection.setRequestMethod("GET");
293 connection.setDoInput(true);
294 connection.setDoOutput(false);
295 setHttpRequestParameters(connection);
296 connection.connect();
297 String sessionId = extractOsmSession(connection);
298 if (sessionId == null)
299 throw new OsmOAuthAuthorisationException(tr("OSM website didn''t reply a session cookie in response to ''{0}'',", url.toString()));
300 return sessionId;
301 } catch(IOException e) {
302 throw new OsmOAuthAuthorisationException(e);
303 } finally {
304 synchronized(this) {
305 connection = null;
306 }
307 }
308 }
309
310 protected void authenticateOsmSession(String sessionId, String userName, String password) throws OsmLoginFailedException {
311 DataOutputStream dout = null;
312 try {
313 URL url = new URL(buildOsmLoginUrl());
314 synchronized(this) {
315 connection = (HttpURLConnection)url.openConnection();
316 }
317 connection.setRequestMethod("POST");
318 connection.setDoInput(true);
319 connection.setDoOutput(true);
320 connection.setUseCaches(false);
321
322 Map<String,String> parameters = new HashMap<String, String>();
323 parameters.put("user[email]", userName);
324 parameters.put("user[password]", password);
325 parameters.put("referer", "/");
326 parameters.put("commit", "Login");
327
328 String request = buildPostRequest(parameters);
329
330 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
331 connection.setRequestProperty("Content-Length", Integer.toString(request.length()));
332 connection.setRequestProperty("Cookie", "_osm_session=" + sessionId);
333 // make sure we can catch 302 Moved Temporarily below
334 connection.setInstanceFollowRedirects(false);
335 setHttpRequestParameters(connection);
336
337 connection.connect();
338
339 dout = new DataOutputStream(connection.getOutputStream());
340 dout.writeBytes(request);
341 dout.flush();
342 dout.close();
343
344 // after a successful login the OSM website sends a redirect to a follow up page. Everything
345 // else, including a 200 OK, is a failed login. A 200 OK is replied if the login form with
346 // an error page is sent to back to the user.
347 //
348 int retCode = connection.getResponseCode();
349 if (retCode != HttpURLConnection.HTTP_MOVED_TEMP)
350 throw new OsmOAuthAuthorisationException(tr("Failed to authenticate user ''{0}'' with password ''***'' as OAuth user", userName));
351 } catch(OsmOAuthAuthorisationException e) {
352 throw new OsmLoginFailedException(e.getCause());
353 } catch(IOException e) {
354 throw new OsmLoginFailedException(e);
355 } finally {
356 if (dout != null) {
357 try {
358 dout.close();
359 } catch(IOException e) { /* ignore */ }
360 }
361 synchronized(this) {
362 connection = null;
363 }
364 }
365 }
366
367 protected void logoutOsmSession(String sessionId) throws OsmOAuthAuthorisationException {
368 try {
369 URL url = new URL(buildOsmLogoutUrl());
370 synchronized(this) {
371 connection = (HttpURLConnection)url.openConnection();
372 }
373 connection.setRequestMethod("GET");
374 connection.setDoInput(true);
375 connection.setDoOutput(false);
376 setHttpRequestParameters(connection);
377 connection.connect();
378 }catch(MalformedURLException e) {
379 throw new OsmOAuthAuthorisationException(e);
380 } catch(IOException e) {
381 throw new OsmOAuthAuthorisationException(e);
382 } finally {
383 synchronized(this) {
384 connection = null;
385 }
386 }
387 }
388
389 protected void sendAuthorisationRequest(String sessionId, OAuthToken requestToken, OsmPrivileges privileges) throws OsmOAuthAuthorisationException {
390 Map<String, String> parameters = new HashMap<String, String>();
391 parameters.put("oauth_token", requestToken.getKey());
392 parameters.put("oauth_callback", "");
393 if (privileges.isAllowWriteApi()) {
394 parameters.put("allow_write_api", "yes");
395 }
396 if (privileges.isAllowWriteGpx()) {
397 parameters.put("allow_write_gpx", "yes");
398 }
399 if (privileges.isAllowReadGpx()) {
400 parameters.put("allow_read_gpx", "yes");
401 }
402 if (privileges.isAllowWritePrefs()) {
403 parameters.put("allow_write_prefs", "yes");
404 }
405 if (privileges.isAllowReadPrefs()) {
406 parameters.put("allow_read_prefs", "yes");
407 }
408
409 parameters.put("commit", "Save changes");
410
411 String request = buildPostRequest(parameters);
412 DataOutputStream dout = null;
413 try {
414 URL url = new URL(oauthProviderParameters.getAuthoriseUrl());
415 synchronized(this) {
416 connection = (HttpURLConnection)url.openConnection();
417 }
418 connection.setRequestMethod("POST");
419 connection.setDoInput(true);
420 connection.setDoOutput(true);
421 connection.setUseCaches(false);
422 connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
423 connection.setRequestProperty("Content-Length", Integer.toString(request.length()));
424 connection.setRequestProperty("Cookie", "_osm_session=" + sessionId);
425 connection.setInstanceFollowRedirects(false);
426 setHttpRequestParameters(connection);
427
428 connection.connect();
429
430 dout = new DataOutputStream(connection.getOutputStream());
431 dout.writeBytes(request);
432 dout.flush();
433 dout.close();
434
435 int retCode = connection.getResponseCode();
436 if (retCode != HttpURLConnection.HTTP_MOVED_TEMP)
437 throw new OsmOAuthAuthorisationException(tr("Failed to autorise OAuth request ''{0}''", requestToken.getKey()));
438 } catch(MalformedURLException e) {
439 throw new OsmOAuthAuthorisationException(e);
440 } catch(IOException e) {
441 throw new OsmOAuthAuthorisationException(e);
442 } finally {
443 if (dout != null) {
444 try {
445 dout.close();
446 } catch(IOException e) { /* ignore */ }
447 }
448 synchronized(this) {
449 connection = null;
450 }
451 }
452 }
453
454 protected void setHttpRequestParameters(HttpURLConnection connection) {
455 connection.setRequestProperty("User-Agent", Version.getInstance().getAgentString());
456 connection.setRequestProperty("Host", connection.getURL().getHost());
457 }
458
459 /**
460 * Automatically authorises a request token for a set of privileges.
461 *
462 * @param requestToken the request token. Must not be null.
463 * @param osmUserName the OSM user name. Must not be null.
464 * @param osmPassword the OSM password. Must not be null.
465 * @param privileges the set of privileges. Must not be null.
466 * @param monitor a progress monitor. Defaults to {@see NullProgressMonitor#INSTANCE} if null
467 * @throws IllegalArgumentException thrown if requestToken is null
468 * @throws IllegalArgumentException thrown if osmUserName is null
469 * @throws IllegalArgumentException thrown if osmPassword is null
470 * @throws IllegalArgumentException thrown if privileges is null
471 * @throws OsmOAuthAuthorisationException thrown if the authorisation fails
472 * @throws OsmTransferCancelledException thrown if the task is cancelled by the user
473 */
474 public void authorise(OAuthToken requestToken, String osmUserName, String osmPassword, OsmPrivileges privileges, ProgressMonitor monitor) throws IllegalArgumentException, OsmOAuthAuthorisationException, OsmTransferCancelledException{
475 CheckParameterUtil.ensureParameterNotNull(requestToken, "requestToken");
476 CheckParameterUtil.ensureParameterNotNull(osmUserName, "osmUserName");
477 CheckParameterUtil.ensureParameterNotNull(osmPassword, "osmPassword");
478 CheckParameterUtil.ensureParameterNotNull(privileges, "privileges");
479
480 if (monitor == null) {
481 monitor = NullProgressMonitor.INSTANCE;
482 }
483 try {
484 monitor.beginTask(tr("Authorising OAuth Request token ''{0}'' at the OSM website ...", requestToken.getKey()));
485 monitor.setTicksCount(4);
486 monitor.indeterminateSubTask(tr("Initializing a session at the OSM website..."));
487 String sessionId = fetchOsmWebsiteSessionId();
488 if (canceled)
489 throw new OsmTransferCancelledException();
490 monitor.worked(1);
491
492 monitor.indeterminateSubTask(tr("Authenticating the session for user ''{0}''...", osmUserName));
493 authenticateOsmSession(sessionId, osmUserName, osmPassword);
494 if (canceled)
495 throw new OsmTransferCancelledException();
496 monitor.worked(1);
497
498 monitor.indeterminateSubTask(tr("Authorising request token ''{0}''...", requestToken.getKey()));
499 sendAuthorisationRequest(sessionId, requestToken, privileges);
500 if (canceled)
501 throw new OsmTransferCancelledException();
502 monitor.worked(1);
503
504 monitor.indeterminateSubTask(tr("Logging out session ''{0}''...", sessionId));
505 logoutOsmSession(sessionId);
506 if (canceled)
507 throw new OsmTransferCancelledException();
508 monitor.worked(1);
509 } catch(OsmOAuthAuthorisationException e) {
510 if (canceled)
511 throw new OsmTransferCancelledException();
512 throw e;
513 } finally {
514 monitor.finishTask();
515 }
516 }
517}
Note: See TracBrowser for help on using the repository browser.