• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Adyen / adyen-java-api-library / #2703

16 Oct 2023 09:08AM CUT coverage: 12.568%. First build
#2703

push

web-flow
Merge 5bb3765b7 into fe719ccb3

6014 of 6014 new or added lines in 86 files covered. (100.0%)

11852 of 94302 relevant lines covered (12.57%)

0.13 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/main/java/com/adyen/httpclient/AdyenHttpClient.java
1
/*
2
 *                       ######
3
 *                       ######
4
 * ############    ####( ######  #####. ######  ############   ############
5
 * #############  #####( ######  #####. ######  #############  #############
6
 *        ######  #####( ######  #####. ######  #####  ######  #####  ######
7
 * ###### ######  #####( ######  #####. ######  #####  #####   #####  ######
8
 * ###### ######  #####( ######  #####. ######  #####          #####  ######
9
 * #############  #############  #############  #############  #####  ######
10
 *  ############   ############  #############   ############  #####  ######
11
 *                                      ######
12
 *                               #############
13
 *                               ############
14
 *
15
 * Adyen Java API Library
16
 *
17
 * Copyright (c) 2021 Adyen B.V.
18
 * This file is open source and available under the MIT license.
19
 * See the LICENSE file for more info.
20
 */
21
package com.adyen.httpclient;
22

23
import com.adyen.Client;
24
import com.adyen.Config;
25
import com.adyen.constants.ApiConstants;
26
import com.adyen.enums.Environment;
27
import com.adyen.model.RequestOptions;
28
import com.adyen.terminal.security.TerminalCommonNameValidator;
29
import org.apache.commons.codec.binary.Base64;
30
import org.apache.hc.client5.http.classic.methods.HttpDelete;
31
import org.apache.hc.client5.http.classic.methods.HttpGet;
32
import org.apache.hc.client5.http.classic.methods.HttpPatch;
33
import org.apache.hc.client5.http.classic.methods.HttpPost;
34
import org.apache.hc.client5.http.classic.methods.HttpUriRequest;
35
import org.apache.hc.client5.http.classic.methods.HttpUriRequestBase;
36
import org.apache.hc.client5.http.config.RequestConfig;
37
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
38
import org.apache.hc.client5.http.impl.classic.HttpClients;
39
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
40
import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
41
import org.apache.hc.core5.http.HttpHost;
42
import org.apache.hc.core5.http.io.entity.StringEntity;
43
import org.apache.hc.core5.net.URIBuilder;
44

45
import javax.net.ssl.HostnameVerifier;
46
import javax.net.ssl.KeyManagerFactory;
47
import javax.net.ssl.SSLContext;
48
import javax.net.ssl.SSLPeerUnverifiedException;
49
import javax.net.ssl.TrustManager;
50
import javax.net.ssl.TrustManagerFactory;
51
import java.io.IOException;
52
import java.net.InetSocketAddress;
53
import java.net.Proxy;
54
import java.net.URI;
55
import java.net.URISyntaxException;
56
import java.nio.charset.Charset;
57
import java.security.GeneralSecurityException;
58
import java.security.KeyStore;
59
import java.security.cert.X509Certificate;
60
import java.util.Map;
61
import java.util.concurrent.TimeUnit;
62

63
import static com.adyen.constants.ApiConstants.HttpMethod.POST;
64
import static com.adyen.constants.ApiConstants.RequestProperty.ACCEPT_CHARSET;
65
import static com.adyen.constants.ApiConstants.RequestProperty.ADYEN_LIBRARY_NAME;
66
import static com.adyen.constants.ApiConstants.RequestProperty.ADYEN_LIBRARY_VERSION;
67
import static com.adyen.constants.ApiConstants.RequestProperty.API_KEY;
68
import static com.adyen.constants.ApiConstants.RequestProperty.APPLICATION_JSON_TYPE;
69
import static com.adyen.constants.ApiConstants.RequestProperty.CONTENT_TYPE;
70
import static com.adyen.constants.ApiConstants.RequestProperty.IDEMPOTENCY_KEY;
71
import static com.adyen.constants.ApiConstants.RequestProperty.REQUESTED_VERIFICATION_CODE_HEADER;
72
import static com.adyen.constants.ApiConstants.RequestProperty.USER_AGENT;
73

74
public class AdyenHttpClient implements ClientInterface {
×
75

76
    private static final String CHARSET = "UTF-8";
77
    private static final String TERMINAL_CERTIFICATE_ALIAS = "TerminalCertificate";
78
    private static final String SSL = "SSL";
79
    private static final String TLSV1_2 = "TLSv1.2";
80
    private Proxy proxy;
81

82
    public Proxy getProxy() {
83
        return proxy;
×
84
    }
85

86
    public void setProxy(Proxy proxy) {
87
        this.proxy = proxy;
×
88
    }
×
89

90
    @Override
91
    public String request(String endpoint, String requestBody, Config config) throws IOException, HTTPClientException {
92
        return request(endpoint, requestBody, config, false);
×
93
    }
94

95
    @Override
96
    public String request(String endpoint, String requestBody, Config config, boolean isApiKeyRequired) throws IOException, HTTPClientException {
97
        return request(endpoint, requestBody, config, isApiKeyRequired, null);
×
98
    }
99

100
    @Override
101
    public String request(String endpoint, String requestBody, Config config, boolean isApiKeyRequired, RequestOptions requestOptions) throws IOException, HTTPClientException {
102
        return request(endpoint, requestBody, config, isApiKeyRequired, requestOptions, POST);
×
103
    }
104

105
    @Override
106
    public String request(String endpoint, String requestBody, Config config, boolean isApiKeyRequired, RequestOptions requestOptions, ApiConstants.HttpMethod httpMethod) throws IOException, HTTPClientException {
107
        return request(endpoint, requestBody, config, isApiKeyRequired, requestOptions, httpMethod, null);
×
108
    }
109

110
    @Override
111
    public String request(String endpoint, String requestBody, Config config, boolean isApiKeyRequired, RequestOptions requestOptions, ApiConstants.HttpMethod httpMethod, Map<String, String> params) throws IOException, HTTPClientException {
112
        try (CloseableHttpClient httpclient = createCloseableHttpClient(config)) {
×
113
            HttpUriRequestBase httpRequest = createRequest(endpoint, requestBody, config, isApiKeyRequired, requestOptions, httpMethod, params);
×
114

115
            // Execute request with a custom response handler
116
            AdyenResponse response = httpclient.execute(httpRequest, new AdyenResponseHandler());
×
117

118
            if (response.getStatus() < 200 || response.getStatus() >= 300) {
×
119
                throw new HTTPClientException(response.getStatus(), "HTTP Exception", response.getHeaders(), response.getBody());
×
120
            }
121
            return response.getBody();
×
122
        }
123
    }
124

125
    private HttpUriRequestBase createRequest(String endpoint, String requestBody, Config config, boolean isApiKeyRequired, RequestOptions requestOptions, ApiConstants.HttpMethod httpMethod, Map<String, String> params) throws HTTPClientException {
126
        HttpUriRequestBase httpRequest = createHttpRequestBase(createUri(endpoint, params), requestBody, httpMethod);
×
127

128
        RequestConfig.Builder builder = RequestConfig.custom();
×
129
        if (config.getReadTimeoutMillis() > 0) {
×
130
            builder.setResponseTimeout(config.getReadTimeoutMillis(), TimeUnit.MILLISECONDS);
×
131
        }
132
        if (config.getConnectionTimeoutMillis() > 0) {
×
133
            builder.setConnectTimeout(config.getConnectionTimeoutMillis(), TimeUnit.MILLISECONDS);
×
134
        }
135
        if (proxy != null && proxy.address() instanceof InetSocketAddress) {
×
136
            InetSocketAddress inetSocketAddress = (InetSocketAddress) proxy.address();
×
137
            builder.setProxy(new HttpHost(inetSocketAddress.getHostName(), inetSocketAddress.getPort()));
×
138
        }
139
        httpRequest.setConfig(builder.build());
×
140

141
        setAuthentication(httpRequest, isApiKeyRequired, config);
×
142
        setHeaders(config, requestOptions, httpRequest);
×
143

144
        return httpRequest;
×
145
    }
146

147
    private void setHeaders(Config config, RequestOptions requestOptions, HttpUriRequestBase httpUriRequest) {
148

149
        setContentType(httpUriRequest, APPLICATION_JSON_TYPE);
×
150
        httpUriRequest.addHeader(ACCEPT_CHARSET, CHARSET);
×
151
        httpUriRequest.addHeader(USER_AGENT, String.format("%s %s/%s", config.getApplicationName(), Client.LIB_NAME, Client.LIB_VERSION));
×
152
        httpUriRequest.addHeader(ADYEN_LIBRARY_NAME, Client.LIB_NAME);
×
153
        httpUriRequest.addHeader(ADYEN_LIBRARY_VERSION, Client.LIB_VERSION);
×
154

155
        if (requestOptions != null) {
×
156
            if (requestOptions.getIdempotencyKey() != null) {
×
157
                httpUriRequest.addHeader(IDEMPOTENCY_KEY, requestOptions.getIdempotencyKey());
×
158
            }
159
            if (requestOptions.getRequestedVerificationCodeHeader() != null) {
×
160
                httpUriRequest.addHeader(REQUESTED_VERIFICATION_CODE_HEADER, requestOptions.getRequestedVerificationCodeHeader());
×
161
            }
162

163
            if (requestOptions.getAdditionalServiceHeaders() != null) {
×
164
                requestOptions.getAdditionalServiceHeaders().forEach(httpUriRequest::addHeader);
×
165
            }
166
        }
167
    }
×
168

169
    private HttpUriRequestBase createHttpRequestBase(URI endpoint, String requestBody, ApiConstants.HttpMethod httpMethod) {
170
        StringEntity requestEntity = null;
×
171
        if (requestBody != null && !requestBody.isEmpty()) {
×
172
            requestEntity = new StringEntity(requestBody, Charset.forName(CHARSET));
×
173
        }
174

175
        switch (httpMethod) {
×
176
            case GET:
177
                return new HttpGet(endpoint);
×
178
            case PATCH:
179
                HttpPatch httpPatch = new HttpPatch(endpoint);
×
180
                httpPatch.setEntity(requestEntity);
×
181
                return httpPatch;
×
182
            case DELETE:
183
                return new HttpDelete(endpoint);
×
184
            default:
185
                // Default to POST if httpMethod is not provided
186
                HttpPost httpPost = new HttpPost(endpoint);
×
187
                httpPost.setEntity(requestEntity);
×
188
                return httpPost;
×
189
        }
190
    }
191

192
    private URI createUri(String endpoint, Map<String, String> params) throws HTTPClientException {
193
        try {
194
            URIBuilder uriBuilder = new URIBuilder(endpoint);
×
195
            if (params != null && !params.isEmpty()) {
×
196
                for (String key: params.keySet()) {
×
197
                    uriBuilder.addParameter(key, params.get(key));
×
198
                }
×
199
            }
200
            return uriBuilder.build();
×
201
        } catch (URISyntaxException e) {
×
202
            throw new HTTPClientException("Invalid URI", e);
×
203
        }
204
    }
205

206
    private CloseableHttpClient createCloseableHttpClient(Config config) throws HTTPClientException {
207
        if (config.getClientKeyStore() != null && config.getTrustKeyStore() != null) {
×
208
            return createHttpClientWithSocketFactory(getClientCertificateAuthSSLContext(config));
×
209
        }
210
        if (config.getTerminalCertificate() != null) {
×
211
            return createHttpClientWithSocketFactory(getTerminalCertificateSocketFactory(config));
×
212
        }
213
        return HttpClients.createSystem();
×
214
    }
215

216
    private CloseableHttpClient createHttpClientWithSocketFactory(SSLConnectionSocketFactory socketFactory) {
217
        return HttpClients.custom()
×
218
                .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
×
219
                        .setSSLSocketFactory(socketFactory)
×
220
                        .build())
×
221
                .build();
×
222
    }
223

224
    private SSLConnectionSocketFactory getTerminalCertificateSocketFactory(Config config) throws HTTPClientException {
225
        try {
226
            // Create new KeyStore for the terminal certificate
227
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
×
228
            keyStore.load(null, null);
×
229
            keyStore.setCertificateEntry(TERMINAL_CERTIFICATE_ALIAS, config.getTerminalCertificate());
×
230

231
            TrustManagerFactory trustFactory =
232
                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
×
233
            trustFactory.init(keyStore);
×
234
            TrustManager[] trustManagers = trustFactory.getTrustManagers();
×
235

236
            // Install the terminal certificate trust manager
237
            SSLContext sc = SSLContext.getInstance(SSL);
×
238
            sc.init(null, trustManagers, new java.security.SecureRandom());
×
239

240
            return new SSLConnectionSocketFactory(sc, createHostnameVerifier(config.getEnvironment()));
×
241
        } catch (GeneralSecurityException | IOException e) {
×
242
            throw new HTTPClientException("Error loading certificate from path", e);
×
243
        }
244
    }
245

246
    private SSLConnectionSocketFactory getClientCertificateAuthSSLContext(Config config) throws HTTPClientException {
247
        try {
248
            char[] password = null;
×
249
            if (config.getClientKeyStorePassword() != null && !config.getClientKeyStorePassword().isEmpty()) {
×
250
                password = config.getClientKeyStorePassword().toCharArray();
×
251
            }
252

253
            // Create a TrustManager that trusts the CAs in our Trust KeyStore
254
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
×
255
            tmf.init(config.getTrustKeyStore());
×
256

257
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
×
258
            keyManagerFactory.init(config.getClientKeyStore(), password);
×
259

260
            // Create an SSLContext that uses our TrustManager
261
            SSLContext context = SSLContext.getInstance(TLSV1_2);
×
262
            context.init(keyManagerFactory.getKeyManagers(), tmf.getTrustManagers(), null);
×
263
            return new SSLConnectionSocketFactory(context);
×
264
        } catch (Exception e) {
×
265
            throw new HTTPClientException("Error creating SSL Context", e);
×
266
        }
267
    }
268

269
    private HostnameVerifier createHostnameVerifier(final Environment environment) {
270
        return (host, session) -> {
×
271
            try {
272
                if (session.getPeerCertificates() != null && session.getPeerCertificates().length > 0) {
×
273
                    // Assume the first certificate is the leaf, since chain will be ordered, according to Java documentation:
274
                    // https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/SSLSession.html#getPeerCertificates()
275
                    X509Certificate certificate = (X509Certificate) session.getPeerCertificates()[0];
×
276
                    return TerminalCommonNameValidator.validateCertificate(certificate, environment);
×
277
                }
278
                return false;
×
279
            } catch (SSLPeerUnverifiedException e) {
×
280
                e.printStackTrace();
×
281
                return false;
×
282
            }
283
        };
284
    }
285

286
    /**
287
     * Sets content type
288
     */
289
    private void setAuthentication(HttpUriRequest httpUriRequest, boolean isApiKeyRequired, Config config) {
290
        String apiKey = config.getApiKey();
×
291
        // Use Api key if required or if provided
292
        if (isApiKeyRequired || (apiKey != null && !apiKey.isEmpty())) {
×
293
            setApiKey(httpUriRequest, apiKey);
×
294
        } else {
295
            setBasicAuthentication(httpUriRequest, config.getUsername(), config.getPassword());
×
296
        }
297
    }
×
298

299
    /**
300
     * Sets content type
301
     */
302
    private void setContentType(HttpUriRequest httpUriRequest, String contentType) {
303
        httpUriRequest.addHeader(CONTENT_TYPE, contentType);
×
304
    }
×
305

306
    /**
307
     * Sets api key
308
     */
309
    private void setApiKey(HttpUriRequest httpUriRequest, String apiKey) {
310
        if (apiKey != null && !apiKey.isEmpty()) {
×
311
            httpUriRequest.addHeader(API_KEY, apiKey);
×
312
        }
313
    }
×
314

315
    /**
316
     * Adds Basic Authentication headers
317
     */
318
    private void setBasicAuthentication(HttpUriRequest httpUriRequest, String username, String password) {
319
        // set basic authentication
320
        String authString = username + ":" + password;
×
321
        byte[] authEncBytes = Base64.encodeBase64(authString.getBytes());
×
322
        String authStringEnc = new String(authEncBytes);
×
323

324
        httpUriRequest.addHeader("Authorization", "Basic " + authStringEnc);
×
325
    }
×
326
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc