001package com.box.sdk; 002 003import java.io.IOException; 004import java.io.StringReader; 005import java.net.MalformedURLException; 006import java.net.URL; 007import java.security.PrivateKey; 008 009import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; 010import org.bouncycastle.openssl.PEMDecryptorProvider; 011import org.bouncycastle.openssl.PEMEncryptedKeyPair; 012import org.bouncycastle.openssl.PEMKeyPair; 013import org.bouncycastle.openssl.PEMParser; 014import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; 015import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; 016import org.jose4j.jws.AlgorithmIdentifiers; 017import org.jose4j.jws.JsonWebSignature; 018import org.jose4j.jwt.JwtClaims; 019import org.jose4j.lang.JoseException; 020 021import com.eclipsesource.json.JsonObject; 022 023/** 024 * Represents an authenticated Box Developer Edition connection to the Box API. 025 * 026 * <p>This class handles everything for Box Developer Edition that isn't already handled by BoxAPIConnection.</p> 027 */ 028public class BoxDeveloperEditionAPIConnection extends BoxAPIConnection { 029 030 private static final String JWT_AUDIENCE = "https://api.box.com/oauth2/token"; 031 032 private final String entityID; 033 private final DeveloperEditionEntityType entityType; 034 private final EncryptionAlgorithm encryptionAlgorithm; 035 private final String publicKeyID; 036 private final String privateKey; 037 private final String privateKeyPassword; 038 039 /** 040 * Disabling an invalid constructor for Box Developer Edition. 041 * @param accessToken an initial access token to use for authenticating with the API. 042 */ 043 public BoxDeveloperEditionAPIConnection(String accessToken) { 044 super(null); 045 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 046 } 047 048 /** 049 * Disabling an invalid constructor for Box Developer Edition. 050 * @param clientID the client ID to use when refreshing the access token. 051 * @param clientSecret the client secret to use when refreshing the access token. 052 * @param accessToken an initial access token to use for authenticating with the API. 053 * @param refreshToken an initial refresh token to use when refreshing the access token. 054 */ 055 public BoxDeveloperEditionAPIConnection(String clientID, String clientSecret, String accessToken, 056 String refreshToken) { 057 058 super(null); 059 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 060 } 061 062 /** 063 * Disabling an invalid constructor for Box Developer Edition. 064 * @param clientID the client ID to use when exchanging the auth code for an access token. 065 * @param clientSecret the client secret to use when exchanging the auth code for an access token. 066 * @param authCode an auth code obtained from the first half of the OAuth process. 067 */ 068 public BoxDeveloperEditionAPIConnection(String clientID, String clientSecret, String authCode) { 069 super(null); 070 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 071 } 072 073 /** 074 * Disabling an invalid constructor for Box Developer Edition. 075 * @param clientID the client ID to use when requesting an access token. 076 * @param clientSecret the client secret to use when requesting an access token. 077 */ 078 public BoxDeveloperEditionAPIConnection(String clientID, String clientSecret) { 079 super(null); 080 throw new BoxAPIException("This constructor is not available for BoxDeveloperEditionAPIConnection."); 081 } 082 083 /** 084 * Constructs a new BoxDeveloperEditionAPIConnection. 085 * @param entityId enterprise ID or a user ID. 086 * @param entityType the type of entityId. 087 * @param clientID the client ID to use when exchanging the JWT assertion for an access token. 088 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 089 * @param encryptionPref the encryption preferences for signing the JWT. 090 */ 091 public BoxDeveloperEditionAPIConnection(String entityId, DeveloperEditionEntityType entityType, 092 String clientID, String clientSecret, JWTEncryptionPreferences encryptionPref) { 093 094 super(clientID, clientSecret); 095 096 this.entityID = entityId; 097 this.entityType = entityType; 098 this.publicKeyID = encryptionPref.getPublicKeyID(); 099 this.privateKey = encryptionPref.getPrivateKey(); 100 this.privateKeyPassword = encryptionPref.getPrivateKeyPassword(); 101 this.encryptionAlgorithm = encryptionPref.getEncryptionAlgorithm(); 102 } 103 104 /** 105 * Creates a new Box Developer Edition connection with enterprise token. 106 * @param enterpriseId the enterprise ID to use for requesting access token. 107 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 108 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 109 * @param encryptionPref the encryption preferences for signing the JWT. 110 * @return a new instance of BoxAPIConnection. 111 */ 112 public static BoxDeveloperEditionAPIConnection getAppEnterpriseConnection(String enterpriseId, String clientId, 113 String clientSecret, JWTEncryptionPreferences encryptionPref) { 114 115 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(enterpriseId, 116 DeveloperEditionEntityType.ENTERPRISE, clientId, clientSecret, encryptionPref); 117 118 connection.authenticate(); 119 120 return connection; 121 } 122 123 /** 124 * Creates a new Box Developer Edition connection with App User token. 125 * @param userId the user ID to use for an App User. 126 * @param clientId the client ID to use when exchanging the JWT assertion for an access token. 127 * @param clientSecret the client secret to use when exchanging the JWT assertion for an access token. 128 * @param encryptionPref the encryption preferences for signing the JWT. 129 * @return a new instance of BoxAPIConnection. 130 */ 131 public static BoxDeveloperEditionAPIConnection getAppUserConnection(String userId, String clientId, 132 String clientSecret, JWTEncryptionPreferences encryptionPref) { 133 134 BoxDeveloperEditionAPIConnection connection = new BoxDeveloperEditionAPIConnection(userId, 135 DeveloperEditionEntityType.USER, clientId, clientSecret, encryptionPref); 136 137 connection.authenticate(); 138 139 return connection; 140 } 141 142 /** 143 * Disabling the non-Box Developer Edition authenticate method. 144 * @param authCode an auth code obtained from the first half of the OAuth process. 145 */ 146 public void authenticate(String authCode) { 147 throw new BoxAPIException("BoxDeveloperEditionAPIConnection does not allow authenticating with an auth code."); 148 } 149 150 /** 151 * Authenticates the API connection for Box Developer Edition. 152 */ 153 public void authenticate() { 154 URL url = null; 155 try { 156 url = new URL(this.getTokenURL()); 157 } catch (MalformedURLException e) { 158 assert false : "An invalid token URL indicates a bug in the SDK."; 159 throw new RuntimeException("An invalid token URL indicates a bug in the SDK.", e); 160 } 161 162 String jwtAssertion = this.constructJWTAssertion(); 163 164 String urlParameters = String.format("grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" 165 + "&client_id=%s&client_secret=%s&assertion=%s", 166 this.getClientID(), this.getClientSecret(), jwtAssertion); 167 168 BoxAPIRequest request = new BoxAPIRequest(this, url, "POST"); 169 request.shouldAuthenticate(false); 170 request.setBody(urlParameters); 171 172 BoxJSONResponse response = (BoxJSONResponse) request.send(); 173 String json = response.getJSON(); 174 175 JsonObject jsonObject = JsonObject.readFrom(json); 176 this.setAccessToken(jsonObject.get("access_token").asString()); 177 this.setLastRefresh(System.currentTimeMillis()); 178 this.setExpires(jsonObject.get("expires_in").asLong() * 1000); 179 } 180 181 /** 182 * BoxDeveloperEditionAPIConnection can always refresh, but this method is required elsewhere. 183 * @return true always. 184 */ 185 public boolean canRefresh() { 186 return true; 187 } 188 189 /** 190 * Refresh's this connection's access token using Box Developer Edition. 191 * @throws IllegalStateException if this connection's access token cannot be refreshed. 192 */ 193 public void refresh() { 194 this.getRefreshLock().writeLock().lock(); 195 196 try { 197 this.authenticate(); 198 } catch (BoxAPIException e) { 199 this.notifyError(e); 200 this.getRefreshLock().writeLock().unlock(); 201 throw e; 202 } 203 204 this.notifyRefresh(); 205 this.getRefreshLock().writeLock().unlock(); 206 } 207 208 private String constructJWTAssertion() { 209 JwtClaims claims = new JwtClaims(); 210 claims.setIssuer(this.getClientID()); 211 claims.setAudience(JWT_AUDIENCE); 212 claims.setExpirationTimeMinutesInTheFuture(1.0f); 213 claims.setSubject(this.entityID); 214 claims.setClaim("box_sub_type", this.entityType.toString()); 215 claims.setGeneratedJwtId(64); 216 217 JsonWebSignature jws = new JsonWebSignature(); 218 jws.setPayload(claims.toJson()); 219 jws.setKey(this.decryptPrivateKey()); 220 jws.setAlgorithmHeaderValue(this.getAlgorithmIdentifier()); 221 jws.setHeader("typ", "JWT"); 222 if ((this.publicKeyID != null) && !this.publicKeyID.isEmpty()) { 223 jws.setHeader("kid", this.publicKeyID); 224 } 225 226 String assertion; 227 228 try { 229 assertion = jws.getCompactSerialization(); 230 } catch (JoseException e) { 231 throw new BoxAPIException("Error serializing JSON Web Token assertion.", e); 232 } 233 234 return assertion; 235 } 236 237 private String getAlgorithmIdentifier() { 238 String algorithmId = AlgorithmIdentifiers.RSA_USING_SHA256; 239 switch (this.encryptionAlgorithm) { 240 case RSA_SHA_384: 241 algorithmId = AlgorithmIdentifiers.RSA_USING_SHA384; 242 break; 243 case RSA_SHA_512: 244 algorithmId = AlgorithmIdentifiers.RSA_USING_SHA512; 245 break; 246 case RSA_SHA_256: 247 default: 248 break; 249 } 250 251 return algorithmId; 252 } 253 254 private PrivateKey decryptPrivateKey() { 255 PrivateKey decryptedPrivateKey; 256 257 try { 258 PEMParser keyReader = new PEMParser(new StringReader(this.privateKey)); 259 Object keyPair = keyReader.readObject(); 260 keyReader.close(); 261 262 if (keyPair instanceof PEMEncryptedKeyPair) { 263 JcePEMDecryptorProviderBuilder builder = new JcePEMDecryptorProviderBuilder(); 264 PEMDecryptorProvider decryptionProvider = builder.build(this.privateKeyPassword.toCharArray()); 265 keyPair = ((PEMEncryptedKeyPair) keyPair).decryptKeyPair(decryptionProvider); 266 } 267 268 PrivateKeyInfo keyInfo = ((PEMKeyPair) keyPair).getPrivateKeyInfo(); 269 decryptedPrivateKey = (new JcaPEMKeyConverter()).getPrivateKey(keyInfo); 270 } catch (IOException e) { 271 throw new BoxAPIException("Error parsing private key for Box Developer Edition.", e); 272 } 273 274 return decryptedPrivateKey; 275 } 276}