Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support proxy authentication with basic authentication scheme #32

Merged
merged 2 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ API client library for Kintone REST APIs on Java.
Add dependency declaration in `build.gradle` of your project.
```
dependencies {
implementation 'com.kintone:kintone-java-client:1.2.1'
implementation 'com.kintone:kintone-java-client:1.3.0'
}
```
- For projects using Maven
Expand All @@ -17,7 +17,7 @@ API client library for Kintone REST APIs on Java.
<dependency>
<groupId>com.kintone</groupId>
<artifactId>kintone-java-client</artifactId>
<version>1.2.1</version>
<version>1.3.0</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ plugins {
id 'com.github.hierynomus.license' version '0.15.0'
}

version = '1.2.1'
version = '1.3.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8

Expand Down
4 changes: 2 additions & 2 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ client.close();
Add dependency declaration in `build.gradle` of your project.
```groovy
dependencies {
implementation 'com.kintone:kintone-java-client:1.2.1'
implementation 'com.kintone:kintone-java-client:1.3.0'
}
```

Expand All @@ -39,7 +39,7 @@ Add dependency declaration in `pom.xml` of your project.
<dependency>
<groupId>com.kintone</groupId>
<artifactId>kintone-java-client</artifactId>
<version>1.2.1</version>
<version>1.3.0</version>
</dependency>
```

Expand Down
49 changes: 43 additions & 6 deletions src/main/java/com/kintone/client/InternalClientImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,23 +25,27 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.net.ssl.SSLContext;
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
import org.apache.hc.client5.http.config.RequestConfig;
import org.apache.hc.client5.http.entity.mime.HttpMultipartMode;
import org.apache.hc.client5.http.entity.mime.InputStreamBody;
import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder;
import org.apache.hc.client5.http.impl.auth.BasicScheme;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
import org.apache.hc.client5.http.protocol.HttpClientContext;
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder;
import org.apache.hc.core5.http.*;
import org.apache.hc.core5.http.io.SocketConfig;
import org.apache.hc.core5.http.io.entity.ByteArrayEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.ssl.TLS;
import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
import org.apache.hc.core5.pool.PoolReusePolicy;
Expand All @@ -55,12 +59,15 @@ class InternalClientImpl extends InternalClient {
private final Long guestId;
private final String userAgent;
private final JsonMapper mapper;
private final HttpHost proxyHost;
private final BasicScheme proxyAuth;

InternalClientImpl(
String baseUrl,
Auth auth,
BasicAuth basicAuth,
URI proxyHost,
BasicAuth proxyAuth,
SSLContext sslContext,
Long guestId,
String appendixUserAgent,
Expand All @@ -73,14 +80,16 @@ class InternalClientImpl extends InternalClient {
this.guestId = guestId;
this.userAgent = getUserAgent(appendixUserAgent);
this.mapper = new JsonMapper();
this.proxyHost = createProxyHost(proxyHost);
this.proxyAuth = createPreemptiveProxyAuth(proxyAuth);
this.httpClient =
createHttpClient(
sslContext, proxyHost, connectionTimeout, socketTimeout, connectionRequestTimeout);
sslContext, this.proxyHost, connectionTimeout, socketTimeout, connectionRequestTimeout);
}

private static CloseableHttpClient createHttpClient(
SSLContext sslContext,
URI proxyHost,
HttpHost proxyHost,
int connTimeout,
int socketTimeout,
int connRequestTimeout) {
Expand All @@ -89,9 +98,8 @@ private static CloseableHttpClient createHttpClient(
configBuilder.setConnectionRequestTimeout(connRequestTimeout, TimeUnit.MILLISECONDS);

if (proxyHost != null) {
HttpHost proxy =
new HttpHost(proxyHost.getScheme(), proxyHost.getHost(), proxyHost.getPort());
configBuilder.setProxy(proxy);
configBuilder.setProxy(proxyHost);
configBuilder.setProxyPreferredAuthSchemes(Collections.singleton("basic"));
}

PoolingHttpClientConnectionManager connectionManager =
Expand All @@ -114,6 +122,32 @@ private static CloseableHttpClient createHttpClient(
return clientBuilder.build();
}

private static HttpHost createProxyHost(URI proxyHost) {
if (proxyHost == null) {
return null;
}
return new HttpHost(proxyHost.getScheme(), proxyHost.getHost(), proxyHost.getPort());
}

private static BasicScheme createPreemptiveProxyAuth(BasicAuth proxyAuth) {
if (proxyAuth == null) {
return null;
}
BasicScheme basicScheme = new BasicScheme();
basicScheme.initPreemptive(
new UsernamePasswordCredentials(
proxyAuth.getUser(), proxyAuth.getPassword().toCharArray()));
return basicScheme;
}

private HttpContext createHttpContext() {
HttpClientContext context = HttpClientContext.create();
if (proxyHost != null && proxyAuth != null) {
context.resetAuthExchange(proxyHost, proxyAuth);
}
return context;
}

@Override
<T extends KintoneResponseBody> T call(
KintoneApi api, KintoneRequest body, List<ResponseHandler> handlers) {
Expand All @@ -134,6 +168,7 @@ <T extends KintoneResponseBody> T call(
try {
return httpClient.execute(
request,
createHttpContext(),
response -> {
KintoneResponse<T> result = parseJsonResponse(response, clazz);
applyHandlers(result, handlers);
Expand Down Expand Up @@ -185,6 +220,7 @@ BulkRequestsResponseBody bulkRequest(BulkRequestsRequest body, List<ResponseHand
try {
return httpClient.execute(
request,
createHttpContext(),
response -> {
KintoneResponse<BulkRequestsResponseBody> resp =
parseResponse(
Expand Down Expand Up @@ -212,7 +248,7 @@ DownloadFileResponseBody download(DownloadFileRequest request, List<ResponseHand
HttpUriRequest req = createJsonRequest(KintoneApi.DOWNLOAD_FILE.getMethod(), path, request);
KintoneResponse<DownloadFileResponseBody> r;
try {
CloseableHttpResponse response = httpClient.execute(req);
CloseableHttpResponse response = httpClient.execute(req, createHttpContext());
com.kintone.client.model.HttpResponse resp = new HttpResponseImpl(response);
r = parseResponse(response, stream -> new DownloadFileResponseBody(resp));
} catch (IOException e) {
Expand Down Expand Up @@ -247,6 +283,7 @@ KintoneResponse<UploadFileResponseBody> upload(
try {
return httpClient.execute(
httpRequest,
createHttpContext(),
response -> {
KintoneResponse<UploadFileResponseBody> r =
parseJsonResponse(response, UploadFileResponseBody.class);
Expand Down
16 changes: 16 additions & 0 deletions src/main/java/com/kintone/client/KintoneClientBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public class KintoneClientBuilder {
private String baseUrl;
private Auth auth;
private URI proxyHost;
private BasicAuth proxyAuth;
private SSLContext sslContext;
private int connectionTimeout = DEFAULT_TIMEOUT;
private int socketTimeout = DEFAULT_TIMEOUT;
Expand Down Expand Up @@ -177,6 +178,20 @@ public KintoneClientBuilder withProxy(String scheme, String hostname, int port)
return this;
}

/**
* Sets the proxy authentication settings. This is an optional setting. Note that this library
* only supports the proxy authentication with Basic authentication scheme. Other authentication
* schemes, such as Digest access authentication scheme, are not supported.
*
* @param user the user for proxy authentication
* @param password the password for proxy authentication
* @return this builder instance
*/
public KintoneClientBuilder setProxyAuthentication(String user, String password) {
this.proxyAuth = new BasicAuth(user, password);
return this;
}

/**
* Sets the certificate for client certificate authentication. This is an optional setting.
*
Expand Down Expand Up @@ -279,6 +294,7 @@ public KintoneClient build() {
auth,
basicAuth,
proxyHost,
proxyAuth,
sslContext,
guestSpaceId,
appendixUserAgent,
Expand Down
10 changes: 5 additions & 5 deletions src/test/java/com/kintone/client/InternalClientImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@ private InternalClientImpl setupClient() {
String baseUrl = "http://localhost:" + server.getLocalPort();
Auth auth = Auth.byPassword(userName, password);
return new InternalClientImpl(
baseUrl, auth, null, null, null, null, "", timeout, timeout, timeout);
baseUrl, auth, null, null, null, null, null, "", timeout, timeout, timeout);
}

private InternalClientImpl setupClient(long guestSpaceId) {
String baseUrl = "http://localhost:" + server.getLocalPort();
Auth auth = Auth.byPassword(userName, password);
return new InternalClientImpl(
baseUrl, auth, null, null, null, guestSpaceId, "", timeout, timeout, timeout);
baseUrl, auth, null, null, null, null, guestSpaceId, "", timeout, timeout, timeout);
}

private Record setupExpectedRecord() {
Expand Down Expand Up @@ -422,7 +422,7 @@ public void call_authByApiToken() {
Auth auth = Auth.byApiToken(apiToken);
InternalClientImpl sut =
new InternalClientImpl(
baseUrl, auth, null, null, null, null, "", timeout, timeout, timeout);
baseUrl, auth, null, null, null, null, null, "", timeout, timeout, timeout);
GetRecordRequest req = new GetRecordRequest().setApp(1L).setId(1L);
GetRecordResponseBody resp = sut.call(KintoneApi.GET_RECORD, req, Collections.emptyList());

Expand Down Expand Up @@ -455,7 +455,7 @@ public void call_authByApiTokenList() {
Auth auth = Auth.byApiToken(Arrays.asList(apiToken1, apiToken2));
InternalClientImpl sut =
new InternalClientImpl(
baseUrl, auth, null, null, null, null, "", timeout, timeout, timeout);
baseUrl, auth, null, null, null, null, null, "", timeout, timeout, timeout);

GetRecordRequest req = new GetRecordRequest().setApp(1L).setId(1L);
GetRecordResponseBody resp = sut.call(KintoneApi.GET_RECORD, req, Collections.emptyList());
Expand Down Expand Up @@ -489,7 +489,7 @@ public void call_authWithBasic() {
BasicAuth basicAuth = new BasicAuth("basicUser", "basicPassword");
InternalClientImpl sut =
new InternalClientImpl(
baseUrl, auth, basicAuth, null, null, null, "", timeout, timeout, timeout);
baseUrl, auth, basicAuth, null, null, null, null, "", timeout, timeout, timeout);
GetRecordRequest req = new GetRecordRequest().setApp(1L).setId(1L);
GetRecordResponseBody resp = sut.call(KintoneApi.GET_RECORD, req, Collections.emptyList());

Expand Down