Skip to content

Commit

Permalink
feat: Using OkHttp in Java SDK (#1083)
Browse files Browse the repository at this point in the history
Co-authored-by: Barbara Czyż <[email protected]>
  • Loading branch information
antusus and bszwarc authored Jan 17, 2023
1 parent a15edbf commit 2656698
Show file tree
Hide file tree
Showing 144 changed files with 4,216 additions and 5,939 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/build-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ name: build-main
on:
pull_request:
types: [ opened, synchronize ]
branches:
- main
push:
branches:
- main
Expand All @@ -29,4 +27,4 @@ jobs:
- name: Coverage
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: ./gradlew jacocoTestReport coverallsJacoco
run: ./gradlew jacocoTestReport coverallsJacoco
24 changes: 20 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ The Box Java SDK for interacting with the
## Latest Release
Latest release can be found [here](https://github.com/box/box-java-sdk/tree/v3.8.2).

## Upgrades
You can read about how to migrate to the new version [here](https://github.com/box/box-java-sdk/tree/v3.8.0/doc/upgrades).

## Versions
We use a modified version of [Semantic Versioning](https://semver.org/) for all changes. See [version strategy](VERSIONS.md) for details which is effective from 30 July 2022.

Expand Down Expand Up @@ -56,6 +59,14 @@ If you are developing application for Android visit our [Android guide](doc/andr
If you don't install this, you'll get an exception about key length or exception about parsing PKCS private key for Box Developer Edition. This is not a Box thing, this is a U.S. Government requirement concerning strong encryption.
The listed jar is for Oracle JRE. There might be other similar JARs for different JRE versions like the one below for IBM JDK
[Java Cryptography Extension for IBM JDK](https://www14.software.ibm.com/webapp/iwm/web/preLogin.do?source=jcesdk)
6. [okhttp v4.10.0](https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp/4.10.0)
Maven: `com.squareup.okhttp3:okhttp:4.10.0`
7. [okio-jvm v3.2.0](https://mvnrepository.com/artifact/com.squareup.okio/okio-jvm/3.2.0)
Maven: `com.squareup.okio:okio-jvm:3.2.0`
8. [kotlin-stdlib v1.6.20](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib/1.6.20)
Maven: `org.jetbrains.kotlin:kotlin-stdlib:1.6.20`
9. [kotlin-stdlib-common v1.6.20](https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-common/1.6.20)
Maven: `org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20`

An app has to be authorized by the admin of the enterprise before these tests. It's always good to begin with the
[Getting Started Section](https://developer.box.com/docs/setting-up-a-jwt-app) at Box's developer website.
Expand Down Expand Up @@ -161,16 +172,21 @@ To run the project, follow below steps

3. Provide the Id of the admin user (or any enterprise user) in `src/example/java/com/box/sdk/example/BoxDeveloperEditionAPIConnectionAsEnterpriseUser.java`.

```java
```java
public final class BoxDeveloperEditionAPIConnectionAsEnterpriseUser {

private static final String USER_ID = "";
// ...
Reader reader = new FileReader("src/example/config/config.json");
BoxConfig boxConfig = BoxConfig.readFrom(reader);

BoxDeveloperEditionAPIConnection api =
new BoxDeveloperEditionAPIConnection(USER_ID, DeveloperEditionEntityType.USER, boxConfig, accessTokenCache);
IAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(10);

BoxDeveloperEditionAPIConnection api = new BoxDeveloperEditionAPIConnection(
USER_ID,
DeveloperEditionEntityType.USER,
boxConfig,
accessTokenCache
);
}
```

Expand Down
11 changes: 1 addition & 10 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
}
because "v1.57 is compatible with org.bouncycastle:bc-fips:1.0.2.1 which is needed for FIPS compliance purposes"
}
implementation "com.squareup.okhttp3:okhttp:4.10.0"
testsCommonImplementation "junit:junit:4.13.2"
testsCommonImplementation "org.hamcrest:hamcrest-library:2.2"
testsCommonImplementation "org.mockito:mockito-core:4.8.0"
Expand Down Expand Up @@ -117,16 +118,6 @@ task integrationTest(type: Test) {
classpath = sourceSets.intTest.runtimeClasspath
}

//TODO: enable JWT tests
//task integrationTestJWT(type: Test) {
// description "Runs the JWT based integration tests."
// group "Verification"
// testLogging.showStandardStreams = true
// useJUnit {
// includeCategories "com.box.sdk.IntegrationTestJWT"
// }
//}

jacoco {
reportsDirectory = file("$buildDir/reports/jacoco")
}
Expand Down
2 changes: 1 addition & 1 deletion config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
<!-- Miscellaneous other checks. -->
<!-- See http://checkstyle.sf.net/config_misc.html -->
<module name="ArrayTypeStyle"/>
<module name="TodoComment"/>
<!-- <module name="TodoComment"/>-->
<module name="UpperEll"/>

<module name="ParameterAssignment"/>
Expand Down
15 changes: 8 additions & 7 deletions doc/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ object has been created you can use that to create an API connection.
```java
Reader reader = new FileReader("src/example/config/config.json");
BoxConfig boxConfig = BoxConfig.readFrom(reader);

BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig);
IAccessTokenCache tokenCache = new InMemoryLRUAccessTokenCache(100);
BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig, tokenCache);
```

It is also possible to get an API connection for an app user or managed user by doing something like this:
It is also possible to get an API connection for an app user or managed user:

<!-- sample x_auth init_with_jwt_with_user_id -->
```java
Reader reader = new FileReader("src/example/config/config.json");
BoxConfig boxConfig = BoxConfig.readFrom(reader);

InMemoryLRUAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(100);
IAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(100);
BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getUserConnection(userId, boxConfig, accessTokenCache);
```

Expand All @@ -85,7 +85,7 @@ jwtPreferences.setPrivateKeyPassword("PRIVATE-KEY-PASSWORD");
jwtPreferences.setPrivateKey("PRIVATE-KEY");
jwtPreferences.setEncryptionAlgorithm(EncryptionAlgorithm.RSA_SHA_256);

InMemoryLRUAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(100);
IAccessTokenCache accessTokenCache = new InMemoryLRUAccessTokenCache(100);
BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection
.getUserConnection("USER-ID", "CLIENT-ID","CLIENT-SECRET", jwtPreferences, accessTokenCache);

Expand All @@ -108,8 +108,9 @@ jwtPreferences.setPrivateKey("PRIVATE-KEY");
jwtPreferences.setEncryptionAlgorithm(EncryptionAlgorithm.RSA_SHA_256);

BoxConfig boxConfig = new BoxConfig("YOUR-CLIENT-ID", "YOUR-CLIENT-SECRET", "ENTERPRISE-ID", jwtPreferences);

BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig);
IAccessTokenCache tokenCache = new InMemoryLRUAccessTokenCache(10);

BoxDeveloperEditionAPIConnection api = BoxDeveloperEditionAPIConnection.getAppEnterpriseConnection(boxConfig, tokenCache);
```

### Standard 3-Legged Oauth 2.0
Expand Down
3 changes: 1 addition & 2 deletions doc/collaborations.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ collaboration.delete();
Get a Collaboration's Information
---------------------------------

Calling [`getInfo()`][get-info] on a collaboration returns a snapshot of the
Calling [`getInfo()`][get-info-fields] on a collaboration returns a snapshot of the
collaboration's info.

<!-- sample get_collaborations_id -->
Expand All @@ -106,7 +106,6 @@ BoxCollaboration collaboration = new BoxCollaboration(api, "id");
BoxCollaboration.Info info = collaboration.getInfo(BoxCollaboration.ALL_FIELDS);
```

[get-info]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxCollaboration.html#getInfo--
[get-info-fields]: http://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxCollaboration.html#getInfo-java.lang.String...-

Get the Collaborations on a Folder
Expand Down
149 changes: 144 additions & 5 deletions doc/configuration.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Configuration

- [Proxy configuration](#proxy-configuration)
- [Custom proxy authenticator](#custom-proxy-authenticator)
- [Example: NTLM authenticator](#example-ntlm-authenticator)
- [Configure retries of calls and timeouts](#configure-retries-of-calls-and-timeouts)
- [Maximum retries](#maximum-retries)
- [Connection timeout](#connection-timeout)
Expand All @@ -11,24 +13,123 @@
- [Base App URL](#base-app-url)
- [Token URL](#token-url-deprecated)
- [Revoke URL](#revoke-url-deprecated)
- [SSL configuration](#ssl-configuration)

# Proxy configuration

To set up proxy
use [BoxApiConnection.setProxy](https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAPIConnection.html#setProxy-java.net.Proxy-)
to set proxy address
and [BoxApiConnection.setProxyUsername](https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAPIConnection.html#setProxyUsername-java.lang.String-) /
[BoxApiConnection.setProxyPassword](https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAPIConnection.html#setProxyPassword-java.lang.String-)
and [BoxApiConnection.setProxyBasicAuthentication][set-basic-proxy-auth]
to set username and password required by proxy:

```java
BoxAPIConnection api=new BoxAPIConnection("access_token");
Proxy proxy=new Proxy(Proxy.Type.HTTP,new InetSocketAddress("proxy_url",8888));
Proxy proxy = new Proxy(Proxy.Type.HTTP,new InetSocketAddress("proxy_url",8888));
// You can use any subclass of BoxAPIConnection
api.setProxy(proxy);
api.setProxyUsername("proxyUsername");
api.setProxyPassword("proxyPassword");
api.setProxyBasicAuthentication("proxyUsername", "proxyPassword");
```
Proxy username and password will be used only if provided `SocketAddress` is an instance of
`InetSocketAddress`. If you would like to use a custom `SocketAddress` you can provide your own
`okhttp3.Authenticator` using [BoxApiConnection.setProxyAuthenticator(Authenticator)][set-proxy-authenticator]


## Custom proxy authenticator
By using [BoxApiConnection.setProxyBasicAuthentication][set-basic-proxy-auth] you can enable default
proxy authenticator that handles only Basic authentication. But you can provide your own authenticator by using
[BoxApiConnection.setProxyAuthenticator(Authenticator)][set-proxy-authenticator].

To do that you will need to add a dependency to your project:
```
"com.squareup.okhttp3:okhttp:XXX"
```
Please match the version with what SDK is using by checking `build.gradle`
and looking for entry `implementation "com.squareup.okhttp3:okhttp:"`.

Now you can add an authenticator. by calling

```java
BoxAPIConnection api = new BoxAPIConnection("access_token");
Proxy proxy = new Proxy(Proxy.Type.HTTP,new InetSocketAddress("proxy_url",8888));
api.setProxy(proxy);
api.setProxyAuthenticator((route, response) -> response
.request()
.newBuilder()
.addHeader("Proxy-Authorization", "My custom authenticator")
.build()
);
```

### Example: NTLM authenticator

For example, you can add NTLM authorization. This is example NTLM authenticator that
is using parts from Apache Http Client 5.

```java
import okhttp3.Authenticator;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import org.apache.hc.client5.http.impl.auth.NTLMEngineException;

public class NTLMAuthenticator implements Authenticator {
final NTLMEngineImpl engine = new NTLMEngineImpl();
private final String domain;
private final String username;
private final String password;
private final String ntlmMsg1;

public NTLMAuthenticator(String username, String password, String domain) {
this.domain = domain;
this.username = username;
this.password = password;
String localNtlmMsg1 = null;
try {
localNtlmMsg1 = engine.generateType1Msg(null, null);
} catch (Exception e) {
e.printStackTrace();
}
ntlmMsg1 = localNtlmMsg1;
}

@Override
public Request authenticate(Route route, Response response) {
if(response.code() == 407 && "Proxy authorization required".equals(response.message())) {
String ntlmChallenge = response.headers("Proxy-Authenticate")
.stream()
.filter(h -> h.startsWith("NTLM "))
.findFirst().orElse("");
if(ntlmChallenge.length() > 5) {
try {
String ntlmMsg3 = engine.generateType3Msg(username, password.toCharArray(), domain, "ok-http-example-ntlm", ntlmChallenge.substring(5));
return response.request().newBuilder().header("proxy-Authorization", "NTLM " + ntlmMsg3).build();
} catch (NTLMEngineException e) {
throw new RuntimeException(e);
}
}
return response.request().newBuilder().header("proxy-Authorization", "NTLM " + ntlmMsg1).build();
}
return response.request();
}
}
```

The `NTLMEngineImpl` could be created by using Apache implementation that can be found
[here](https://github.com/apache/httpcomponents-client/blob/master/httpclient5/src/main/java/org/apache/hc/client5/http/impl/auth/NTLMEngineImpl.java).
You can add a dependency to `org.apache.httpcomponents.client5:httpclient5:5.1.3`.
Copy the `NTLMEngineImpl` class and add it to your source.

Now you can use custom NTML Authenticator in your `BoxAPIConnection`:
```java
BoxAPIConnection api = new BoxAPIConnection("access_token");
Proxy proxy = new Proxy(Proxy.Type.HTTP,new InetSocketAddress("proxy_url",8888));
api.setProxy(proxy);
api.setProxyAuthenticator(new NTLMAuthenticator("some proxy username", "some proxy password", "proxy workgroup"));
```

[set-basic-proxy-auth]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAPIConnection.html#setProxyBasicAuthentication-java.lang.String-java.lang.String-
[set-proxy-authenticator]: https://opensource.box.com/box-java-sdk/javadoc/com/box/sdk/BoxAPIConnection.html#setProxyAuthenticator-okhttp3.Authenticator-

# Configure retries of calls and timeouts
SDK can retry failed calls when:
Expand Down Expand Up @@ -151,3 +252,41 @@ api.setRevokeURL("https://example.com/revoke");
```

If you use `setRevokeUrl` this URL will be used over the one coming from`setBaseUrl` when doing authentication.

# SSL configuration
You can override default settings used to verify SSL certificates.
This can be used to allow using self-signed certificates. For example:
```java
BoxAPIConnection api = new BoxAPIConnection(...);

// to allow self-signed certificates
X509TrustManager trustManager = new X509TrustManager() {
@Override
public void checkClientTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}

@Override
public void checkServerTrusted(java.security.cert.X509Certificate[] chain, String authType) {
}

@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return new java.security.cert.X509Certificate[]{};
}
};

// to allow self-signed certificates created for localhost
HostnameVerifier hostnameVerifier = (hostname, session) -> true;

api.configureSslCertificatesValidation(trustManager, hostnameVerifier);
```

If you just need to provide trust manager use `BoxAPIConnection.DEFAULT_HOSTNAME_VERIFIER` as a hostname verifier.
The same goes for hostname verifier. If you need just to provide it use
`BoxAPIConnection.DEFAULT_TRUST_MANAGER` as a trust manager.
Example:
```java
BoxAPIConnection api = new BoxAPIConnection(...);
X509TrustManager trustManager = ...
api.configureSslCertificatesValidation(trustManager, BoxAPIConnection.DEFAULT_HOSTNAME_VERIFIER);
```
Loading

0 comments on commit 2656698

Please sign in to comment.