source: josm/trunk/src/org/openstreetmap/josm/gui/oauth/OsmOAuthAuthorizationClient.java@ 6830

Last change on this file since 6830 was 6643, checked in by Don-vip, 10 years ago

global replacement of e.printStackTrace() by Main.error(e)

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