All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.signpath.signpathclient.api.http.SignPathApiHttpClient Maven / Gradle / Ivy

There is a newer version: 1.0.9
Show newest version
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);
            }
        }
    }
    
 
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy