io.signpath.signpathclient.api.http.SignPathApiHttpClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of api-client Show documentation
Show all versions of api-client Show documentation
A Client for the SignPath API
package io.signpath.signpathclient.api.http;
import com.squareup.moshi.JsonAdapter;
import com.squareup.moshi.Moshi;
import io.signpath.signpathclient.ClientSettings;
import io.signpath.signpathclient.SignPathClientException;
import io.signpath.signpathclient.api.model.SigningRequest;
import io.signpath.signpathclient.api.model.SigningRequestSubmitResponse;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
/**
*
* @author rober
*/
public class SignPathApiHttpClient {
private static final String UserAgentHeaderName = "User-Agent";
public static final String BASE_URL = "https://app.signpath.io/API/";
private static final String ARTIFACT_PARAM_NAME = "Artifact";
private static final int NUMBER_OF_RETRIES = 4;
private OkHttpClient okHttpClient;
private final String signPathApiBaseUrl;
private final ClientSettings clientSettings;
private final PrintStream logger;
public SignPathApiHttpClient(String signPathApiBaseUrl, PrintStream logger, ClientSettings clientSettings) {
this.signPathApiBaseUrl = signPathApiBaseUrl;
this.logger = logger;
this.clientSettings = clientSettings;
log("User agent: " + clientSettings.GetUserAgnet());
int requestTimeoutInSeconds = clientSettings.getUploadAndDownloadRequestTimeoutInSeconds();
this.okHttpClient = new OkHttpClient.Builder()
.connectTimeout(requestTimeoutInSeconds, TimeUnit.SECONDS)
.readTimeout(requestTimeoutInSeconds, TimeUnit.SECONDS)
.writeTimeout(requestTimeoutInSeconds, TimeUnit.SECONDS)
.callTimeout(requestTimeoutInSeconds, TimeUnit.SECONDS)
.build();
}
public SigningRequest getSigningRequestHttp(String apiToken,
String tbsToken,
String organizationId,
String signingRequestId) {
String url = "v1/{OrganizationId}/SigningRequests/{SigningRequestId}";
url = url.replace("{OrganizationId}", organizationId);
url = url.replace("{SigningRequestId}", signingRequestId);
Request request = new Request.Builder()
.url(this.signPathApiBaseUrl + url)
.addHeader("Authorization", this.buildAuthorizationHeaderValue(apiToken, tbsToken))
.addHeader(UserAgentHeaderName, this.clientSettings.GetUserAgnet())
.build();
okhttp3.Call call = okHttpClient.newCall(request);
okhttp3.Response response = executeWithRetryOkHttp(call);
SigningRequest signingRequest = new SigningRequest();
if (response.isSuccessful() && response.body() != null) {
try {
Moshi moshi = new Moshi.Builder().build();
JsonAdapter jsonAdapter = moshi.adapter(SigningRequest.class).lenient();
signingRequest = jsonAdapter.fromJson(response.body().string());
} catch (IOException ex) {
log(ex.getMessage(), ex);
}
}
return signingRequest;
}
public String submitSigningRequestHttp(
String apiToken,
String tbsToken,
String organizationId,
File artifact,
String projectSlug,
String signingPolicySlug,
String artifactConfigurationSlug,
String description,
Map origin) {
String url = "v1/{OrganizationId}/SigningRequests";
url = url.replace("{OrganizationId}", organizationId);
MultipartBody.Builder requestBuilder = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(ARTIFACT_PARAM_NAME, artifact.getName(),
RequestBody.create(MediaType.parse("application/octet-stream"), artifact))
.addFormDataPart("ProjectSlug", projectSlug)
.addFormDataPart("SigningPolicySlug", signingPolicySlug);
if (artifactConfigurationSlug != null && !artifactConfigurationSlug.isEmpty()) {
requestBuilder.addFormDataPart("ArtifactConfigurationSlug", artifactConfigurationSlug);
}
if (description != null && !description.isEmpty()) {
requestBuilder.addFormDataPart("Description", description);
}
if (origin != null) {
String prefix = "Origin.";
for (String key : origin.keySet()) {
String value = origin.get(key);
RequestBody originRequestBody;
if (value.startsWith("@")) {
String filePath = value.substring(1);
requestBuilder.addFormDataPart(prefix + key, ARTIFACT_PARAM_NAME,
RequestBody.create(MediaType.parse("application/octet-stream"), new File(filePath)));
} else {
requestBuilder.addFormDataPart(prefix + key, value);
}
}
}
RequestBody requestBody = requestBuilder.build();
Request request = new Request.Builder()
.url(this.signPathApiBaseUrl + url)
.addHeader("Authorization", this.buildAuthorizationHeaderValue(apiToken, tbsToken))
.addHeader(UserAgentHeaderName, this.clientSettings.GetUserAgnet())
.post(requestBody)
.build();
okhttp3.Call call = okHttpClient.newCall(request);
okhttp3.Response response = executeWithRetryOkHttp(call);
SigningRequestSubmitResponse signingRequestSubmitResponse = new SigningRequestSubmitResponse();
if (response.isSuccessful() && response.body() != null) {
try {
Moshi moshi = new Moshi.Builder().build();
JsonAdapter jsonAdapter = moshi.adapter(SigningRequestSubmitResponse.class).lenient();
signingRequestSubmitResponse = jsonAdapter.fromJson(response.body().string());
} catch (IOException ex) {
log(ex.getMessage(), ex);
}
}
return signingRequestSubmitResponse.getSigningRequestId();
}
public void downloadSignedArtifactHttp(
String apiToken,
String tbsToken,
String organizationId,
String signingRequestId,
File artifactTargetFile) {
String url = "v1/{OrganizationId}/SigningRequests/{SigningRequestId}/SignedArtifact";
url = url.replace("{OrganizationId}", organizationId);
url = url.replace("{SigningRequestId}", signingRequestId);
Request request = new Request.Builder()
.url(this.signPathApiBaseUrl + url)
.addHeader("Authorization", this.buildAuthorizationHeaderValue(apiToken, tbsToken))
.addHeader(UserAgentHeaderName, this.clientSettings.GetUserAgnet())
.build();
okhttp3.Call call = okHttpClient.newCall(request);
okhttp3.Response response = executeWithRetryOkHttp(call);
try ( InputStream stream = response.body().byteStream(); FileOutputStream outStream = new FileOutputStream(artifactTargetFile);) {
copyStream(stream, outStream);
outStream.close();
} catch (IOException ex) {
log(ex.getMessage(), ex);
throw new SignPathClientException("Artifact download error", ex);
}
}
private okhttp3.Response executeWithRetryOkHttp(okhttp3.Call call) throws SignPathClientException {
int numberOfTries = 0;
int delayInSeconds = 1;
okhttp3.Response lastResponse;
Exception lastException = null;
while (numberOfTries <= NUMBER_OF_RETRIES) {
if (numberOfTries > 0) {
log(String.format("Retry %s...",numberOfTries));
}
try {
// make sure we don't call execute more than once per call object
lastResponse = call.clone().execute();
if (!IsTransientHttpError(lastResponse.code())) {
// the response is either successful or not transient
// that's why there is no sense in trying again
// we return whatever we have
if (!lastResponse.isSuccessful()) {
throw new SignPathClientException(formatResponseError(lastResponse));
}
return lastResponse;
}
} catch (IOException ex) {
lastException = ex;
log("encountered io exception -> retry triggered", ex);
}
try {
Thread.sleep(delayInSeconds * 1000);
} catch (InterruptedException ex) {
log(ex.getMessage(), ex);
}
numberOfTries++;
delayInSeconds *= 3; // each try increases the delay three times
}
throw new SignPathClientException("SignPath API communication error.", lastException);
}
private String formatResponseError (okhttp3.Response response) {
log("No success response.");
String responseBody = "";
try {
responseBody = response.body().string();
} catch (IOException ex) {
log(ex.getMessage(), ex);
}
String additionalReason = "";
if (401 == response.code()) {
additionalReason = " Did you provide the correct ApiToken?";
}
else if(403 == response.code() && !responseBody.contains("feature is disabled")) {
additionalReason = " Did you add the CI user to the list of submitters in the specified signing policy? Did you provide the correct OrganizationId? In case you are using a trusted build system, did you link it to the specified project?";
}
String serverMessage = "";
if (!StringUtils.isEmpty(responseBody)) {
serverMessage = " (Server reported the following message: '" + responseBody + "')";
}
String errorMessage = String.format("Error %1$s.%2$s%3$s",
response.code(),additionalReason, serverMessage);
return errorMessage;
}
private String buildAuthorizationHeaderValue(String apiToken, String tbsToken) {
Validate.notEmpty(apiToken);
//Validate.notEmpty(tbsToken);
if (tbsToken == null || tbsToken.isEmpty()) {
return String.format("Bearer %s", apiToken);
}
return String.format("Bearer %s:%s", apiToken, tbsToken);
}
private static boolean IsTransientHttpError(int httpResponseCode) {
return httpResponseCode == 408 // Request Timeout
|| (httpResponseCode >= 500 && httpResponseCode < 600);
}
private void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buf = new byte[8192];
int length;
while ((length = input.read(buf)) > 0) {
output.write(buf, 0, length);
}
}
private void log(String message) {
log(message, null);
}
private void log(String message, Throwable ex) {
if (this.logger != null) {
this.logger.println(message);
if(ex != null) {
ex.printStackTrace(this.logger);
}
}
}
}