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