This project has retired. For details please refer to its
Attic page.
OAuthAuthenticationProvider xref
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.chemistry.opencmis.client.bindings.spi;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.InputStreamReader;
24 import java.io.OutputStreamWriter;
25 import java.io.Reader;
26 import java.io.Serializable;
27 import java.io.Writer;
28 import java.net.HttpURLConnection;
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
35 import java.util.Map;
36 import java.util.concurrent.locks.ReentrantReadWriteLock;
37
38 import org.apache.chemistry.opencmis.client.bindings.impl.ClientVersion;
39 import org.apache.chemistry.opencmis.commons.SessionParameter;
40 import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
41 import org.apache.chemistry.opencmis.commons.impl.IOUtils;
42 import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
43 import org.apache.chemistry.opencmis.commons.impl.json.JSONObject;
44 import org.apache.chemistry.opencmis.commons.impl.json.parser.JSONParser;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154 public class OAuthAuthenticationProvider extends StandardAuthenticationProvider {
155
156 private static final Logger LOG = LoggerFactory.getLogger(OAuthAuthenticationProvider.class);
157 private static final String TOKEN_TYPE_BEARER = "bearer";
158
159 private static final long serialVersionUID = 1L;
160
161 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
162
163 private Token token = null;
164 private long defaultTokenLifetime = 3600;
165 private List<TokenListener> tokenListeners;
166
167 @Override
168 public void setSession(BindingSession session) {
169 super.setSession(session);
170
171 if (token == null) {
172
173 String accessToken = null;
174 if (session.get(SessionParameter.OAUTH_ACCESS_TOKEN) instanceof String) {
175 accessToken = (String) session.get(SessionParameter.OAUTH_ACCESS_TOKEN);
176 }
177
178
179 String refreshToken = null;
180 if (session.get(SessionParameter.OAUTH_REFRESH_TOKEN) instanceof String) {
181 refreshToken = (String) session.get(SessionParameter.OAUTH_REFRESH_TOKEN);
182 }
183
184
185 long expirationTimestamp = 0;
186 if (session.get(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP) instanceof String) {
187 try {
188 expirationTimestamp = Long.parseLong((String) session
189 .get(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP));
190 } catch (NumberFormatException nfe) {
191
192 }
193 } else if (session.get(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP) instanceof Number) {
194 expirationTimestamp = ((Number) session.get(SessionParameter.OAUTH_EXPIRATION_TIMESTAMP)).longValue();
195 }
196
197
198 if (session.get(SessionParameter.OAUTH_DEFAULT_TOKEN_LIFETIME) instanceof String) {
199 try {
200 defaultTokenLifetime = Long.parseLong((String) session
201 .get(SessionParameter.OAUTH_DEFAULT_TOKEN_LIFETIME));
202 } catch (NumberFormatException nfe) {
203
204 }
205 } else if (session.get(SessionParameter.OAUTH_DEFAULT_TOKEN_LIFETIME) instanceof Number) {
206 defaultTokenLifetime = ((Number) session.get(SessionParameter.OAUTH_DEFAULT_TOKEN_LIFETIME))
207 .longValue();
208 }
209
210 token = new Token(accessToken, refreshToken, expirationTimestamp);
211 fireTokenListner(token);
212 }
213 }
214
215 @Override
216 public Map<String, List<String>> getHTTPHeaders(String url) {
217 Map<String, List<String>> headers = super.getHTTPHeaders(url);
218 if (headers == null) {
219 headers = new HashMap<String, List<String>>();
220 }
221
222 headers.put("Authorization", Collections.singletonList("Bearer " + getAccessToken()));
223
224 return headers;
225 }
226
227
228
229
230
231
232 public Token getToken() {
233 lock.readLock().lock();
234 try {
235 return token;
236 } finally {
237 lock.readLock().unlock();
238 }
239 }
240
241
242
243
244
245
246
247 public void addTokenListener(TokenListener listner) {
248 if (listner == null) {
249 return;
250 }
251
252 lock.writeLock().lock();
253 try {
254 if (tokenListeners == null) {
255 tokenListeners = new ArrayList<OAuthAuthenticationProvider.TokenListener>();
256 }
257
258 tokenListeners.add(listner);
259 } finally {
260 lock.writeLock().unlock();
261 }
262 }
263
264
265
266
267
268
269
270 public void removeTokenListener(TokenListener listner) {
271 if (listner == null) {
272 return;
273 }
274
275 lock.writeLock().lock();
276 try {
277 if (tokenListeners != null) {
278 tokenListeners.remove(listner);
279 }
280 } finally {
281 lock.writeLock().unlock();
282 }
283 }
284
285
286
287
288 protected void fireTokenListner(Token token) {
289 if (tokenListeners == null) {
290 return;
291 }
292
293 for (TokenListener listner : tokenListeners) {
294 listner.tokenRefreshed(token);
295 }
296 }
297
298 @Override
299 protected boolean getSendBearerToken() {
300
301 return false;
302 }
303
304
305
306
307
308
309
310 protected String getAccessToken() {
311 lock.writeLock().lock();
312 try {
313 if (token.getAccessToken() == null) {
314 if (token.getRefreshToken() == null) {
315 requestToken();
316 } else {
317 refreshToken();
318 }
319 } else if (token.isExpired()) {
320 refreshToken();
321 }
322
323 return token.getAccessToken();
324 } catch (CmisConnectionException ce) {
325 throw ce;
326 } catch (Exception e) {
327 throw new CmisConnectionException("Cannot get OAuth access token: " + e.getMessage(), e);
328 } finally {
329 lock.writeLock().unlock();
330 }
331 }
332
333 private void requestToken() throws IOException {
334 if (LOG.isDebugEnabled()) {
335 LOG.debug("Requesting new OAuth access token.");
336 }
337
338 makeRequest(false);
339
340 if (LOG.isTraceEnabled()) {
341 LOG.trace(token.toString());
342 }
343 }
344
345 private void refreshToken() throws IOException {
346 if (LOG.isDebugEnabled()) {
347 LOG.debug("Refreshing OAuth access token.");
348 }
349
350 makeRequest(true);
351
352 if (LOG.isTraceEnabled()) {
353 LOG.trace(token.toString());
354 }
355 }
356
357 private void makeRequest(boolean isRefresh) throws IOException {
358 Object tokenEndpoint = getSession().get(SessionParameter.OAUTH_TOKEN_ENDPOINT);
359 if (!(tokenEndpoint instanceof String)) {
360 throw new CmisConnectionException("Token endpoint not set!");
361 }
362
363 if (isRefresh && token.getRefreshToken() == null) {
364 throw new CmisConnectionException("No refresh token!");
365 }
366
367
368 HttpURLConnection conn = (HttpURLConnection) (new URL(tokenEndpoint.toString())).openConnection();
369 conn.setRequestMethod("POST");
370 conn.setDoInput(true);
371 conn.setDoOutput(true);
372 conn.setAllowUserInteraction(false);
373 conn.setUseCaches(false);
374 conn.setRequestProperty("User-Agent",
375 (String) getSession().get(SessionParameter.USER_AGENT, ClientVersion.OPENCMIS_USER_AGENT));
376 conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
377
378
379 Writer writer = new OutputStreamWriter(conn.getOutputStream(), IOUtils.UTF8);
380
381 if (isRefresh) {
382 writer.write("grant_type=refresh_token");
383
384 writer.write("&refresh_token=");
385 writer.write(IOUtils.encodeURL(token.getRefreshToken()));
386 } else {
387 writer.write("grant_type=authorization_code");
388
389 Object code = getSession().get(SessionParameter.OAUTH_CODE);
390 if (code != null) {
391 writer.write("&code=");
392 writer.write(IOUtils.encodeURL(code.toString()));
393 }
394
395 Object redirectUri = getSession().get(SessionParameter.OAUTH_REDIRECT_URI);
396 if (redirectUri != null) {
397 writer.write("&redirect_uri=");
398 writer.write(IOUtils.encodeURL(redirectUri.toString()));
399 }
400 }
401
402 Object clientId = getSession().get(SessionParameter.OAUTH_CLIENT_ID);
403 if (clientId != null) {
404 writer.write("&client_id=");
405 writer.write(IOUtils.encodeURL(clientId.toString()));
406 }
407
408 Object clientSecret = getSession().get(SessionParameter.OAUTH_CLIENT_SECRET);
409 if (clientSecret != null) {
410 writer.write("&client_secret=");
411 writer.write(IOUtils.encodeURL(clientSecret.toString()));
412 }
413
414 writer.close();
415
416
417 conn.connect();
418
419
420 if (conn.getResponseCode() != 200) {
421 JSONObject jsonResponse = parseResponse(conn);
422
423 Object error = jsonResponse.get("error");
424 String errorStr = error == null ? null : error.toString();
425
426 Object description = jsonResponse.get("error_description");
427 String descriptionStr = description == null ? null : description.toString();
428
429 Object uri = jsonResponse.get("error_uri");
430 String uriStr = uri == null ? null : uri.toString();
431
432 if (LOG.isDebugEnabled()) {
433 LOG.debug("OAuth token request failed: {}", jsonResponse.toJSONString());
434 }
435
436 throw new CmisOAuthException("OAuth token request failed" + (errorStr == null ? "" : ": " + errorStr)
437 + (descriptionStr == null ? "" : ": " + descriptionStr), errorStr, descriptionStr, uriStr);
438 }
439
440
441 JSONObject jsonResponse = parseResponse(conn);
442
443 Object tokenType = jsonResponse.get("token_type");
444 if (!(tokenType instanceof String) || !TOKEN_TYPE_BEARER.equalsIgnoreCase((String) tokenType)) {
445 throw new CmisOAuthException("Unsupported OAuth token type: " + tokenType);
446 }
447
448 Object jsonAccessToken = jsonResponse.get("access_token");
449 if (!(jsonAccessToken instanceof String)) {
450 throw new CmisOAuthException("Invalid OAuth access_token!");
451 }
452
453 Object jsonRefreshToken = jsonResponse.get("refresh_token");
454 if (jsonRefreshToken != null && !(jsonRefreshToken instanceof String)) {
455 throw new CmisOAuthException("Invalid OAuth refresh_token!");
456 }
457
458 long expiresIn = defaultTokenLifetime;
459 Object jsonExpiresIn = jsonResponse.get("expires_in");
460 if (jsonExpiresIn != null) {
461 if (jsonExpiresIn instanceof Number) {
462 expiresIn = ((Number) jsonExpiresIn).longValue();
463 } else if (jsonExpiresIn instanceof String) {
464 try {
465 expiresIn = Long.parseLong((String) jsonExpiresIn);
466 } catch (NumberFormatException nfe) {
467 throw new CmisOAuthException("Invalid OAuth expires_in value!");
468 }
469 } else {
470 throw new CmisOAuthException("Invalid OAuth expires_in value!");
471 }
472
473 if (expiresIn <= 0) {
474 expiresIn = defaultTokenLifetime;
475 }
476 }
477
478 token = new Token(jsonAccessToken.toString(), (jsonRefreshToken == null ? null : jsonRefreshToken.toString()),
479 expiresIn * 1000 + System.currentTimeMillis());
480 fireTokenListner(token);
481 }
482
483 private JSONObject parseResponse(HttpURLConnection conn) {
484 Reader reader = null;
485 InputStream stream = null;
486 try {
487 int respCode = conn.getResponseCode();
488 if (respCode == 401) {
489 Map<String, Map<String, String>> challenges = MimeHelper.getChallengesFromAuthenticateHeader(conn
490 .getHeaderField("WWW-Authenticate"));
491
492 if (challenges != null && challenges.containsKey(TOKEN_TYPE_BEARER)) {
493 Map<String, String> params = challenges.get(TOKEN_TYPE_BEARER);
494
495 String errorStr = params.get("error");
496 String descriptionStr = params.get("error_description");
497 String uriStr = params.get("error_uri");
498
499 if (LOG.isDebugEnabled()) {
500 LOG.debug("Invalid OAuth token: {}", params.toString());
501 }
502
503 throw new CmisOAuthException("Unauthorized" + (errorStr == null ? "" : ": " + errorStr)
504 + (descriptionStr == null ? "" : ": " + descriptionStr), errorStr, descriptionStr, uriStr);
505 }
506
507 throw new CmisOAuthException("Unauthorized!");
508 }
509
510 if (respCode >= 200 && respCode < 300) {
511 stream = conn.getInputStream();
512 } else {
513 stream = conn.getErrorStream();
514 }
515 if (stream == null) {
516 throw new CmisOAuthException("Invalid OAuth token response!");
517 }
518
519 reader = new InputStreamReader(stream, extractCharset(conn));
520 JSONParser parser = new JSONParser();
521 Object response = parser.parse(reader);
522
523 if (!(response instanceof JSONObject)) {
524 throw new CmisOAuthException("Invalid OAuth token response!");
525 }
526
527 return (JSONObject) response;
528 } catch (CmisConnectionException ce) {
529 throw ce;
530 } catch (Exception pe) {
531 throw new CmisOAuthException("Parsing the OAuth token response failed: " + pe.getMessage(), pe);
532 } finally {
533 IOUtils.consumeAndClose(reader);
534 if (reader == null) {
535 IOUtils.closeQuietly(stream);
536 }
537 }
538 }
539
540 private String extractCharset(HttpURLConnection conn) {
541 String charset = IOUtils.UTF8;
542
543 String contentType = conn.getContentType();
544 if (contentType != null) {
545 String[] parts = contentType.split(";");
546 for (int i = 1; i < parts.length; i++) {
547 String part = parts[i].trim().toLowerCase(Locale.ENGLISH);
548 if (part.startsWith("charset")) {
549 int x = part.indexOf('=');
550 charset = part.substring(x + 1).trim();
551 break;
552 }
553 }
554 }
555
556 return charset;
557 }
558
559
560
561
562 public static class Token implements Serializable {
563
564 private static final long serialVersionUID = 1L;
565
566 private String accessToken;
567 private String refreshToken;
568 private long expirationTimestamp;
569
570 public Token(String accessToken, String refreshToken, long expirationTimestamp) {
571 this.accessToken = accessToken;
572 this.refreshToken = refreshToken;
573 this.expirationTimestamp = expirationTimestamp;
574 }
575
576
577
578
579
580
581 public String getAccessToken() {
582 return accessToken;
583 }
584
585
586
587
588
589
590 public String getRefreshToken() {
591 return refreshToken;
592 }
593
594
595
596
597
598
599
600 public long getExpirationTimestamp() {
601 return expirationTimestamp;
602 }
603
604
605
606
607
608
609
610 public boolean isExpired() {
611 return System.currentTimeMillis() >= expirationTimestamp;
612 }
613
614 @Override
615 public String toString() {
616 return "Access token: " + accessToken + " / Refresh token: " + refreshToken + " / Expires : "
617 + expirationTimestamp;
618 }
619 }
620
621
622
623
624 public interface TokenListener {
625
626
627
628
629
630
631
632 void tokenRefreshed(Token token);
633 }
634
635
636
637
638 public static class CmisOAuthException extends CmisConnectionException {
639
640 private static final long serialVersionUID = 1L;
641
642
643 public static final String ERROR_INVALID_REQUEST = "invalid_request";
644 public static final String ERROR_INVALID_CLIENT = "invalid_client";
645 public static final String ERROR_INVALID_GRANT = "invalid_grant";
646 public static final String ERROR_UNAUTHORIZED_CLIENT = "unauthorized_client";
647 public static final String ERROR_UNSUPPORTED_GRANT_TYPE = "unsupported_grant_type";
648 public static final String ERROR_INVALID_SCOPE = "invalid_scope";
649
650
651 public static final String ERROR_INVALID_TOKEN = "invalid_token";
652
653 private String error;
654 private String errorDescription;
655 private String errorUri;
656
657 public CmisOAuthException() {
658 super();
659 }
660
661 public CmisOAuthException(String message) {
662 super(message);
663 }
664
665 public CmisOAuthException(String message, Throwable cause) {
666 super(message, cause);
667 }
668
669 public CmisOAuthException(String message, String error, String errorDescription, String errorUri) {
670 super(message);
671 this.error = error;
672 this.errorDescription = errorDescription;
673 this.errorUri = errorUri;
674 }
675
676 public String getError() {
677 return error;
678 }
679
680 public String getErrorDescription() {
681 return errorDescription;
682 }
683
684 public String getErrorUri() {
685 return errorUri;
686 }
687 }
688 }