001package com.box.sdk; 002 003import java.net.MalformedURLException; 004import java.net.Proxy; 005import java.net.URI; 006import java.net.URL; 007import java.util.ArrayList; 008import java.util.HashMap; 009import java.util.List; 010import java.util.Map; 011import java.util.concurrent.locks.ReadWriteLock; 012import java.util.concurrent.locks.ReentrantReadWriteLock; 013 014import com.eclipsesource.json.JsonObject; 015 016/** 017 * Represents an authenticated connection to the Box API. 018 * 019 * <p>This class handles storing authentication information, automatic token refresh, and rate-limiting. It can also be 020 * used to configure the Box API endpoint URL in order to hit a different version of the API. Multiple instances of 021 * BoxAPIConnection may be created to support multi-user login.</p> 022 */ 023public class BoxAPIConnection { 024 /** 025 * The default maximum number of times an API request will be tried when an error occurs. 026 */ 027 public static final int DEFAULT_MAX_ATTEMPTS = 5; 028 029 private static final String AUTHORIZATION_URL = "https://account.box.com/api/oauth2/authorize"; 030 private static final String TOKEN_URL_STRING = "https://api.box.com/oauth2/token"; 031 private static final String REVOKE_URL_STRING = "https://api.box.com/oauth2/revoke"; 032 private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; 033 private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; 034 035 private static final String AS_USER_HEADER = "As-User"; 036 private static final String BOX_NOTIFICATIONS_HEADER = "Box-Notifications"; 037 038 private static final String JAVA_VERSION = System.getProperty("java.version"); 039 private static final String SDK_VERSION = "2.37.0"; 040 041 /** 042 * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For 043 * example, if REFRESH_EPSILON = 60000 and the access token expires in less than one minute, it will be refreshed. 044 */ 045 private static final long REFRESH_EPSILON = 60000; 046 047 private final String clientID; 048 private final String clientSecret; 049 private final ReadWriteLock refreshLock; 050 051 // These volatile fields are used when determining if the access token needs to be refreshed. Since they are used in 052 // the double-checked lock in getAccessToken(), they must be atomic. 053 private volatile long lastRefresh; 054 private volatile long expires; 055 056 private Proxy proxy; 057 private String proxyUsername; 058 private String proxyPassword; 059 060 private String userAgent; 061 private String accessToken; 062 private String refreshToken; 063 private String tokenURL; 064 private String revokeURL; 065 private String baseURL; 066 private String baseUploadURL; 067 private boolean autoRefresh; 068 private int maxRequestAttempts; 069 private int connectTimeout; 070 private int readTimeout; 071 private List<BoxAPIConnectionListener> listeners; 072 private RequestInterceptor interceptor; 073 private Map<String, String> customHeaders; 074 075 /** 076 * Constructs a new BoxAPIConnection that authenticates with a developer or access token. 077 * @param accessToken a developer or access token to use for authenticating with the API. 078 */ 079 public BoxAPIConnection(String accessToken) { 080 this(null, null, accessToken, null); 081 } 082 083 /** 084 * Constructs a new BoxAPIConnection with an access token that can be refreshed. 085 * @param clientID the client ID to use when refreshing the access token. 086 * @param clientSecret the client secret to use when refreshing the access token. 087 * @param accessToken an initial access token to use for authenticating with the API. 088 * @param refreshToken an initial refresh token to use when refreshing the access token. 089 */ 090 public BoxAPIConnection(String clientID, String clientSecret, String accessToken, String refreshToken) { 091 this.clientID = clientID; 092 this.clientSecret = clientSecret; 093 this.accessToken = accessToken; 094 this.refreshToken = refreshToken; 095 this.tokenURL = TOKEN_URL_STRING; 096 this.revokeURL = REVOKE_URL_STRING; 097 this.baseURL = DEFAULT_BASE_URL; 098 this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; 099 this.autoRefresh = true; 100 this.maxRequestAttempts = BoxGlobalSettings.getMaxRequestAttempts(); 101 this.connectTimeout = BoxGlobalSettings.getConnectTimeout(); 102 this.readTimeout = BoxGlobalSettings.getReadTimeout(); 103 this.refreshLock = new ReentrantReadWriteLock(); 104 this.userAgent = "Box Java SDK v" + SDK_VERSION + " (Java " + JAVA_VERSION + ")"; 105 this.listeners = new ArrayList<BoxAPIConnectionListener>(); 106 this.customHeaders = new HashMap<String, String>(); 107 } 108 109 /** 110 * Constructs a new BoxAPIConnection with an auth code that was obtained from the first half of OAuth. 111 * @param clientID the client ID to use when exchanging the auth code for an access token. 112 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 113 * @param authCode an auth code obtained from the first half of the OAuth process. 114 */ 115 public BoxAPIConnection(String clientID, String clientSecret, String authCode) { 116 this(clientID, clientSecret, null, null); 117 this.authenticate(authCode); 118 } 119 120 /** 121 * Constructs a new BoxAPIConnection. 122 * @param clientID the client ID to use when exchanging the auth code for an access token. 123 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 124 */ 125 public BoxAPIConnection(String clientID, String clientSecret) { 126 this(clientID, clientSecret, null, null); 127 } 128 129 /** 130 * Constructs a new BoxAPIConnection levaraging BoxConfig. 131 * @param boxConfig BoxConfig file, which should have clientId and clientSecret 132 */ 133 public BoxAPIConnection(BoxConfig boxConfig) { 134 this(boxConfig.getClientId(), boxConfig.getClientSecret(), null, null); 135 } 136 137 /** 138 * Restores a BoxAPIConnection from a saved state. 139 * 140 * @see #save 141 * @param clientID the client ID to use with the connection. 142 * @param clientSecret the client secret to use with the connection. 143 * @param state the saved state that was created with {@link #save}. 144 * @return a restored API connection. 145 */ 146 public static BoxAPIConnection restore(String clientID, String clientSecret, String state) { 147 BoxAPIConnection api = new BoxAPIConnection(clientID, clientSecret); 148 api.restore(state); 149 return api; 150 } 151 152 /** 153 * Return the authorization URL which is used to perform the authorization_code based OAuth2 flow. 154 * @param clientID the client ID to use with the connection. 155 * @param redirectUri the URL to which Box redirects the browser when authentication completes. 156 * @param state the text string that you choose. 157 * Box sends the same string to your redirect URL when authentication is complete. 158 * @param scopes this optional parameter identifies the Box scopes available 159 * to the application once it's authenticated. 160 * @return the authorization URL 161 */ 162 public static URL getAuthorizationURL(String clientID, URI redirectUri, String state, List<String> scopes) { 163 URLTemplate template = new URLTemplate(AUTHORIZATION_URL); 164 QueryStringBuilder queryBuilder = new QueryStringBuilder().appendParam("client_id", clientID) 165 .appendParam("response_type", "code") 166 .appendParam("redirect_uri", redirectUri.toString()) 167 .appendParam("state", state); 168 169 if (scopes != null && !scopes.isEmpty()) { 170 StringBuilder builder = new StringBuilder(); 171 int size = scopes.size() - 1; 172 int i = 0; 173 while (i < size) { 174 builder.append(scopes.get(i)); 175 builder.append(" "); 176 i++; 177 } 178 builder.append(scopes.get(i)); 179 180 queryBuilder.appendParam("scope", builder.toString()); 181 } 182 183 return template.buildWithQuery("", queryBuilder.toString()); 184 } 185 186 /** 187 * Authenticates the API connection by obtaining access and refresh tokens using the auth code that was obtained 188 * from the first half of OAuth. 189 * @param authCode the auth code obtained from the first half of the OAuth process. 190 */ 191 public void authenticate(String authCode) { 192 URL url = null; 193 try { 194 url = new URL(this.tokenURL); 195 } catch (MalformedURLException e) { 196 assert false : "An invalid token URL indicates a bug in the SDK."; 197 throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e); 198 } 199 200 String urlParameters = String.format("grant_type=authorization_code&code=%s&client_id=%s&client_secret=%s", 201 authCode, this.clientID, this.clientSecret); 202 203 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 204 request.shouldAuthenticate(false); 205 request.setBody(urlParameters); 206 207 BoxJSONResponse response = (BoxJSONResponse) request.send(); 208 String json = response.getJSON(); 209 210 JsonObject jsonObject = JsonObject.readFrom(json); 211 this.accessToken = jsonObject.get("access_token").asString(); 212 this.refreshToken = jsonObject.get("refresh_token").asString(); 213 this.lastRefresh = System.currentTimeMillis(); 214 this.expires = jsonObject.get("expires_in").asLong() * 1000; 215 } 216 217 /** 218 * Gets the client ID. 219 * @return the client ID. 220 */ 221 public String getClientID() { 222 return this.clientID; 223 } 224 225 /** 226 * Gets the client secret. 227 * @return the client secret. 228 */ 229 public String getClientSecret() { 230 return this.clientSecret; 231 } 232 233 /** 234 * Sets the amount of time for which this connection's access token is valid before it must be refreshed. 235 * @param milliseconds the number of milliseconds for which the access token is valid. 236 */ 237 public void setExpires(long milliseconds) { 238 this.expires = milliseconds; 239 } 240 241 /** 242 * Gets the amount of time for which this connection's access token is valid. 243 * @return the amount of time in milliseconds. 244 */ 245 public long getExpires() { 246 return this.expires; 247 } 248 249 /** 250 * Gets the token URL that's used to request access tokens. The default value is 251 * "https://www.box.com/api/oauth2/token". 252 * @return the token URL. 253 */ 254 public String getTokenURL() { 255 return this.tokenURL; 256 } 257 258 /** 259 * Sets the token URL that's used to request access tokens. For example, the default token URL is 260 * "https://www.box.com/api/oauth2/token". 261 * @param tokenURL the token URL. 262 */ 263 public void setTokenURL(String tokenURL) { 264 this.tokenURL = tokenURL; 265 } 266 267 /** 268 * Set the URL used for token revocation. 269 * @param url The url to use. 270 */ 271 public void setRevokeURL(String url) { 272 this.revokeURL = url; 273 } 274 275 /** 276 * Returns the URL used for token revocation. 277 * @return The url used for token revocation. 278 */ 279 public String getRevokeURL() { 280 return this.revokeURL; 281 } 282 283 /** 284 * Gets the base URL that's used when sending requests to the Box API. The default value is 285 * "https://api.box.com/2.0/". 286 * @return the base URL. 287 */ 288 public String getBaseURL() { 289 return this.baseURL; 290 } 291 292 /** 293 * Sets the base URL to be used when sending requests to the Box API. For example, the default base URL is 294 * "https://api.box.com/2.0/". 295 * @param baseURL a base URL 296 */ 297 public void setBaseURL(String baseURL) { 298 this.baseURL = baseURL; 299 } 300 301 /** 302 * Gets the base upload URL that's used when performing file uploads to Box. 303 * @return the base upload URL. 304 */ 305 public String getBaseUploadURL() { 306 return this.baseUploadURL; 307 } 308 309 /** 310 * Sets the base upload URL to be used when performing file uploads to Box. 311 * @param baseUploadURL a base upload URL. 312 */ 313 public void setBaseUploadURL(String baseUploadURL) { 314 this.baseUploadURL = baseUploadURL; 315 } 316 317 /** 318 * Gets the user agent that's used when sending requests to the Box API. 319 * @return the user agent. 320 */ 321 public String getUserAgent() { 322 return this.userAgent; 323 } 324 325 /** 326 * Sets the user agent to be used when sending requests to the Box API. 327 * @param userAgent the user agent. 328 */ 329 public void setUserAgent(String userAgent) { 330 this.userAgent = userAgent; 331 } 332 333 /** 334 * Gets an access token that can be used to authenticate an API request. This method will automatically refresh the 335 * access token if it has expired since the last call to <code>getAccessToken()</code>. 336 * @return a valid access token that can be used to authenticate an API request. 337 */ 338 public String getAccessToken() { 339 if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) { 340 this.refreshLock.writeLock().lock(); 341 try { 342 if (this.needsRefresh()) { 343 this.refresh(); 344 } 345 } finally { 346 this.refreshLock.writeLock().unlock(); 347 } 348 } 349 350 return this.accessToken; 351 } 352 353 /** 354 * Sets the access token to use when authenticating API requests. 355 * @param accessToken a valid access token to use when authenticating API requests. 356 */ 357 public void setAccessToken(String accessToken) { 358 this.accessToken = accessToken; 359 } 360 361 /** 362 * Gets the refresh lock to be used when refreshing an access token. 363 * @return the refresh lock. 364 */ 365 protected ReadWriteLock getRefreshLock() { 366 return this.refreshLock; 367 } 368 /** 369 * Gets a refresh token that can be used to refresh an access token. 370 * @return a valid refresh token. 371 */ 372 public String getRefreshToken() { 373 return this.refreshToken; 374 } 375 376 /** 377 * Sets the refresh token to use when refreshing an access token. 378 * @param refreshToken a valid refresh token. 379 */ 380 public void setRefreshToken(String refreshToken) { 381 this.refreshToken = refreshToken; 382 } 383 384 /** 385 * Gets the last time that the access token was refreshed. 386 * 387 * @return the last refresh time in milliseconds. 388 */ 389 public long getLastRefresh() { 390 return this.lastRefresh; 391 } 392 393 /** 394 * Sets the last time that the access token was refreshed. 395 * 396 * <p>This value is used when determining if an access token needs to be auto-refreshed. If the amount of time since 397 * the last refresh exceeds the access token's expiration time, then the access token will be refreshed.</p> 398 * 399 * @param lastRefresh the new last refresh time in milliseconds. 400 */ 401 public void setLastRefresh(long lastRefresh) { 402 this.lastRefresh = lastRefresh; 403 } 404 405 /** 406 * Enables or disables automatic refreshing of this connection's access token. Defaults to true. 407 * @param autoRefresh true to enable auto token refresh; otherwise false. 408 */ 409 public void setAutoRefresh(boolean autoRefresh) { 410 this.autoRefresh = autoRefresh; 411 } 412 413 /** 414 * Gets whether or not automatic refreshing of this connection's access token is enabled. Defaults to true. 415 * @return true if auto token refresh is enabled; otherwise false. 416 */ 417 public boolean getAutoRefresh() { 418 return this.autoRefresh; 419 } 420 421 /** 422 * Gets the maximum number of times an API request will be tried when an error occurs. 423 * @return the maximum number of request attempts. 424 */ 425 public int getMaxRequestAttempts() { 426 return this.maxRequestAttempts; 427 } 428 429 /** 430 * Sets the maximum number of times an API request will be tried when an error occurs. 431 * @param attempts the maximum number of request attempts. 432 */ 433 public void setMaxRequestAttempts(int attempts) { 434 this.maxRequestAttempts = attempts; 435 } 436 437 /** 438 * Gets the connect timeout for this connection in milliseconds. 439 * @return the number of milliseconds to connect before timing out. 440 */ 441 public int getConnectTimeout() { 442 return this.connectTimeout; 443 } 444 445 /** 446 * Sets the connect timeout for this connection. 447 * @param connectTimeout The number of milliseconds to wait for the connection to be established. 448 */ 449 public void setConnectTimeout(int connectTimeout) { 450 this.connectTimeout = connectTimeout; 451 } 452 453 /** 454 * Gets the read timeout for this connection in milliseconds. 455 * @return the number of milliseconds to wait for bytes to be read before timing out. 456 */ 457 public int getReadTimeout() { 458 return this.readTimeout; 459 } 460 461 /** 462 * Sets the read timeout for this connection. 463 * @param readTimeout The number of milliseconds to wait for bytes to be read. 464 */ 465 public void setReadTimeout(int readTimeout) { 466 this.readTimeout = readTimeout; 467 } 468 469 /** 470 * Gets the proxy value to use for API calls to Box. 471 * @return the current proxy. 472 */ 473 public Proxy getProxy() { 474 return this.proxy; 475 } 476 477 /** 478 * Sets the proxy to use for API calls to Box. 479 * @param proxy the proxy to use for API calls to Box. 480 */ 481 public void setProxy(Proxy proxy) { 482 this.proxy = proxy; 483 } 484 485 /** 486 * Gets the username to use for a proxy that requires basic auth. 487 * @return the username to use for a proxy that requires basic auth. 488 */ 489 public String getProxyUsername() { 490 return this.proxyUsername; 491 } 492 493 /** 494 * Sets the username to use for a proxy that requires basic auth. 495 * @param proxyUsername the username to use for a proxy that requires basic auth. 496 */ 497 public void setProxyUsername(String proxyUsername) { 498 this.proxyUsername = proxyUsername; 499 } 500 501 /** 502 * Gets the password to use for a proxy that requires basic auth. 503 * @return the password to use for a proxy that requires basic auth. 504 */ 505 public String getProxyPassword() { 506 return this.proxyPassword; 507 } 508 509 /** 510 * Sets the password to use for a proxy that requires basic auth. 511 * @param proxyPassword the password to use for a proxy that requires basic auth. 512 */ 513 public void setProxyPassword(String proxyPassword) { 514 this.proxyPassword = proxyPassword; 515 } 516 517 /** 518 * Determines if this connection's access token can be refreshed. An access token cannot be refreshed if a refresh 519 * token was never set. 520 * @return true if the access token can be refreshed; otherwise false. 521 */ 522 public boolean canRefresh() { 523 return this.refreshToken != null; 524 } 525 526 /** 527 * Determines if this connection's access token has expired and needs to be refreshed. 528 * @return true if the access token needs to be refreshed; otherwise false. 529 */ 530 public boolean needsRefresh() { 531 boolean needsRefresh; 532 533 this.refreshLock.readLock().lock(); 534 long now = System.currentTimeMillis(); 535 long tokenDuration = (now - this.lastRefresh); 536 needsRefresh = (tokenDuration >= this.expires - REFRESH_EPSILON); 537 this.refreshLock.readLock().unlock(); 538 539 return needsRefresh; 540 } 541 542 /** 543 * Refresh's this connection's access token using its refresh token. 544 * @throws IllegalStateException if this connection's access token cannot be refreshed. 545 */ 546 public void refresh() { 547 this.refreshLock.writeLock().lock(); 548 549 if (!this.canRefresh()) { 550 this.refreshLock.writeLock().unlock(); 551 throw new IllegalStateException("The BoxAPIConnection cannot be refreshed because it doesn't have a " 552 + "refresh token."); 553 } 554 555 URL url = null; 556 try { 557 url = new URL(this.tokenURL); 558 } catch (MalformedURLException e) { 559 this.refreshLock.writeLock().unlock(); 560 assert false : "An invalid refresh URL indicates a bug in the SDK."; 561 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 562 } 563 564 String urlParameters = String.format("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", 565 this.refreshToken, this.clientID, this.clientSecret); 566 567 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 568 request.shouldAuthenticate(false); 569 request.setBody(urlParameters); 570 571 String json; 572 try { 573 BoxJSONResponse response = (BoxJSONResponse) request.send(); 574 json = response.getJSON(); 575 } catch (BoxAPIException e) { 576 this.notifyError(e); 577 this.refreshLock.writeLock().unlock(); 578 throw e; 579 } 580 581 JsonObject jsonObject = JsonObject.readFrom(json); 582 this.accessToken = jsonObject.get("access_token").asString(); 583 this.refreshToken = jsonObject.get("refresh_token").asString(); 584 this.lastRefresh = System.currentTimeMillis(); 585 this.expires = jsonObject.get("expires_in").asLong() * 1000; 586 587 this.notifyRefresh(); 588 589 this.refreshLock.writeLock().unlock(); 590 } 591 592 /** 593 * Restores a saved connection state into this BoxAPIConnection. 594 * 595 * @see #save 596 * @param state the saved state that was created with {@link #save}. 597 */ 598 public void restore(String state) { 599 JsonObject json = JsonObject.readFrom(state); 600 String accessToken = json.get("accessToken").asString(); 601 String refreshToken = json.get("refreshToken").asString(); 602 long lastRefresh = json.get("lastRefresh").asLong(); 603 long expires = json.get("expires").asLong(); 604 String userAgent = json.get("userAgent").asString(); 605 String tokenURL = json.get("tokenURL").asString(); 606 String baseURL = json.get("baseURL").asString(); 607 String baseUploadURL = json.get("baseUploadURL").asString(); 608 boolean autoRefresh = json.get("autoRefresh").asBoolean(); 609 int maxRequestAttempts = json.get("maxRequestAttempts").asInt(); 610 611 this.accessToken = accessToken; 612 this.refreshToken = refreshToken; 613 this.lastRefresh = lastRefresh; 614 this.expires = expires; 615 this.userAgent = userAgent; 616 this.tokenURL = tokenURL; 617 this.baseURL = baseURL; 618 this.baseUploadURL = baseUploadURL; 619 this.autoRefresh = autoRefresh; 620 this.maxRequestAttempts = maxRequestAttempts; 621 } 622 623 /** 624 * Notifies a refresh event to all the listeners. 625 */ 626 protected void notifyRefresh() { 627 for (BoxAPIConnectionListener listener : this.listeners) { 628 listener.onRefresh(this); 629 } 630 } 631 632 /** 633 * Notifies an error event to all the listeners. 634 * @param error A BoxAPIException instance. 635 */ 636 protected void notifyError(BoxAPIException error) { 637 for (BoxAPIConnectionListener listener : this.listeners) { 638 listener.onError(this, error); 639 } 640 } 641 642 /** 643 * Add a listener to listen to Box API connection events. 644 * @param listener a listener to listen to Box API connection. 645 */ 646 public void addListener(BoxAPIConnectionListener listener) { 647 this.listeners.add(listener); 648 } 649 650 /** 651 * Remove a listener listening to Box API connection events. 652 * @param listener the listener to remove. 653 */ 654 public void removeListener(BoxAPIConnectionListener listener) { 655 this.listeners.remove(listener); 656 } 657 658 /** 659 * Gets the RequestInterceptor associated with this API connection. 660 * @return the RequestInterceptor associated with this API connection. 661 */ 662 public RequestInterceptor getRequestInterceptor() { 663 return this.interceptor; 664 } 665 666 /** 667 * Sets a RequestInterceptor that can intercept requests and manipulate them before they're sent to the Box API. 668 * @param interceptor the RequestInterceptor. 669 */ 670 public void setRequestInterceptor(RequestInterceptor interceptor) { 671 this.interceptor = interceptor; 672 } 673 674 /** 675 * Get a lower-scoped token restricted to a resource for the list of scopes that are passed. 676 * @param scopes the list of scopes to which the new token should be restricted for 677 * @param resource the resource for which the new token has to be obtained 678 * @return scopedToken which has access token and other details 679 */ 680 public ScopedToken getLowerScopedToken(List<String> scopes, String resource) { 681 assert (scopes != null); 682 assert (scopes.size() > 0); 683 URL url = null; 684 try { 685 url = new URL(this.getTokenURL()); 686 } catch (MalformedURLException e) { 687 assert false : "An invalid refresh URL indicates a bug in the SDK."; 688 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 689 } 690 691 StringBuilder spaceSeparatedScopes = new StringBuilder(); 692 for (int i = 0; i < scopes.size(); i++) { 693 spaceSeparatedScopes.append(scopes.get(i)); 694 if (i < scopes.size() - 1) { 695 spaceSeparatedScopes.append(" "); 696 } 697 } 698 699 String urlParameters = null; 700 701 if (resource != null) { 702 //this.getAccessToken() ensures we have a valid access token 703 urlParameters = String.format("grant_type=urn:ietf:params:oauth:grant-type:token-exchange" 704 + "&subject_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=%s" 705 + "&scope=%s&resource=%s", 706 this.getAccessToken(), spaceSeparatedScopes, resource); 707 } else { 708 //this.getAccessToken() ensures we have a valid access token 709 urlParameters = String.format("grant_type=urn:ietf:params:oauth:grant-type:token-exchange" 710 + "&subject_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=%s" 711 + "&scope=%s", 712 this.getAccessToken(), spaceSeparatedScopes); 713 } 714 715 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 716 request.shouldAuthenticate(false); 717 request.setBody(urlParameters); 718 719 String json; 720 try { 721 BoxJSONResponse response = (BoxJSONResponse) request.send(); 722 json = response.getJSON(); 723 } catch (BoxAPIException e) { 724 this.notifyError(e); 725 throw e; 726 } 727 728 JsonObject jsonObject = JsonObject.readFrom(json); 729 ScopedToken token = new ScopedToken(jsonObject); 730 token.setObtainedAt(System.currentTimeMillis()); 731 token.setExpiresIn(jsonObject.get("expires_in").asLong() * 1000); 732 return token; 733 } 734 735 /** 736 * Revokes the tokens associated with this API connection. This results in the connection no 737 * longer being able to make API calls until a fresh authorization is made by calling authenticate() 738 */ 739 public void revokeToken() { 740 741 URL url = null; 742 try { 743 url = new URL(this.revokeURL); 744 } catch (MalformedURLException e) { 745 assert false : "An invalid refresh URL indicates a bug in the SDK."; 746 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 747 } 748 749 String urlParameters = String.format("token=%s&client_id=%s&client_secret=%s", 750 this.accessToken, this.clientID, this.clientSecret); 751 752 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 753 request.shouldAuthenticate(false); 754 request.setBody(urlParameters); 755 756 try { 757 request.send(); 758 } catch (BoxAPIException e) { 759 throw e; 760 } 761 } 762 763 /** 764 * Saves the state of this connection to a string so that it can be persisted and restored at a later time. 765 * 766 * <p>Note that proxy settings aren't automatically saved or restored. This is mainly due to security concerns 767 * around persisting proxy authentication details to the state string. If your connection uses a proxy, you will 768 * have to manually configure it again after restoring the connection.</p> 769 * 770 * @see #restore 771 * @return the state of this connection. 772 */ 773 public String save() { 774 JsonObject state = new JsonObject() 775 .add("accessToken", this.accessToken) 776 .add("refreshToken", this.refreshToken) 777 .add("lastRefresh", this.lastRefresh) 778 .add("expires", this.expires) 779 .add("userAgent", this.userAgent) 780 .add("tokenURL", this.tokenURL) 781 .add("baseURL", this.baseURL) 782 .add("baseUploadURL", this.baseUploadURL) 783 .add("autoRefresh", this.autoRefresh) 784 .add("maxRequestAttempts", this.maxRequestAttempts); 785 return state.toString(); 786 } 787 788 String lockAccessToken() { 789 if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) { 790 this.refreshLock.writeLock().lock(); 791 try { 792 if (this.needsRefresh()) { 793 this.refresh(); 794 } 795 this.refreshLock.readLock().lock(); 796 } finally { 797 this.refreshLock.writeLock().unlock(); 798 } 799 } else { 800 this.refreshLock.readLock().lock(); 801 } 802 803 return this.accessToken; 804 } 805 806 void unlockAccessToken() { 807 this.refreshLock.readLock().unlock(); 808 } 809 810 /** 811 * Get the value for the X-Box-UA header. 812 * @return the header value. 813 */ 814 String getBoxUAHeader() { 815 816 return "agent=box-java-sdk/" + SDK_VERSION + "; env=Java/" + JAVA_VERSION; 817 } 818 819 /** 820 * Sets a custom header to be sent on all requests through this API connection. 821 * @param header the header name. 822 * @param value the header value. 823 */ 824 public void setCustomHeader(String header, String value) { 825 this.customHeaders.put(header, value); 826 } 827 828 /** 829 * Removes a custom header, so it will no longer be sent on requests through this API connection. 830 * @param header the header name. 831 */ 832 public void removeCustomHeader(String header) { 833 this.customHeaders.remove(header); 834 } 835 836 /** 837 * Suppresses email notifications from API actions. This is typically used by security or admin applications 838 * to prevent spamming end users when doing automated processing on their content. 839 */ 840 public void suppressNotifications() { 841 this.setCustomHeader(BOX_NOTIFICATIONS_HEADER, "off"); 842 } 843 844 /** 845 * Re-enable email notifications from API actions if they have been suppressed. 846 * @see #suppressNotifications 847 */ 848 public void enableNotifications() { 849 this.removeCustomHeader(BOX_NOTIFICATIONS_HEADER); 850 } 851 852 /** 853 * Set this API connection to make API calls on behalf of another users, impersonating them. This 854 * functionality can only be used by admins and service accounts. 855 * @param userID the ID of the user to act as. 856 */ 857 public void asUser(String userID) { 858 this.setCustomHeader(AS_USER_HEADER, userID); 859 } 860 861 /** 862 * Sets this API connection to make API calls on behalf of the user with whom the access token is associated. 863 * This undoes any previous calls to asUser(). 864 * @see #asUser 865 */ 866 public void asSelf() { 867 this.removeCustomHeader(AS_USER_HEADER); 868 } 869 870 Map<String, String> getHeaders() { 871 return this.customHeaders; 872 } 873}