• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

box / box-java-sdk / #5209

16 Oct 2025 01:16PM UTC coverage: 13.665%. Remained the same
#5209

Pull #1503

github

web-flow
Merge c4523ac26 into ecb840ebb
Pull Request #1503: docs: Update CONTRIBUTING.md with commit message guidelines

8374 of 61282 relevant lines covered (13.66%)

0.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

79.05
/src/main/java/com/box/sdk/BoxAPIConnection.java
1
package com.box.sdk;
2

3
import static java.lang.String.format;
4
import static java.lang.String.join;
5
import static java.util.Collections.singletonList;
6
import static okhttp3.ConnectionSpec.MODERN_TLS;
7

8
import com.eclipsesource.json.Json;
9
import com.eclipsesource.json.JsonObject;
10
import com.eclipsesource.json.JsonValue;
11
import java.io.IOException;
12
import java.net.MalformedURLException;
13
import java.net.Proxy;
14
import java.net.URI;
15
import java.net.URL;
16
import java.security.KeyManagementException;
17
import java.security.NoSuchAlgorithmException;
18
import java.time.Duration;
19
import java.util.ArrayList;
20
import java.util.HashMap;
21
import java.util.List;
22
import java.util.Map;
23
import java.util.Optional;
24
import java.util.concurrent.locks.ReadWriteLock;
25
import java.util.concurrent.locks.ReentrantReadWriteLock;
26
import java.util.regex.Pattern;
27
import javax.net.ssl.HostnameVerifier;
28
import javax.net.ssl.SSLContext;
29
import javax.net.ssl.TrustManager;
30
import javax.net.ssl.X509TrustManager;
31
import okhttp3.Authenticator;
32
import okhttp3.Call;
33
import okhttp3.Credentials;
34
import okhttp3.Headers;
35
import okhttp3.OkHttpClient;
36
import okhttp3.Request;
37
import okhttp3.Response;
38

39
/**
40
 * @deprecated {@link BoxAPIConnection} class, and the entire the com.box.sdk package is deprecated,
41
 *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
42
 *     com.box.sdkgen.client.BoxClient}
43
 *     <p>Represents an authenticated connection to the Box API.
44
 *     <p>This class handles storing authentication information, automatic token refresh, and
45
 *     rate-limiting. It can also be used to configure the Box API endpoint URL in order to hit a
46
 *     different version of the API. Multiple instances of BoxAPIConnection may be created to
47
 *     support multi-user login.
48
 */
49
@Deprecated
50
public class BoxAPIConnection {
51

52
  /**
53
   * Used as a marker to setup connection to use default HostnameVerifier Example:
54
   *
55
   * <pre>{@code
56
   * BoxApiConnection api = new BoxApiConnection(...);
57
   * HostnameVerifier myHostnameVerifier = ...
58
   * api.configureSslCertificatesValidation(DEFAULT_TRUST_MANAGER, myHostnameVerifier);
59
   * }</pre>
60
   */
61
  public static final X509TrustManager DEFAULT_TRUST_MANAGER = null;
1✔
62
  /**
63
   * Used as a marker to setup connection to use default HostnameVerifier Example:
64
   *
65
   * <pre>{@code
66
   * BoxApiConnection api = new BoxApiConnection(...);
67
   * X509TrustManager myTrustManager = ...
68
   * api.configureSslCertificatesValidation(myTrustManager, DEFAULT_HOSTNAME_VERIFIER);
69
   * }</pre>
70
   */
71
  public static final HostnameVerifier DEFAULT_HOSTNAME_VERIFIER = null;
1✔
72

73
  /**
74
   * The default maximum number of times an API request will be retried after an error response is
75
   * received.
76
   */
77
  public static final int DEFAULT_MAX_RETRIES = 5;
78
  /** Default authorization URL */
79
  protected static final String DEFAULT_BASE_AUTHORIZATION_URL = "https://account.box.com/api/";
80

81
  static final String AS_USER_HEADER = "As-User";
82

83
  private static final String API_VERSION = "2.0";
84
  private static final String OAUTH_SUFFIX = "oauth2/authorize";
85
  private static final String TOKEN_URL_SUFFIX = "oauth2/token";
86
  private static final String REVOKE_URL_SUFFIX = "oauth2/revoke";
87
  private static final String DEFAULT_BASE_URL = "https://api.box.com/";
88
  private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/";
89
  private static final String DEFAULT_BASE_APP_URL = "https://app.box.com";
90

91
  private static final String BOX_NOTIFICATIONS_HEADER = "Box-Notifications";
92

93
  private static final String JAVA_VERSION = System.getProperty("java.version");
1✔
94
  private static final String SDK_VERSION = "4.16.3";
95

96
  /**
97
   * The amount of buffer time, in milliseconds, to use when determining if an access token should
98
   * be refreshed. For example, if REFRESH_EPSILON = 60000 and the access token expires in less than
99
   * one minute, it will be refreshed.
100
   */
101
  private static final long REFRESH_EPSILON = 60000;
102

103
  private final String clientID;
104
  private final String clientSecret;
105
  private final ReadWriteLock refreshLock;
106
  private X509TrustManager trustManager;
107
  private HostnameVerifier hostnameVerifier;
108

109
  // These volatile fields are used when determining if the access token needs to be refreshed.
110
  // Since they are used in
111
  // the double-checked lock in getAccessToken(), they must be atomic.
112
  private volatile long lastRefresh;
113
  private volatile long expires;
114

115
  private Proxy proxy;
116
  private String proxyUsername;
117
  private String proxyPassword;
118

119
  private String userAgent;
120
  private String accessToken;
121
  private String refreshToken;
122
  private String tokenURL;
123
  private String revokeURL;
124
  private String baseURL;
125
  private String baseUploadURL;
126
  private String baseAppURL;
127
  private String baseAuthorizationURL;
128
  private boolean autoRefresh;
129
  private int maxRetryAttempts;
130
  private int connectTimeout;
131
  private int readTimeout;
132
  private boolean useZstdCompression;
133
  private final List<BoxAPIConnectionListener> listeners;
134
  private RequestInterceptor interceptor;
135
  private final Map<String, String> customHeaders;
136

137
  private OkHttpClient httpClient;
138
  private OkHttpClient noRedirectsHttpClient;
139
  private Authenticator authenticator;
140

141
  /**
142
   * @deprecated {@link BoxAPIConnection} class, and the entire com.box.sdk package is deprecated,
143
   *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
144
   *     com.box.sdkgen.client.BoxClient}
145
   *     <p>Constructs a new BoxAPIConnection that authenticates with a developer or access token.
146
   * @param accessToken a developer or access token to use for authenticating with the API.
147
   */
148
  @Deprecated
149
  public BoxAPIConnection(String accessToken) {
150
    this(null, null, accessToken, null);
1✔
151
  }
1✔
152

153
  /**
154
   * @deprecated {@link BoxAPIConnection} class, and the entire com.box.sdk package is deprecated,
155
   *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
156
   *     com.box.sdkgen.client.BoxClient}
157
   *     <p>Constructs a new BoxAPIConnection with an access token that can be refreshed.
158
   * @param clientID the client ID to use when refreshing the access token.
159
   * @param clientSecret the client secret to use when refreshing the access token.
160
   * @param accessToken an initial access token to use for authenticating with the API.
161
   * @param refreshToken an initial refresh token to use when refreshing the access token.
162
   */
163
  @Deprecated
164
  public BoxAPIConnection(
165
      String clientID, String clientSecret, String accessToken, String refreshToken) {
1✔
166
    this.clientID = clientID;
1✔
167
    this.clientSecret = clientSecret;
1✔
168
    this.accessToken = accessToken;
1✔
169
    this.refreshToken = refreshToken;
1✔
170
    this.baseURL = fixBaseUrl(DEFAULT_BASE_URL);
1✔
171
    this.baseUploadURL = fixBaseUrl(DEFAULT_BASE_UPLOAD_URL);
1✔
172
    this.baseAppURL = DEFAULT_BASE_APP_URL;
1✔
173
    this.baseAuthorizationURL = DEFAULT_BASE_AUTHORIZATION_URL;
1✔
174
    this.autoRefresh = true;
1✔
175
    this.maxRetryAttempts = BoxGlobalSettings.getMaxRetryAttempts();
1✔
176
    this.connectTimeout = BoxGlobalSettings.getConnectTimeout();
1✔
177
    this.readTimeout = BoxGlobalSettings.getReadTimeout();
1✔
178
    this.useZstdCompression = BoxGlobalSettings.getUseZstdCompression();
1✔
179
    this.refreshLock = new ReentrantReadWriteLock();
1✔
180
    this.userAgent = "Box Java SDK v" + SDK_VERSION + " (Java " + JAVA_VERSION + ")";
1✔
181
    this.listeners = new ArrayList<>();
1✔
182
    this.customHeaders = new HashMap<>();
1✔
183
    buildHttpClients();
1✔
184
  }
1✔
185

186
  /**
187
   * @deprecated {@link BoxAPIConnection} class, and the entire com.box.sdk package is deprecated,
188
   *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
189
   *     com.box.sdkgen.client.BoxClient}
190
   *     <p>Constructs a new BoxAPIConnection with an auth code that was obtained from the first
191
   *     half of OAuth.
192
   * @param clientID the client ID to use when exchanging the auth code for an access token.
193
   * @param clientSecret the client secret to use when exchanging the auth code for an access token.
194
   * @param authCode an auth code obtained from the first half of the OAuth process.
195
   */
196
  @Deprecated
197
  public BoxAPIConnection(String clientID, String clientSecret, String authCode) {
198
    this(clientID, clientSecret, null, null);
×
199
    this.authenticate(authCode);
×
200
  }
×
201

202
  /**
203
   * @deprecated {@link BoxAPIConnection} class, and the entire com.box.sdk package is deprecated,
204
   *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
205
   *     com.box.sdkgen.client.BoxClient}
206
   *     <p>Constructs a new BoxAPIConnection.
207
   * @param clientID the client ID to use when exchanging the auth code for an access token.
208
   * @param clientSecret the client secret to use when exchanging the auth code for an access token.
209
   */
210
  @Deprecated
211
  public BoxAPIConnection(String clientID, String clientSecret) {
212
    this(clientID, clientSecret, null, null);
1✔
213
  }
1✔
214

215
  /**
216
   * @deprecated {@link BoxAPIConnection} class, and the entire com.box.sdk package is deprecated,
217
   *     it is recommended to use {@link com.box.sdkgen} package. Instead of this class use {@link
218
   *     com.box.sdkgen.client.BoxClient}
219
   *     <p>Constructs a new BoxAPIConnection leveraging BoxConfig.
220
   * @param boxConfig BoxConfig file, which should have clientId and clientSecret
221
   */
222
  @Deprecated
223
  public BoxAPIConnection(BoxConfig boxConfig) {
224
    this(boxConfig.getClientId(), boxConfig.getClientSecret(), null, null);
×
225
  }
×
226

227
  private void buildHttpClients() {
228
    OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder();
1✔
229
    if (trustManager != null) {
1✔
230
      try {
231
        SSLContext sslContext = SSLContext.getInstance("SSL");
1✔
232
        sslContext.init(null, new TrustManager[] {trustManager}, new java.security.SecureRandom());
1✔
233
        httpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager);
1✔
234
      } catch (NoSuchAlgorithmException | KeyManagementException e) {
×
235
        throw new RuntimeException(e);
×
236
      }
1✔
237
    }
238

239
    OkHttpClient.Builder builder =
1✔
240
        httpClientBuilder
241
            .followSslRedirects(true)
1✔
242
            .followRedirects(true)
1✔
243
            .connectTimeout(Duration.ofMillis(connectTimeout))
1✔
244
            .readTimeout(Duration.ofMillis(readTimeout))
1✔
245
            .connectionSpecs(singletonList(MODERN_TLS));
1✔
246

247
    if (hostnameVerifier != null) {
1✔
248
      httpClientBuilder.hostnameVerifier(hostnameVerifier);
1✔
249
    }
250

251
    if (proxy != null) {
1✔
252
      builder.proxy(proxy);
1✔
253
      if (proxyUsername != null && proxyPassword != null) {
1✔
254
        builder.proxyAuthenticator(
1✔
255
            (route, response) -> {
256
              String credential = Credentials.basic(proxyUsername, proxyPassword);
1✔
257
              return response
1✔
258
                  .request()
1✔
259
                  .newBuilder()
1✔
260
                  .header("Proxy-Authorization", credential)
1✔
261
                  .build();
1✔
262
            });
263
      }
264
      if (this.authenticator != null) {
1✔
265
        builder.proxyAuthenticator(authenticator);
1✔
266
      }
267
    }
268
    builder = modifyHttpClientBuilder(builder);
1✔
269
    if (this.useZstdCompression) {
1✔
270
      builder.addNetworkInterceptor(new ZstdInterceptor());
1✔
271
    }
272

273
    this.httpClient = builder.build();
1✔
274
    this.noRedirectsHttpClient =
1✔
275
        new OkHttpClient.Builder(httpClient)
276
            .followSslRedirects(false)
1✔
277
            .followRedirects(false)
1✔
278
            .build();
1✔
279
  }
1✔
280

281
  /**
282
   * Can be used to modify OkHttp.Builder used to create connection. This method is called after all
283
   * modifications were done, thus allowing others to create their own connections and further
284
   * customize builder.
285
   *
286
   * @param httpClientBuilder Builder that will be used to create http connection.
287
   * @return Modified builder.
288
   */
289
  protected OkHttpClient.Builder modifyHttpClientBuilder(OkHttpClient.Builder httpClientBuilder) {
290
    return httpClientBuilder;
1✔
291
  }
292

293
  /**
294
   * Sets a proxy authenticator that will be used when proxy requires authentication. If you use
295
   * {@link BoxAPIConnection#setProxyBasicAuthentication(String, String)} it adds an authenticator
296
   * that performs Basic authorization. By calling this method you can override this behaviour. You
297
   * do not need to call {@link BoxAPIConnection#setProxyBasicAuthentication(String, String)} in
298
   * order to set custom authenticator.
299
   *
300
   * @param authenticator Custom authenticator that will be called when proxy asks for
301
   *     authorization.
302
   */
303
  public void setProxyAuthenticator(Authenticator authenticator) {
304
    this.authenticator = authenticator;
1✔
305
    buildHttpClients();
1✔
306
  }
1✔
307

308
  /**
309
   * Restores a BoxAPIConnection from a saved state.
310
   *
311
   * @param clientID the client ID to use with the connection.
312
   * @param clientSecret the client secret to use with the connection.
313
   * @param state the saved state that was created with {@link #save}.
314
   * @return a restored API connection.
315
   * @see #save
316
   */
317
  public static BoxAPIConnection restore(String clientID, String clientSecret, String state) {
318
    BoxAPIConnection api = new BoxAPIConnection(clientID, clientSecret);
1✔
319
    api.restore(state);
1✔
320
    return api;
1✔
321
  }
322

323
  /**
324
   * Returns the default authorization URL which is used to perform the authorization_code based
325
   * OAuth2 flow. If custom Authorization URL is needed use instance method {@link
326
   * BoxAPIConnection#getAuthorizationURL}
327
   *
328
   * @param clientID the client ID to use with the connection.
329
   * @param redirectUri the URL to which Box redirects the browser when authentication completes.
330
   * @param state the text string that you choose. Box sends the same string to your redirect URL
331
   *     when authentication is complete.
332
   * @param scopes this optional parameter identifies the Box scopes available to the application
333
   *     once it's authenticated.
334
   * @return the authorization URL
335
   */
336
  public static URL getAuthorizationURL(
337
      String clientID, URI redirectUri, String state, List<String> scopes) {
338
    return createFullAuthorizationUrl(
1✔
339
        DEFAULT_BASE_AUTHORIZATION_URL, clientID, redirectUri, state, scopes);
340
  }
341

342
  private static URL createFullAuthorizationUrl(
343
      String authorizationUrl,
344
      String clientID,
345
      URI redirectUri,
346
      String state,
347
      List<String> scopes) {
348
    URLTemplate template = new URLTemplate(authorizationUrl + OAUTH_SUFFIX);
1✔
349
    QueryStringBuilder queryBuilder =
1✔
350
        new QueryStringBuilder()
351
            .appendParam("client_id", clientID)
1✔
352
            .appendParam("response_type", "code")
1✔
353
            .appendParam("redirect_uri", redirectUri.toString())
1✔
354
            .appendParam("state", state);
1✔
355

356
    if (scopes != null && !scopes.isEmpty()) {
1✔
357
      queryBuilder.appendParam("scope", join(" ", scopes));
1✔
358
    }
359

360
    return template.buildWithQuery("", queryBuilder.toString());
1✔
361
  }
362

363
  /**
364
   * Authenticates the API connection by obtaining access and refresh tokens using the auth code
365
   * that was obtained from the first half of OAuth.
366
   *
367
   * @param authCode the auth code obtained from the first half of the OAuth process.
368
   */
369
  public void authenticate(String authCode) {
370
    URL url;
371
    try {
372
      url = new URL(this.getTokenURL());
1✔
373
    } catch (MalformedURLException e) {
×
374
      assert false : "An invalid token URL indicates a bug in the SDK.";
×
375
      throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e);
×
376
    }
1✔
377

378
    String urlParameters =
1✔
379
        format(
1✔
380
            "grant_type=authorization_code&code=%s&client_id=%s&client_secret=%s",
381
            authCode, this.clientID, this.clientSecret);
382

383
    BoxAPIRequest request = new BoxAPIRequest(this, url, "POST");
1✔
384
    request.shouldAuthenticate(false);
1✔
385
    request.setBody(urlParameters);
1✔
386

387
    // authentication uses form url encoded but response is JSON
388
    try (BoxJSONResponse response = (BoxJSONResponse) request.send()) {
1✔
389
      String json = response.getJSON();
1✔
390

391
      JsonObject jsonObject = Json.parse(json).asObject();
1✔
392
      this.accessToken = jsonObject.get("access_token").asString();
1✔
393
      this.refreshToken = jsonObject.get("refresh_token").asString();
1✔
394
      this.lastRefresh = System.currentTimeMillis();
1✔
395
      this.expires = jsonObject.get("expires_in").asLong() * 1000;
1✔
396
    }
397
  }
1✔
398

399
  /**
400
   * Gets the client ID.
401
   *
402
   * @return the client ID.
403
   */
404
  public String getClientID() {
405
    return this.clientID;
1✔
406
  }
407

408
  /**
409
   * Gets the client secret.
410
   *
411
   * @return the client secret.
412
   */
413
  public String getClientSecret() {
414
    return this.clientSecret;
1✔
415
  }
416

417
  /**
418
   * Gets the amount of time for which this connection's access token is valid.
419
   *
420
   * @return the amount of time in milliseconds.
421
   */
422
  public long getExpires() {
423
    return this.expires;
1✔
424
  }
425

426
  /**
427
   * Sets the amount of time for which this connection's access token is valid before it must be
428
   * refreshed.
429
   *
430
   * @param milliseconds the number of milliseconds for which the access token is valid.
431
   */
432
  public void setExpires(long milliseconds) {
433
    this.expires = milliseconds;
1✔
434
  }
1✔
435

436
  /**
437
   * Gets the token URL that's used to request access tokens. The default value is
438
   * "https://www.box.com/api/oauth2/token". The URL is created from {@link
439
   * BoxAPIConnection#baseURL} and {@link BoxAPIConnection#TOKEN_URL_SUFFIX}.
440
   *
441
   * @return the token URL.
442
   */
443
  public String getTokenURL() {
444
    if (this.tokenURL != null) {
1✔
445
      return this.tokenURL;
1✔
446
    } else {
447
      return this.baseURL + TOKEN_URL_SUFFIX;
1✔
448
    }
449
  }
450

451
  /**
452
   * Returns the URL used for token revocation. The URL is created from {@link
453
   * BoxAPIConnection#baseURL} and {@link BoxAPIConnection#REVOKE_URL_SUFFIX}.
454
   *
455
   * @return The url used for token revocation.
456
   */
457
  public String getRevokeURL() {
458
    if (this.revokeURL != null) {
1✔
459
      return this.revokeURL;
1✔
460
    } else {
461
      return this.baseURL + REVOKE_URL_SUFFIX;
1✔
462
    }
463
  }
464

465
  /**
466
   * Gets the base URL that's used when sending requests to the Box API. The URL is created from
467
   * {@link BoxAPIConnection#baseURL} and {@link BoxAPIConnection#API_VERSION}. The default value is
468
   * "https://api.box.com/2.0/".
469
   *
470
   * @return the base URL.
471
   */
472
  public String getBaseURL() {
473
    return this.baseURL + API_VERSION + "/";
1✔
474
  }
475

476
  /**
477
   * Sets the base URL to be used when sending requests to the Box API. For example, the default
478
   * base URL is "https://api.box.com/". This method changes how {@link
479
   * BoxAPIConnection#getRevokeURL()} and {@link BoxAPIConnection#getTokenURL()} are constructed.
480
   *
481
   * @param baseURL a base URL
482
   */
483
  public void setBaseURL(String baseURL) {
484
    this.baseURL = fixBaseUrl(baseURL);
1✔
485
  }
1✔
486

487
  /**
488
   * Gets the base upload URL that's used when performing file uploads to Box. The URL is created
489
   * from {@link BoxAPIConnection#baseUploadURL} and {@link BoxAPIConnection#API_VERSION}.
490
   *
491
   * @return the base upload URL.
492
   */
493
  public String getBaseUploadURL() {
494
    return this.baseUploadURL + API_VERSION + "/";
1✔
495
  }
496

497
  /**
498
   * Sets the base upload URL to be used when performing file uploads to Box.
499
   *
500
   * @param baseUploadURL a base upload URL.
501
   */
502
  public void setBaseUploadURL(String baseUploadURL) {
503
    this.baseUploadURL = fixBaseUrl(baseUploadURL);
1✔
504
  }
1✔
505

506
  /**
507
   * Returns the authorization URL which is used to perform the authorization_code based OAuth2
508
   * flow. The URL is created from {@link BoxAPIConnection#baseAuthorizationURL} and {@link
509
   * BoxAPIConnection#OAUTH_SUFFIX}.
510
   *
511
   * @param redirectUri the URL to which Box redirects the browser when authentication completes.
512
   * @param state the text string that you choose. Box sends the same string to your redirect URL
513
   *     when authentication is complete.
514
   * @param scopes this optional parameter identifies the Box scopes available to the application
515
   *     once it's authenticated.
516
   * @return the authorization URL
517
   */
518
  public URL getAuthorizationURL(URI redirectUri, String state, List<String> scopes) {
519
    return createFullAuthorizationUrl(
1✔
520
        this.baseAuthorizationURL, this.clientID, redirectUri, state, scopes);
521
  }
522

523
  /**
524
   * Sets authorization base URL which is used to perform the authorization_code based OAuth2 flow.
525
   *
526
   * @param baseAuthorizationURL Authorization URL. Default value is https://account.box.com/api/.
527
   */
528
  public void setBaseAuthorizationURL(String baseAuthorizationURL) {
529
    this.baseAuthorizationURL = fixBaseUrl(baseAuthorizationURL);
1✔
530
  }
1✔
531

532
  /**
533
   * Gets the user agent that's used when sending requests to the Box API.
534
   *
535
   * @return the user agent.
536
   */
537
  public String getUserAgent() {
538
    return this.userAgent;
1✔
539
  }
540

541
  /**
542
   * Sets the user agent to be used when sending requests to the Box API.
543
   *
544
   * @param userAgent the user agent.
545
   */
546
  public void setUserAgent(String userAgent) {
547
    this.userAgent = userAgent;
×
548
  }
×
549

550
  /**
551
   * Gets the base App url. Used for e.g. file requests.
552
   *
553
   * @return the base App Url.
554
   */
555
  public String getBaseAppUrl() {
556
    return this.baseAppURL;
1✔
557
  }
558

559
  /**
560
   * Sets the base App url. Used for e.g. file requests.
561
   *
562
   * @param baseAppURL a base App Url.
563
   */
564
  public void setBaseAppUrl(String baseAppURL) {
565
    this.baseAppURL = baseAppURL;
1✔
566
  }
1✔
567

568
  /**
569
   * Gets an access token that can be used to authenticate an API request. This method will
570
   * automatically refresh the access token if it has expired since the last call to <code>
571
   * getAccessToken()</code>.
572
   *
573
   * @return a valid access token that can be used to authenticate an API request.
574
   */
575
  public String getAccessToken() {
576
    if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) {
1✔
577
      this.refreshLock.writeLock().lock();
1✔
578
      try {
579
        if (this.needsRefresh()) {
1✔
580
          this.refresh();
1✔
581
        }
582
      } finally {
583
        this.refreshLock.writeLock().unlock();
1✔
584
      }
585
    }
586

587
    this.refreshLock.readLock().lock();
1✔
588
    try {
589
      return this.accessToken;
1✔
590
    } finally {
591
      this.refreshLock.readLock().unlock();
1✔
592
    }
593
  }
594

595
  /**
596
   * Sets the access token to use when authenticating API requests.
597
   *
598
   * @param accessToken a valid access token to use when authenticating API requests.
599
   */
600
  public void setAccessToken(String accessToken) {
601
    this.accessToken = accessToken;
1✔
602
  }
1✔
603

604
  /**
605
   * Gets the refresh lock to be used when refreshing an access token.
606
   *
607
   * @return the refresh lock.
608
   */
609
  protected ReadWriteLock getRefreshLock() {
610
    return this.refreshLock;
×
611
  }
612

613
  /**
614
   * Gets a refresh token that can be used to refresh an access token.
615
   *
616
   * @return a valid refresh token.
617
   */
618
  public String getRefreshToken() {
619
    return this.refreshToken;
1✔
620
  }
621

622
  /**
623
   * Sets the refresh token to use when refreshing an access token.
624
   *
625
   * @param refreshToken a valid refresh token.
626
   */
627
  public void setRefreshToken(String refreshToken) {
628
    this.refreshToken = refreshToken;
×
629
  }
×
630

631
  /**
632
   * Gets the last time that the access token was refreshed.
633
   *
634
   * @return the last refresh time in milliseconds.
635
   */
636
  public long getLastRefresh() {
637
    return this.lastRefresh;
1✔
638
  }
639

640
  /**
641
   * Sets the last time that the access token was refreshed.
642
   *
643
   * <p>This value is used when determining if an access token needs to be auto-refreshed. If the
644
   * amount of time since the last refresh exceeds the access token's expiration time, then the
645
   * access token will be refreshed.
646
   *
647
   * @param lastRefresh the new last refresh time in milliseconds.
648
   */
649
  public void setLastRefresh(long lastRefresh) {
650
    this.lastRefresh = lastRefresh;
1✔
651
  }
1✔
652

653
  /**
654
   * Gets whether or not automatic refreshing of this connection's access token is enabled. Defaults
655
   * to true.
656
   *
657
   * @return true if auto token refresh is enabled; otherwise false.
658
   */
659
  public boolean getAutoRefresh() {
660
    return this.autoRefresh;
1✔
661
  }
662

663
  /**
664
   * Enables or disables automatic refreshing of this connection's access token. Defaults to true.
665
   *
666
   * @param autoRefresh true to enable auto token refresh; otherwise false.
667
   */
668
  public void setAutoRefresh(boolean autoRefresh) {
669
    this.autoRefresh = autoRefresh;
1✔
670
  }
1✔
671

672
  /**
673
   * Gets the maximum number of times an API request will be retried after an error response is
674
   * received.
675
   *
676
   * @return the maximum number of request attempts.
677
   */
678
  public int getMaxRetryAttempts() {
679
    return this.maxRetryAttempts;
1✔
680
  }
681

682
  /**
683
   * Sets the maximum number of times an API request will be retried after an error response is
684
   * received.
685
   *
686
   * @param attempts the maximum number of request attempts.
687
   */
688
  public void setMaxRetryAttempts(int attempts) {
689
    this.maxRetryAttempts = attempts;
1✔
690
  }
1✔
691

692
  /**
693
   * Gets the connect timeout for this connection in milliseconds.
694
   *
695
   * @return the number of milliseconds to connect before timing out.
696
   */
697
  public int getConnectTimeout() {
698
    return this.connectTimeout;
1✔
699
  }
700

701
  /**
702
   * Sets the connect timeout for this connection.
703
   *
704
   * @param connectTimeout The number of milliseconds to wait for the connection to be established.
705
   */
706
  public void setConnectTimeout(int connectTimeout) {
707
    this.connectTimeout = connectTimeout;
1✔
708
    buildHttpClients();
1✔
709
  }
1✔
710

711
  /*
712
   * Gets if request use zstd encoding when possible
713
   * @return true if request use zstd encoding when possible
714
   */
715
  public boolean getUseZstdCompression() {
716
    return this.useZstdCompression;
×
717
  }
718

719
  /*
720
   * Sets if request use zstd encoding when possible
721
   * @param useZstdCompression true if request use zstd encoding when possible
722
   */
723
  public void setUseZstdCompression(boolean useZstdCompression) {
724
    this.useZstdCompression = useZstdCompression;
×
725
    buildHttpClients();
×
726
  }
×
727

728
  /**
729
   * Gets the read timeout for this connection in milliseconds.
730
   *
731
   * @return the number of milliseconds to wait for bytes to be read before timing out.
732
   */
733
  public int getReadTimeout() {
734
    return this.readTimeout;
1✔
735
  }
736

737
  /**
738
   * Sets the read timeout for this connection.
739
   *
740
   * @param readTimeout The number of milliseconds to wait for bytes to be read.
741
   */
742
  public void setReadTimeout(int readTimeout) {
743
    this.readTimeout = readTimeout;
1✔
744
    buildHttpClients();
1✔
745
  }
1✔
746

747
  /**
748
   * Gets the proxy value to use for API calls to Box.
749
   *
750
   * @return the current proxy.
751
   */
752
  public Proxy getProxy() {
753
    return this.proxy;
×
754
  }
755

756
  /**
757
   * Sets the proxy to use for API calls to Box.
758
   *
759
   * @param proxy the proxy to use for API calls to Box.
760
   */
761
  public void setProxy(Proxy proxy) {
762
    this.proxy = proxy;
1✔
763
    buildHttpClients();
1✔
764
  }
1✔
765

766
  /**
767
   * Gets the username to use for a proxy that requires basic auth.
768
   *
769
   * @return the username to use for a proxy that requires basic auth.
770
   */
771
  public String getProxyUsername() {
772
    return this.proxyUsername;
×
773
  }
774

775
  /**
776
   * Sets the username to use for a proxy that requires basic auth.
777
   *
778
   * @param proxyUsername the username to use for a proxy that requires basic auth.
779
   * @deprecated Use {@link BoxAPIConnection#setProxyBasicAuthentication(String, String)}
780
   */
781
  public void setProxyUsername(String proxyUsername) {
782
    this.proxyUsername = proxyUsername;
×
783
    buildHttpClients();
×
784
  }
×
785

786
  /**
787
   * Gets the password to use for a proxy that requires basic auth.
788
   *
789
   * @return the password to use for a proxy that requires basic auth.
790
   */
791
  public String getProxyPassword() {
792
    return this.proxyPassword;
×
793
  }
794

795
  /**
796
   * Sets the proxy user and password used in basic authentication
797
   *
798
   * @param proxyUsername Username to use for a proxy that requires basic auth.
799
   * @param proxyPassword Password to use for a proxy that requires basic auth.
800
   */
801
  public void setProxyBasicAuthentication(String proxyUsername, String proxyPassword) {
802
    this.proxyUsername = proxyUsername;
1✔
803
    this.proxyPassword = proxyPassword;
1✔
804
    buildHttpClients();
1✔
805
  }
1✔
806

807
  /**
808
   * Sets the password to use for a proxy that requires basic auth.
809
   *
810
   * @param proxyPassword the password to use for a proxy that requires basic auth.
811
   * @deprecated Use {@link BoxAPIConnection#setProxyBasicAuthentication(String, String)}
812
   */
813
  public void setProxyPassword(String proxyPassword) {
814
    this.proxyPassword = proxyPassword;
×
815
    buildHttpClients();
×
816
  }
×
817

818
  /**
819
   * Determines if this connection's access token can be refreshed. An access token cannot be
820
   * refreshed if a refresh token was never set.
821
   *
822
   * @return true if the access token can be refreshed; otherwise false.
823
   */
824
  public boolean canRefresh() {
825
    return this.refreshToken != null;
1✔
826
  }
827

828
  /**
829
   * Determines if this connection's access token has expired and needs to be refreshed.
830
   *
831
   * @return true if the access token needs to be refreshed; otherwise false.
832
   */
833
  public boolean needsRefresh() {
834
    boolean needsRefresh;
835

836
    this.refreshLock.readLock().lock();
1✔
837
    try {
838
      long now = System.currentTimeMillis();
1✔
839
      long tokenDuration = (now - this.lastRefresh);
1✔
840
      needsRefresh = (tokenDuration >= this.expires - REFRESH_EPSILON);
1✔
841
    } finally {
842
      this.refreshLock.readLock().unlock();
1✔
843
    }
844

845
    return needsRefresh;
1✔
846
  }
847

848
  /**
849
   * Refresh's this connection's access token using its refresh token.
850
   *
851
   * @throws IllegalStateException if this connection's access token cannot be refreshed.
852
   */
853
  public void refresh() {
854
    this.refreshLock.writeLock().lock();
1✔
855
    try {
856
      if (!this.canRefresh()) {
1✔
857
        throw new IllegalStateException(
×
858
            "The BoxAPIConnection cannot be refreshed because it doesn't have a "
859
                + "refresh token.");
860
      }
861

862
      URL url;
863
      try {
864
        url = new URL(getTokenURL());
1✔
865
      } catch (MalformedURLException e) {
×
866
        assert false : "An invalid refresh URL indicates a bug in the SDK.";
×
867
        throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e);
×
868
      }
1✔
869

870
      BoxAPIRequest request = createTokenRequest(url);
1✔
871

872
      String json;
873
      try (BoxAPIResponse boxAPIResponse = request.send()) {
1✔
874
        BoxJSONResponse response = (BoxJSONResponse) boxAPIResponse;
1✔
875
        json = response.getJSON();
1✔
876
      } catch (BoxAPIException e) {
×
877
        this.notifyError(e);
×
878
        throw e;
×
879
      }
1✔
880

881
      extractTokens(Json.parse(json).asObject());
1✔
882
      this.notifyRefresh();
1✔
883
    } finally {
884
      this.refreshLock.writeLock().unlock();
1✔
885
    }
886
  }
1✔
887

888
  /**
889
   * Restores a saved connection state into this BoxAPIConnection.
890
   *
891
   * @param state the saved state that was created with {@link #save}.
892
   * @see #save
893
   */
894
  public void restore(String state) {
895
    JsonObject json = Json.parse(state).asObject();
1✔
896
    String accessToken = json.get("accessToken").asString();
1✔
897
    String refreshToken = getKeyValueOrDefault(json, "refreshToken", null);
1✔
898
    long lastRefresh = json.get("lastRefresh").asLong();
1✔
899
    long expires = json.get("expires").asLong();
1✔
900
    String userAgent = json.get("userAgent").asString();
1✔
901
    String tokenURL = getKeyValueOrDefault(json, "tokenURL", null);
1✔
902
    String revokeURL = getKeyValueOrDefault(json, "revokeURL", null);
1✔
903
    String baseURL =
1✔
904
        adoptBaseUrlWhenLoadingFromOldVersion(
1✔
905
            getKeyValueOrDefault(json, "baseURL", DEFAULT_BASE_URL));
1✔
906
    String baseUploadURL =
1✔
907
        adoptUploadBaseUrlWhenLoadingFromOldVersion(
1✔
908
            getKeyValueOrDefault(json, "baseUploadURL", DEFAULT_BASE_UPLOAD_URL));
1✔
909
    String authorizationURL =
1✔
910
        getKeyValueOrDefault(json, "authorizationURL", DEFAULT_BASE_AUTHORIZATION_URL);
1✔
911
    boolean autoRefresh = json.get("autoRefresh").asBoolean();
1✔
912

913
    // Try to read deprecated value
914
    int maxRequestAttempts = -1;
1✔
915
    if (json.names().contains("maxRequestAttempts")) {
1✔
916
      maxRequestAttempts = json.get("maxRequestAttempts").asInt();
1✔
917
    }
918

919
    int maxRetryAttempts = -1;
1✔
920
    if (json.names().contains("maxRetryAttempts")) {
1✔
921
      maxRetryAttempts = json.get("maxRetryAttempts").asInt();
1✔
922
    }
923

924
    this.accessToken = accessToken;
1✔
925
    this.refreshToken = refreshToken;
1✔
926
    this.lastRefresh = lastRefresh;
1✔
927
    this.expires = expires;
1✔
928
    this.userAgent = userAgent;
1✔
929
    this.tokenURL = tokenURL;
1✔
930
    this.revokeURL = revokeURL;
1✔
931
    this.setBaseURL(baseURL);
1✔
932
    this.setBaseUploadURL(baseUploadURL);
1✔
933
    this.setBaseAuthorizationURL(authorizationURL);
1✔
934
    this.autoRefresh = autoRefresh;
1✔
935

936
    // Try to use deprecated value "maxRequestAttempts", else use newer value "maxRetryAttempts"
937
    if (maxRequestAttempts > -1) {
1✔
938
      this.maxRetryAttempts = maxRequestAttempts - 1;
1✔
939
    }
940
    if (maxRetryAttempts > -1) {
1✔
941
      this.maxRetryAttempts = maxRetryAttempts;
1✔
942
    }
943
  }
1✔
944

945
  private String adoptBaseUrlWhenLoadingFromOldVersion(String url) {
946
    if (url == null) {
1✔
947
      return null;
×
948
    }
949
    String urlEndingWithSlash = fixBaseUrl(url);
1✔
950
    return urlEndingWithSlash.equals("https://api.box.com/2.0/")
1✔
951
        ? DEFAULT_BASE_URL
1✔
952
        : urlEndingWithSlash;
1✔
953
  }
954

955
  private String adoptUploadBaseUrlWhenLoadingFromOldVersion(String url) {
956
    if (url == null) {
1✔
957
      return null;
×
958
    }
959
    String urlEndingWithSlash = fixBaseUrl(url);
1✔
960
    return urlEndingWithSlash.equals("https://upload.box.com/api/2.0/")
1✔
961
        ? DEFAULT_BASE_UPLOAD_URL
1✔
962
        : urlEndingWithSlash;
1✔
963
  }
964

965
  protected String getKeyValueOrDefault(JsonObject json, String key, String defaultValue) {
966
    return Optional.ofNullable(json.get(key))
1✔
967
        .filter(js -> !js.isNull())
1✔
968
        .map(JsonValue::asString)
1✔
969
        .orElse(defaultValue);
1✔
970
  }
971

972
  /** Notifies a refresh event to all the listeners. */
973
  protected void notifyRefresh() {
974
    for (BoxAPIConnectionListener listener : this.listeners) {
1✔
975
      listener.onRefresh(this);
×
976
    }
×
977
  }
1✔
978

979
  /**
980
   * Notifies an error event to all the listeners.
981
   *
982
   * @param error A BoxAPIException instance.
983
   */
984
  protected void notifyError(BoxAPIException error) {
985
    for (BoxAPIConnectionListener listener : this.listeners) {
×
986
      listener.onError(this, error);
×
987
    }
×
988
  }
×
989

990
  /**
991
   * Add a listener to listen to Box API connection events.
992
   *
993
   * @param listener a listener to listen to Box API connection.
994
   */
995
  public void addListener(BoxAPIConnectionListener listener) {
996
    this.listeners.add(listener);
×
997
  }
×
998

999
  /**
1000
   * Remove a listener listening to Box API connection events.
1001
   *
1002
   * @param listener the listener to remove.
1003
   */
1004
  public void removeListener(BoxAPIConnectionListener listener) {
1005
    this.listeners.remove(listener);
×
1006
  }
×
1007

1008
  /**
1009
   * Gets the RequestInterceptor associated with this API connection.
1010
   *
1011
   * @return the RequestInterceptor associated with this API connection.
1012
   */
1013
  public RequestInterceptor getRequestInterceptor() {
1014
    return this.interceptor;
1✔
1015
  }
1016

1017
  /**
1018
   * Sets a RequestInterceptor that can intercept requests and manipulate them before they're sent
1019
   * to the Box API.
1020
   *
1021
   * @param interceptor the RequestInterceptor.
1022
   */
1023
  public void setRequestInterceptor(RequestInterceptor interceptor) {
1024
    this.interceptor = interceptor;
1✔
1025
  }
1✔
1026

1027
  /**
1028
   * Get a lower-scoped token restricted to a resource for the list of scopes that are passed.
1029
   *
1030
   * @param scopes the list of scopes to which the new token should be restricted for
1031
   * @param resource the resource for which the new token has to be obtained
1032
   * @return scopedToken which has access token and other details
1033
   * @throws BoxAPIException if resource is not a valid Box API endpoint or shared link
1034
   */
1035
  public ScopedToken getLowerScopedToken(List<String> scopes, String resource) {
1036
    assert (scopes != null);
1✔
1037
    assert (scopes.size() > 0);
1✔
1038
    URL url;
1039
    try {
1040
      url = new URL(this.getTokenURL());
1✔
1041
    } catch (MalformedURLException e) {
×
1042
      assert false : "An invalid refresh URL indicates a bug in the SDK.";
×
1043
      throw new BoxAPIException("An invalid refresh URL indicates a bug in the SDK.", e);
×
1044
    }
1✔
1045

1046
    StringBuilder spaceSeparatedScopes = this.buildScopesForTokenDownscoping(scopes);
1✔
1047

1048
    String urlParameters =
1✔
1049
        format(
1✔
1050
            "grant_type=urn:ietf:params:oauth:grant-type:token-exchange"
1051
                + "&subject_token_type=urn:ietf:params:oauth:token-type:access_token&subject_token=%s"
1052
                + "&scope=%s",
1053
            this.getAccessToken(), spaceSeparatedScopes);
1✔
1054

1055
    if (resource != null) {
1✔
1056

1057
      ResourceLinkType resourceType = this.determineResourceLinkType(resource);
×
1058

1059
      if (resourceType == ResourceLinkType.APIEndpoint) {
×
1060
        urlParameters = format(urlParameters + "&resource=%s", resource);
×
1061
      } else if (resourceType == ResourceLinkType.SharedLink) {
×
1062
        urlParameters = format(urlParameters + "&box_shared_link=%s", resource);
×
1063
      } else if (resourceType == ResourceLinkType.Unknown) {
×
1064
        String argExceptionMessage = format("Unable to determine resource type: %s", resource);
×
1065
        BoxAPIException e = new BoxAPIException(argExceptionMessage);
×
1066
        this.notifyError(e);
×
1067
        throw e;
×
1068
      } else {
1069
        String argExceptionMessage = format("Unhandled resource type: %s", resource);
×
1070
        BoxAPIException e = new BoxAPIException(argExceptionMessage);
×
1071
        this.notifyError(e);
×
1072
        throw e;
×
1073
      }
1074
    }
1075

1076
    BoxAPIRequest request = new BoxAPIRequest(this, url, "POST");
1✔
1077
    request.shouldAuthenticate(false);
1✔
1078
    request.setBody(urlParameters);
1✔
1079

1080
    String jsonResponse;
1081
    try (BoxJSONResponse response = (BoxJSONResponse) request.send()) {
×
1082
      jsonResponse = response.getJSON();
×
1083
    } catch (BoxAPIException e) {
×
1084
      this.notifyError(e);
×
1085
      throw e;
×
1086
    }
×
1087

1088
    JsonObject jsonObject = Json.parse(jsonResponse).asObject();
×
1089
    ScopedToken token = new ScopedToken(jsonObject);
×
1090
    token.setObtainedAt(System.currentTimeMillis());
×
1091
    token.setExpiresIn(jsonObject.get("expires_in").asLong() * 1000);
×
1092
    return token;
×
1093
  }
1094

1095
  /**
1096
   * Convert List<String> to space-delimited String. Needed for versions prior to Java 8, which
1097
   * don't have String.join(delimiter, list)
1098
   *
1099
   * @param scopes the list of scopes to read from
1100
   * @return space-delimited String of scopes
1101
   */
1102
  private StringBuilder buildScopesForTokenDownscoping(List<String> scopes) {
1103
    StringBuilder spaceSeparatedScopes = new StringBuilder();
1✔
1104
    for (int i = 0; i < scopes.size(); i++) {
1✔
1105
      spaceSeparatedScopes.append(scopes.get(i));
1✔
1106
      if (i < scopes.size() - 1) {
1✔
1107
        spaceSeparatedScopes.append(" ");
×
1108
      }
1109
    }
1110

1111
    return spaceSeparatedScopes;
1✔
1112
  }
1113

1114
  /**
1115
   * Determines the type of resource, given a link to a Box resource.
1116
   *
1117
   * @param resourceLink the resource URL to check
1118
   * @return ResourceLinkType that categorizes the provided resourceLink
1119
   */
1120
  protected ResourceLinkType determineResourceLinkType(String resourceLink) {
1121

1122
    ResourceLinkType resourceType = ResourceLinkType.Unknown;
1✔
1123

1124
    try {
1125
      URL validUrl = new URL(resourceLink);
1✔
1126
      String validURLStr = validUrl.toString();
1✔
1127
      final String apiFilesEndpointPattern = ".*box.com/2.0/files/\\d+";
1✔
1128
      final String apiFoldersEndpointPattern = ".*box.com/2.0/folders/\\d+";
1✔
1129
      final String sharedLinkPattern = "(.*box.com/s/.*|.*box.com.*s=.*)";
1✔
1130

1131
      if (Pattern.matches(apiFilesEndpointPattern, validURLStr)
1✔
1132
          || Pattern.matches(apiFoldersEndpointPattern, validURLStr)) {
1✔
1133
        resourceType = ResourceLinkType.APIEndpoint;
1✔
1134
      } else if (Pattern.matches(sharedLinkPattern, validURLStr)) {
1✔
1135
        resourceType = ResourceLinkType.SharedLink;
1✔
1136
      }
1137
    } catch (MalformedURLException e) {
1✔
1138
      // Swallow exception and return default ResourceLinkType set at top of function
1139
    }
1✔
1140

1141
    return resourceType;
1✔
1142
  }
1143

1144
  /**
1145
   * Revokes the tokens associated with this API connection. This results in the connection no
1146
   * longer being able to make API calls until a fresh authorization is made by calling
1147
   * authenticate()
1148
   */
1149
  public void revokeToken() {
1150

1151
    URL url;
1152
    try {
1153
      url = new URL(getRevokeURL());
1✔
1154
    } catch (MalformedURLException e) {
×
1155
      assert false : "An invalid refresh URL indicates a bug in the SDK.";
×
1156
      throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e);
×
1157
    }
1✔
1158

1159
    String urlParameters =
1✔
1160
        format(
1✔
1161
            "token=%s&client_id=%s&client_secret=%s",
1162
            this.accessToken, this.clientID, this.clientSecret);
1163

1164
    BoxAPIRequest request = new BoxAPIRequest(this, url, "POST");
1✔
1165
    request.shouldAuthenticate(false);
1✔
1166
    request.setBody(urlParameters);
1✔
1167

1168
    request.send().close();
1✔
1169
  }
1✔
1170

1171
  /**
1172
   * Saves the state of this connection to a string so that it can be persisted and restored at a
1173
   * later time.
1174
   *
1175
   * <p>Note that proxy settings aren't automatically saved or restored. This is mainly due to
1176
   * security concerns around persisting proxy authentication details to the state string. If your
1177
   * connection uses a proxy, you will have to manually configure it again after restoring the
1178
   * connection.
1179
   *
1180
   * @return the state of this connection.
1181
   * @see #restore
1182
   */
1183
  public String save() {
1184
    JsonObject state =
1✔
1185
        new JsonObject()
1186
            .add("accessToken", this.accessToken)
1✔
1187
            .add("refreshToken", this.refreshToken)
1✔
1188
            .add("lastRefresh", this.lastRefresh)
1✔
1189
            .add("expires", this.expires)
1✔
1190
            .add("userAgent", this.userAgent)
1✔
1191
            .add("tokenURL", this.tokenURL)
1✔
1192
            .add("revokeURL", this.revokeURL)
1✔
1193
            .add("baseURL", this.baseURL)
1✔
1194
            .add("baseUploadURL", this.baseUploadURL)
1✔
1195
            .add("authorizationURL", this.baseAuthorizationURL)
1✔
1196
            .add("autoRefresh", this.autoRefresh)
1✔
1197
            .add("maxRetryAttempts", this.maxRetryAttempts);
1✔
1198
    return state.toString();
1✔
1199
  }
1200

1201
  String lockAccessToken() {
1202
    if (this.autoRefresh && this.canRefresh() && this.needsRefresh()) {
1✔
1203
      this.refreshLock.writeLock().lock();
×
1204
      try {
1205
        if (this.needsRefresh()) {
×
1206
          this.refresh();
×
1207
        }
1208
        this.refreshLock.readLock().lock();
×
1209
      } finally {
1210
        this.refreshLock.writeLock().unlock();
×
1211
      }
×
1212
    } else {
1213
      this.refreshLock.readLock().lock();
1✔
1214
    }
1215

1216
    return this.accessToken;
1✔
1217
  }
1218

1219
  void unlockAccessToken() {
1220
    this.refreshLock.readLock().unlock();
1✔
1221
  }
1✔
1222

1223
  /**
1224
   * Get the value for the X-Box-UA header.
1225
   *
1226
   * @return the header value.
1227
   */
1228
  String getBoxUAHeader() {
1229

1230
    return "agent=box-java-sdk/" + SDK_VERSION + "; env=Java/" + JAVA_VERSION;
1✔
1231
  }
1232

1233
  /**
1234
   * Sets a custom header to be sent on all requests through this API connection.
1235
   *
1236
   * @param header the header name.
1237
   * @param value the header value.
1238
   */
1239
  public void setCustomHeader(String header, String value) {
1240
    this.customHeaders.put(header, value);
1✔
1241
  }
1✔
1242

1243
  /**
1244
   * Removes a custom header, so it will no longer be sent on requests through this API connection.
1245
   *
1246
   * @param header the header name.
1247
   */
1248
  public void removeCustomHeader(String header) {
1249
    this.customHeaders.remove(header);
1✔
1250
  }
1✔
1251

1252
  /**
1253
   * Suppresses email notifications from API actions. This is typically used by security or admin
1254
   * applications to prevent spamming end users when doing automated processing on their content.
1255
   */
1256
  public void suppressNotifications() {
1257
    this.setCustomHeader(BOX_NOTIFICATIONS_HEADER, "off");
1✔
1258
  }
1✔
1259

1260
  /**
1261
   * Re-enable email notifications from API actions if they have been suppressed.
1262
   *
1263
   * @see #suppressNotifications
1264
   */
1265
  public void enableNotifications() {
1266
    this.removeCustomHeader(BOX_NOTIFICATIONS_HEADER);
1✔
1267
  }
1✔
1268

1269
  /**
1270
   * Set this API connection to make API calls on behalf of another users, impersonating them. This
1271
   * functionality can only be used by admins and service accounts.
1272
   *
1273
   * @param userID the ID of the user to act as.
1274
   */
1275
  public void asUser(String userID) {
1276
    this.setCustomHeader(AS_USER_HEADER, userID);
1✔
1277
  }
1✔
1278

1279
  /**
1280
   * Sets this API connection to make API calls on behalf of the user with whom the access token is
1281
   * associated. This undoes any previous calls to asUser().
1282
   *
1283
   * @see #asUser
1284
   */
1285
  public void asSelf() {
1286
    this.removeCustomHeader(AS_USER_HEADER);
1✔
1287
  }
1✔
1288

1289
  /**
1290
   * Used to override default SSL certification handling. For example, you can provide your own
1291
   * trust manager or hostname verifier to allow self-signed certificates. You can check examples <a
1292
   * href="https://github.com/box/box-java-sdk/blob/combined-sdk/docs/sdk/configuration.md#ssl-configuration">here</a>.
1293
   *
1294
   * @param trustManager TrustManager that verifies certificates are valid.
1295
   * @param hostnameVerifier HostnameVerifier that allows you to specify what hostnames are allowed.
1296
   */
1297
  public void configureSslCertificatesValidation(
1298
      X509TrustManager trustManager, HostnameVerifier hostnameVerifier) {
1299
    this.trustManager = trustManager;
1✔
1300
    this.hostnameVerifier = hostnameVerifier;
1✔
1301
    buildHttpClients();
1✔
1302
  }
1✔
1303

1304
  Map<String, String> getHeaders() {
1305
    return this.customHeaders;
1✔
1306
  }
1307

1308
  protected void extractTokens(JsonObject jsonObject) {
1309
    this.accessToken = jsonObject.get("access_token").asString();
1✔
1310
    this.refreshToken = jsonObject.get("refresh_token").asString();
1✔
1311
    this.lastRefresh = System.currentTimeMillis();
1✔
1312
    this.expires = jsonObject.get("expires_in").asLong() * 1000;
1✔
1313
  }
1✔
1314

1315
  protected BoxAPIRequest createTokenRequest(URL url) {
1316
    String urlParameters =
1✔
1317
        format(
1✔
1318
            "grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s",
1319
            this.refreshToken, this.clientID, this.clientSecret);
1320

1321
    BoxAPIRequest request = new BoxAPIRequest(this, url, "POST");
1✔
1322
    request.shouldAuthenticate(false);
1✔
1323
    request.setBody(urlParameters);
1✔
1324
    return request;
1✔
1325
  }
1326

1327
  private String fixBaseUrl(String baseUrl) {
1328
    return baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
1✔
1329
  }
1330

1331
  Response execute(Request request) {
1332
    return executeOnClient(httpClient, request);
1✔
1333
  }
1334

1335
  Response executeWithoutRedirect(Request request) {
1336
    return executeOnClient(noRedirectsHttpClient, request);
×
1337
  }
1338

1339
  protected Call createNewCall(OkHttpClient httpClient, Request request) {
1340
    return httpClient.newCall(request);
1✔
1341
  }
1342

1343
  private Response executeOnClient(OkHttpClient httpClient, Request request) {
1344
    try {
1345
      return createNewCall(httpClient, request).execute();
1✔
1346
    } catch (IOException e) {
1✔
1347
      throw new BoxAPIException(
1✔
1348
          "Couldn't connect to the Box API due to a network error. Request\n"
1349
              + toSanitizedRequest(request),
1✔
1350
          e);
1351
    }
1352
  }
1353

1354
  protected X509TrustManager getTrustManager() {
1355
    return trustManager;
×
1356
  }
1357

1358
  protected HostnameVerifier getHostnameVerifier() {
1359
    return hostnameVerifier;
×
1360
  }
1361

1362
  /** Used to categorize the types of resource links. */
1363
  protected enum ResourceLinkType {
1✔
1364
    /** Catch-all default for resource links that are unknown. */
1365
    Unknown,
1✔
1366

1367
    /**
1368
     * Resource URLs that point to an API endipoint such as https://api.box.com/2.0/files/:file_id.
1369
     */
1370
    APIEndpoint,
1✔
1371

1372
    /**
1373
     * Resource URLs that point to a resource that has been shared such as
1374
     * https://example.box.com/s/qwertyuiop1234567890asdfghjk or
1375
     * https://example.app.box.com/notes/0987654321?s=zxcvbnm1234567890asdfghjk.
1376
     */
1377
    SharedLink
1✔
1378
  }
1379

1380
  private Request toSanitizedRequest(Request originalRequest) {
1381
    Headers sanitizedHeaders = BoxSensitiveDataSanitizer.sanitizeHeaders(originalRequest.headers());
1✔
1382

1383
    return originalRequest.newBuilder().headers(sanitizedHeaders).build();
1✔
1384
  }
1385
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc