001package com.box.sdk; 002 003import java.net.MalformedURLException; 004import java.net.URL; 005 006import com.eclipsesource.json.JsonObject; 007 008/** 009 * Represents an authenticated connection to the Box API. 010 * 011 * <p>This class handles storing authentication information, automatic token refresh, and rate-limiting. It can also be 012 * used to configure the Box API endpoint URL in order to hit a different version of the API. Multiple instances of 013 * BoxAPIConnection may be created to support multi-user login.</p> 014 */ 015public class BoxAPIConnection { 016 /** 017 * The default maximum number of times an API request will be tried when an error occurs. 018 */ 019 public static final int DEFAULT_MAX_ATTEMPTS = 3; 020 021 private static final String TOKEN_URL_STRING = "https://www.box.com/api/oauth2/token"; 022 private static final String DEFAULT_BASE_URL = "https://api.box.com/2.0/"; 023 private static final String DEFAULT_BASE_UPLOAD_URL = "https://upload.box.com/api/2.0/"; 024 025 /** 026 * The amount of buffer time, in milliseconds, to use when determining if an access token should be refreshed. For 027 * example, if REFRESH_EPSILON = 60000 and the access token expires in less than one minute, it will be refreshed. 028 */ 029 private static final long REFRESH_EPSILON = 60000; 030 031 private final String clientID; 032 private final String clientSecret; 033 034 private long lastRefresh; 035 private long expires; 036 private String baseURL; 037 private String baseUploadURL; 038 private String accessToken; 039 private String refreshToken; 040 private boolean autoRefresh; 041 private int maxRequestAttempts; 042 043 /** 044 * Constructs a new BoxAPIConnection that authenticates with a developer or access token. 045 * @param accessToken a developer or access token to use for authenticating with the API. 046 */ 047 public BoxAPIConnection(String accessToken) { 048 this(null, null, accessToken, null); 049 } 050 051 /** 052 * Constructs a new BoxAPIConnection with an access token that can be refreshed. 053 * @param clientID the client ID to use when refreshing the access token. 054 * @param clientSecret the client secret to use when refreshing the access token. 055 * @param accessToken an initial access token to use for authenticating with the API. 056 * @param refreshToken an initial refresh token to use when refreshing the access token. 057 */ 058 public BoxAPIConnection(String clientID, String clientSecret, String accessToken, String refreshToken) { 059 this.clientID = clientID; 060 this.clientSecret = clientSecret; 061 this.accessToken = accessToken; 062 this.setRefreshToken(refreshToken); 063 this.baseURL = DEFAULT_BASE_URL; 064 this.baseUploadURL = DEFAULT_BASE_UPLOAD_URL; 065 this.autoRefresh = true; 066 this.maxRequestAttempts = DEFAULT_MAX_ATTEMPTS; 067 } 068 069 /** 070 * Constructs a new BoxAPIConnection with an auth code that was obtained from the first half of OAuth. 071 * @param clientID the client ID to use when exchanging the auth code for an access token. 072 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 073 * @param authCode an auth code obtained from the first half of the OAuth process. 074 */ 075 public BoxAPIConnection(String clientID, String clientSecret, String authCode) { 076 this(clientID, clientSecret, null, null); 077 078 URL url = null; 079 try { 080 url = new URL(TOKEN_URL_STRING); 081 } catch (MalformedURLException e) { 082 assert false : "An invalid token URL indicates a bug in the SDK."; 083 throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e); 084 } 085 086 String urlParameters = String.format("grant_type=authorization_code&code=%s&client_id=%s&client_secret=%s", 087 authCode, clientID, clientSecret); 088 089 BoxAPIRequest request = new BoxAPIRequest(url, "POST"); 090 request.addHeader("Content-Type", "application/x-www-form-urlencoded"); 091 request.setBody(urlParameters); 092 093 BoxJSONResponse response = (BoxJSONResponse) request.send(); 094 String json = response.getJSON(); 095 096 JsonObject jsonObject = JsonObject.readFrom(json); 097 this.accessToken = jsonObject.get("access_token").asString(); 098 this.setRefreshToken(jsonObject.get("refresh_token").asString()); 099 this.expires = jsonObject.get("expires_in").asLong() * 1000; 100 } 101 102 /** 103 * Sets the amount of time for which this connection's access token is valid before it must be refreshed. 104 * @param milliseconds the number of milliseconds for which the access token is valid. 105 */ 106 public void setExpires(long milliseconds) { 107 this.expires = milliseconds; 108 } 109 110 /** 111 * Gets the amount of time for which this connection's access token is valid. 112 * @return the amount of time in milliseconds. 113 */ 114 public long getExpires() { 115 return this.expires; 116 } 117 118 /** 119 * Gets the base URL that's used when sending requests to the Box API. The default value is 120 * "https://api.box.com/2.0/". 121 * @return the base URL. 122 */ 123 public String getBaseURL() { 124 return this.baseURL; 125 } 126 127 /** 128 * Sets the base URL to be used when sending requests to the Box API. For example, the default base URL is 129 * "https://api.box.com/2.0/". 130 * @param baseURL a base URL 131 */ 132 public void setBaseURL(String baseURL) { 133 this.baseURL = baseURL; 134 } 135 136 /** 137 * Gets the base upload URL that's used when performing file uploads to Box. 138 * @return the base upload URL. 139 */ 140 public String getBaseUploadURL() { 141 return this.baseUploadURL; 142 } 143 144 /** 145 * Sets the base upload URL to be used when performing file uploads to Box. 146 * @param baseUploadURL a base upload URL. 147 */ 148 public void setBaseUploadURL(String baseUploadURL) { 149 this.baseUploadURL = baseUploadURL; 150 } 151 152 /** 153 * Gets an access token that can be used to authenticate an API request. This method will automatically refresh the 154 * access token if it has expired since the last call to <code>getAccessToken()</code>. 155 * @return a valid access token that can be used to authenticate an API request. 156 */ 157 public String getAccessToken() { 158 if (this.canRefresh() && this.needsRefresh() && this.autoRefresh) { 159 this.refresh(); 160 } 161 162 return this.accessToken; 163 } 164 165 /** 166 * Sets the access token to use when authenticating API requests. 167 * @param accessToken a valid access token to use when authenticating API requests. 168 */ 169 public void setAccessToken(String accessToken) { 170 this.accessToken = accessToken; 171 } 172 173 /** 174 * Gets a refresh token that can be used to refresh an access token. 175 * @return a valid refresh token. 176 */ 177 public String getRefreshToken() { 178 return this.refreshToken; 179 } 180 181 /** 182 * Sets the refresh token to use when refreshing an access token. 183 * @param refreshToken a valid refresh token. 184 */ 185 public void setRefreshToken(String refreshToken) { 186 this.refreshToken = refreshToken; 187 this.lastRefresh = System.currentTimeMillis(); 188 } 189 190 /** 191 * Enables or disables automatic refreshing of this connection's access token. Defaults to true. 192 * @param autoRefresh true to enable auto token refresh; otherwise false. 193 */ 194 public void setAutoRefresh(boolean autoRefresh) { 195 this.autoRefresh = autoRefresh; 196 } 197 198 /** 199 * Gets whether or not automatic refreshing of this connection's access token is enabled. Defaults to true. 200 * @return true if auto token refresh is enabled; otherwise false. 201 */ 202 public boolean getAutoRefresh() { 203 return this.autoRefresh; 204 } 205 206 /** 207 * Gets the maximum number of times an API request will be tried when an error occurs. 208 * @return the maximum number of request attempts. 209 */ 210 public int getMaxRequestAttempts() { 211 return this.maxRequestAttempts; 212 } 213 214 /** 215 * Sets the maximum number of times an API request will be tried when an error occurs. 216 * @param attempts the maximum number of request attempts. 217 */ 218 public void setMaxRequestAttempts(int attempts) { 219 this.maxRequestAttempts = attempts; 220 } 221 222 /** 223 * Determines if this connection's access token can be refreshed. An access token cannot be refreshed if a refresh 224 * token was never set. 225 * @return true if the access token can be refreshed; otherwise false. 226 */ 227 public boolean canRefresh() { 228 return this.refreshToken != null; 229 } 230 231 /** 232 * Determines if this connection's access token has expired and needs to be refreshed. 233 * @return true if the access token needs to be refreshed; otherwise false. 234 */ 235 public boolean needsRefresh() { 236 if (this.expires == 0) { 237 return false; 238 } 239 240 long now = System.currentTimeMillis(); 241 long tokenDuration = (now - this.lastRefresh); 242 return (tokenDuration >= this.expires - REFRESH_EPSILON); 243 } 244 245 /** 246 * Refresh's this connection's access token using its refresh token. 247 * @throws IllegalStateException if this connection's access token cannot be refreshed. 248 */ 249 public void refresh() { 250 if (!this.canRefresh()) { 251 throw new IllegalStateException("The BoxAPIConnection cannot be refreshed because it doesn't have a " 252 + "refresh token."); 253 } 254 255 URL url = null; 256 try { 257 url = new URL(TOKEN_URL_STRING); 258 } catch (MalformedURLException e) { 259 assert false : "An invalid refresh URL indicates a bug in the SDK."; 260 throw new RuntimeException("An invalid refresh URL indicates a bug in the SDK.", e); 261 } 262 263 String urlParameters = String.format("grant_type=refresh_token&refresh_token=%s&client_id=%s&client_secret=%s", 264 this.refreshToken, this.clientID, this.clientSecret); 265 266 BoxAPIRequest request = new BoxAPIRequest(url, "POST"); 267 request.addHeader("Content-Type", "application/x-www-form-urlencoded"); 268 request.setBody(urlParameters); 269 270 BoxJSONResponse response = (BoxJSONResponse) request.send(); 271 String json = response.getJSON(); 272 273 JsonObject jsonObject = JsonObject.readFrom(json); 274 this.accessToken = jsonObject.get("access_token").asString(); 275 this.refreshToken = jsonObject.get("refresh_token").asString(); 276 this.expires = jsonObject.get("expires_in").asLong() * 1000; 277 } 278}