Skip to content

Commit e43acde

Browse files
authored
Merge pull request #106 from bunq/add_java_7_support_bunq/sdk_java#42
Add java 7 support #42
2 parents c0d16de + 0d01b2e commit e43acde

File tree

11 files changed

+130
-58
lines changed

11 files changed

+130
-58
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version '0.13.1'
33

44
apply plugin: 'java'
55
apply plugin: 'maven'
6-
sourceCompatibility = 1.8
6+
sourceCompatibility = 1.7
77

88
repositories {
99
mavenCentral()

src/main/java/com/bunq/sdk/context/ApiContext.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,9 +93,12 @@ private ApiContext(ApiEnvironmentType environmentType, String apiKey) {
9393
/**
9494
* Create and initialize an API Context with current IP as permitted and no proxy.
9595
*/
96-
public static ApiContext create(ApiEnvironmentType environmentType, String apiKey,
97-
String deviceDescription) {
98-
return create(environmentType, apiKey, deviceDescription, new ArrayList<>());
96+
public static ApiContext create(
97+
ApiEnvironmentType environmentType,
98+
String apiKey,
99+
String deviceDescription
100+
) {
101+
return create(environmentType, apiKey, deviceDescription, new ArrayList<String>());
99102
}
100103

101104
/**
@@ -111,7 +114,7 @@ public static ApiContext create(ApiEnvironmentType environmentType, String apiKe
111114
*/
112115
public static ApiContext create(ApiEnvironmentType environmentType, String apiKey,
113116
String deviceDescription, String proxy) {
114-
return create(environmentType, apiKey, deviceDescription, new ArrayList<>(), proxy);
117+
return create(environmentType, apiKey, deviceDescription, new ArrayList<String>(), proxy);
115118
}
116119

117120
/**

src/main/java/com/bunq/sdk/exception/ExceptionFactory.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,14 @@ public static ApiException createExceptionForResponse(int responseCode, List<Str
5656
}
5757

5858
private static String concatenateAllMessage(List<String> messages, String responseId) {
59-
return String.format(FORMAT_ERROR_MESSAGE, responseId, String.join(SEPARATOR_ERROR_MESSAGES, messages));
59+
StringBuilder stringBuilder = new StringBuilder();
60+
61+
for (String message : messages) {
62+
stringBuilder.append(SEPARATOR_ERROR_MESSAGES);
63+
stringBuilder.append(message);
64+
}
65+
66+
return String.format(FORMAT_ERROR_MESSAGE, responseId, stringBuilder.toString());
6067
}
6168

6269
}

src/main/java/com/bunq/sdk/http/ApiClient.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.bunq.sdk.exception.UncaughtExceptionError;
1111
import com.bunq.sdk.json.BunqGsonBuilder;
1212
import com.bunq.sdk.security.SecurityUtils;
13+
import com.bunq.sdk.util.BunqUtil;
1314
import com.google.gson.GsonBuilder;
1415
import com.google.gson.JsonArray;
1516
import com.google.gson.JsonElement;
@@ -206,7 +207,7 @@ public BunqResponseRaw post(
206207
/**
207208
*/
208209
private HttpUrl determineFullUri(String uri) {
209-
return determineFullUri(uri, new HashMap<>());
210+
return determineFullUri(uri, new HashMap<String, String>());
210211
}
211212

212213
/**
@@ -314,7 +315,13 @@ private static String getResponseId(Response response) {
314315

315316
if (headerMap.containsKey(HEADER_RESPONSE_ID_LOWER_CASE)) {
316317
return headerMap.get(HEADER_RESPONSE_ID_LOWER_CASE);
317-
} else return headerMap.getOrDefault(HEADER_RESPONSE_ID_UPPER_CASE, ERROR_COULD_NOT_DETERMINE_RESPONSE_ID);
318+
} else {
319+
return BunqUtil.getValueFromMapOrDefault(
320+
headerMap,
321+
HEADER_RESPONSE_ID_UPPER_CASE,
322+
ERROR_COULD_NOT_DETERMINE_RESPONSE_ID
323+
);
324+
}
318325
}
319326

320327
/**

src/main/java/com/bunq/sdk/http/BunqBasicHeader.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.bunq.sdk.http;
22

3-
public class BunqBasicHeader {
3+
public class BunqBasicHeader implements Comparable<BunqBasicHeader> {
44
private String name;
55
private String value;
66

@@ -23,4 +23,9 @@ public String getValue() {
2323
return value;
2424
}
2525

26+
@Override
27+
public int compareTo(BunqBasicHeader o) {
28+
return getName().compareTo(o.getName());
29+
}
30+
2631
}

src/main/java/com/bunq/sdk/model/core/DeviceServerInternal.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,25 @@
88

99
import java.util.HashMap;
1010
import java.util.List;
11-
import java.util.Map;
1211

1312
public class DeviceServerInternal extends DeviceServer {
14-
public static BunqResponse<Integer> create(ApiContext apiContext, String description, String secret, List<String> permittedIps) {
13+
public static BunqResponse<Integer> create(
14+
ApiContext apiContext,
15+
String description,
16+
String secret,
17+
List<String> permittedIps
18+
) {
1519
ApiClient apiClient = new ApiClient(apiContext);
1620
HashMap<String, Object> requestMap = new HashMap<>();
1721
requestMap.put(FIELD_DESCRIPTION, description);
1822
requestMap.put(FIELD_SECRET, secret);
1923
requestMap.put(FIELD_PERMITTED_IPS, permittedIps);
2024
byte[] requestBytes = gson.toJson(requestMap).getBytes();
21-
BunqResponseRaw responseRaw = apiClient.post(ENDPOINT_URL_CREATE, requestBytes, new HashMap<>());
25+
BunqResponseRaw responseRaw = apiClient.post(
26+
ENDPOINT_URL_CREATE,
27+
requestBytes,
28+
new HashMap<String, String>()
29+
);
2230

2331
return processForId(responseRaw);
2432
}

src/main/java/com/bunq/sdk/model/core/Installation.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ public static BunqResponse<Installation> create(ApiContext apiContext,
4242
String publicKeyClientString) {
4343
ApiClient apiClient = new ApiClient(apiContext);
4444
byte[] requestBytes = generateRequestBodyBytes(publicKeyClientString);
45-
BunqResponseRaw responseRaw = apiClient.post(ENDPOINT_URL_POST, requestBytes, new HashMap<>());
45+
BunqResponseRaw responseRaw = apiClient.post(
46+
ENDPOINT_URL_POST,
47+
requestBytes,
48+
new HashMap<String, String>()
49+
);
4650

4751
return fromJsonArrayNested(Installation.class, responseRaw);
4852
}

src/main/java/com/bunq/sdk/model/core/SessionServer.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,11 @@ public SessionServer(Id id, SessionToken sessionToken, UserPerson userPerson) {
5454
public static BunqResponse<SessionServer> create(ApiContext apiContext) {
5555
ApiClient apiClient = new ApiClient(apiContext);
5656
byte[] requestBytes = generateRequestBodyBytes(apiContext.getApiKey());
57-
BunqResponseRaw responseRaw = apiClient.post(ENDPOINT_URL_POST, requestBytes, new HashMap<>());
57+
BunqResponseRaw responseRaw = apiClient.post(
58+
ENDPOINT_URL_POST,
59+
requestBytes,
60+
new HashMap<String, String>()
61+
);
5862

5963
return fromJsonArrayNested(SessionServer.class, responseRaw);
6064
}

src/main/java/com/bunq/sdk/security/SecurityUtils.java

Lines changed: 63 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import javax.crypto.Mac;
1818
import javax.crypto.SecretKey;
1919
import javax.crypto.spec.IvParameterSpec;
20+
import javax.xml.bind.DatatypeConverter;
2021
import java.io.ByteArrayOutputStream;
2122
import java.io.IOException;
2223
import java.security.GeneralSecurityException;
@@ -35,12 +36,10 @@
3536
import java.security.spec.X509EncodedKeySpec;
3637
import java.util.ArrayList;
3738
import java.util.Arrays;
38-
import java.util.Base64;
3939
import java.util.List;
4040
import java.util.Map;
4141
import java.util.regex.Matcher;
4242
import java.util.regex.Pattern;
43-
import java.util.stream.Collectors;
4443

4544
/**
4645
* Static lib containing methods for handling encryption.
@@ -189,9 +188,9 @@ public static String getPrivateKeyFormattedString(KeyPair keyPair) {
189188
*/
190189
private static String getPrivateKeyFormattedString(PrivateKey privateKey) {
191190
byte[] encodedPrivateKey = privateKey.getEncoded();
192-
byte[] base64 = Base64.getEncoder().encode(encodedPrivateKey);
191+
String privateKeyString = DatatypeConverter.printBase64Binary(encodedPrivateKey);
193192

194-
return String.format(PRIVATE_KEY_FORMAT, new String(base64));
193+
return String.format(PRIVATE_KEY_FORMAT, privateKeyString);
195194
}
196195

197196
/**
@@ -230,7 +229,7 @@ private static byte[] getAllPublicKeyByteFromFormattedString(String publicKeyStr
230229
publicKeyString = publicKeyString.replace(PUBLIC_KEY_END, STRING_EMPTY);
231230
publicKeyString = publicKeyString.replace(NEWLINE, STRING_EMPTY);
232231

233-
return Base64.getDecoder().decode(publicKeyString);
232+
return DatatypeConverter.parseBase64Binary(publicKeyString);
234233
}
235234

236235
/**
@@ -257,7 +256,7 @@ private static byte[] getAllPrivateKeyByteFromFormattedString(String privateKeyS
257256
privateKeyString = privateKeyString.replace(PRIVATE_KEY_END, STRING_EMPTY);
258257
privateKeyString = privateKeyString.replace(NEWLINE, STRING_EMPTY);
259258

260-
return Base64.getDecoder().decode(privateKeyString);
259+
return DatatypeConverter.parseBase64Binary(privateKeyString);
261260
}
262261

263262
public static String getPublicKeyFormattedString(KeyPair keyPair) {
@@ -269,9 +268,9 @@ public static String getPublicKeyFormattedString(KeyPair keyPair) {
269268
*/
270269
public static String getPublicKeyFormattedString(PublicKey publicKey) {
271270
byte[] encodedPublicKey = publicKey.getEncoded();
272-
byte[] base64 = Base64.getEncoder().encode(encodedPublicKey);
271+
String publicKeyString = DatatypeConverter.printBase64Binary(encodedPublicKey);
273272

274-
return String.format(PUBLIC_KEY_FORMAT, new String(base64));
273+
return String.format(PUBLIC_KEY_FORMAT, publicKeyString);
275274
}
276275

277276
/**
@@ -308,22 +307,27 @@ private static byte[] generateInitializationVector() {
308307
return initializationVector;
309308
}
310309

311-
private static void addHeaderClientEncryptionKey(ApiContext apiContext, SecretKey key,
312-
Map<String, String> customHeaders) {
310+
private static void addHeaderClientEncryptionKey(
311+
ApiContext apiContext,
312+
SecretKey key,
313+
Map<String, String> customHeaders
314+
) {
313315
try {
314316
Cipher cipher = Cipher.getInstance(KEY_CIPHER_ALGORITHM);
315317
cipher.init(Cipher.ENCRYPT_MODE, apiContext.getInstallationContext().getPublicKeyServer());
316318
byte[] keyEncrypted = cipher.doFinal(key.getEncoded());
317-
String keyEncryptedEncoded = Base64.getEncoder().encodeToString(keyEncrypted);
319+
String keyEncryptedEncoded = DatatypeConverter.printBase64Binary(keyEncrypted);
318320
customHeaders.put(HEADER_CLIENT_ENCRYPTION_KEY, keyEncryptedEncoded);
319321
} catch (GeneralSecurityException exception) {
320322
throw new BunqException(exception.getMessage());
321323
}
322324
}
323325

324-
private static void addHeaderClientEncryptionIv(byte[] initializationVector, Map<String,
325-
String> customHeaders) {
326-
String initializationVectorEncoded = Base64.getEncoder().encodeToString(initializationVector);
326+
private static void addHeaderClientEncryptionIv(
327+
byte[] initializationVector,
328+
Map<String, String> customHeaders
329+
) {
330+
String initializationVectorEncoded = DatatypeConverter.printBase64Binary(initializationVector);
327331

328332
customHeaders.put(HEADER_CLIENT_ENCRYPTION_IV, initializationVectorEncoded);
329333
}
@@ -357,7 +361,7 @@ private static void addHeaderClientEncryptionHmac(byte[] requestBytes,
357361
bufferedSink.flush();
358362
bufferedSink.close();
359363
byte[] hmac = mac.doFinal();
360-
String hmacEncoded = Base64.getEncoder().encodeToString(hmac);
364+
String hmacEncoded = DatatypeConverter.printBase64Binary(hmac);
361365
customHeaders.put(HEADER_CLIENT_ENCRYPTION_HMAC, hmacEncoded);
362366
} catch (GeneralSecurityException | IOException exception) {
363367
throw new BunqException(exception.getMessage());
@@ -412,16 +416,27 @@ private static byte[] getEntityBodyBytes(BunqRequestBuilder requestBuilder) thro
412416
}
413417

414418
private static String generateRequestHeadersSortedString(BunqRequestBuilder bunqRequestBuilder) {
415-
return Arrays.stream(bunqRequestBuilder.getAllHeaderAsArray())
416-
.filter(
417-
header ->
418-
header.getName().startsWith(HEADER_NAME_PREFIX_X_BUNQ) ||
419-
header.getName().equals(ApiClient.HEADER_CACHE_CONTROL) ||
420-
header.getName().equals(ApiClient.HEADER_USER_AGENT)
421-
)
422-
.map(header -> header.getName() + DELIMITER_HEADER_NAME_AND_VALUE + header.getValue())
423-
.sorted()
424-
.collect(Collectors.joining(NEWLINE));
419+
StringBuilder stringBuilder = new StringBuilder();
420+
421+
BunqBasicHeader[] allHeadersAsArray = bunqRequestBuilder.getAllHeaderAsArray();
422+
Arrays.sort(allHeadersAsArray);
423+
424+
for (BunqBasicHeader header : allHeadersAsArray) {
425+
if (header.getName().startsWith(HEADER_NAME_PREFIX_X_BUNQ) ||
426+
header.getName().equals(ApiClient.HEADER_CACHE_CONTROL) ||
427+
header.getName().equals(ApiClient.HEADER_USER_AGENT)) {
428+
if (stringBuilder.length() != 0) {
429+
stringBuilder.append(NEWLINE);
430+
}
431+
432+
stringBuilder
433+
.append(header.getName())
434+
.append(DELIMITER_HEADER_NAME_AND_VALUE)
435+
.append(header.getValue());
436+
}
437+
}
438+
439+
return stringBuilder.toString();
425440
}
426441

427442
/**
@@ -439,7 +454,7 @@ private static String signBase64(byte[] bytesToSign, KeyPair keyPair) throws Bun
439454
byte[] dataBytesSigned = signDataWithSignature(signature, bytesToSign);
440455
verifyDataSigned(signature, keyPair.getPublic(), bytesToSign, dataBytesSigned);
441456

442-
return Base64.getEncoder().encodeToString(dataBytesSigned);
457+
return DatatypeConverter.printBase64Binary(dataBytesSigned);
443458
}
444459

445460
private static Signature getSignatureInstance() throws BunqException {
@@ -500,8 +515,9 @@ public static void validateResponseSignature(
500515
HEADER_SERVER_SIGNATURE,
501516
response.header(HEADER_SERVER_SIGNATURE)
502517
);
503-
byte[] serverSignatureBase64Bytes = headerServerSignature.getValue().getBytes();
504-
byte[] serverSignatureDecoded = Base64.getDecoder().decode(serverSignatureBase64Bytes);
518+
byte[] serverSignatureDecoded = DatatypeConverter.parseBase64Binary(
519+
headerServerSignature.getValue()
520+
);
505521
verifyDataSigned(signature, keyPublicServer, responseBytes, serverSignatureDecoded);
506522
}
507523

@@ -561,16 +577,25 @@ private static byte[] getResponseHeadBytes(int responseCode, BunqBasicHeader[] r
561577
return requestHeadString.getBytes();
562578
}
563579

564-
private static String generateResponseHeadersSortedString(BunqBasicHeader[] responseHeaders) {
565-
return Arrays.stream(responseHeaders)
566-
.filter(
567-
header ->
568-
header.getName().startsWith(HEADER_NAME_PREFIX_X_BUNQ) &&
569-
!header.getName().equals(HEADER_SERVER_SIGNATURE)
570-
)
571-
.map(header -> header.getName() + DELIMITER_HEADER_NAME_AND_VALUE + header.getValue())
572-
.sorted()
573-
.collect(Collectors.joining(NEWLINE));
580+
private static String generateResponseHeadersSortedString(BunqBasicHeader[] allResponseHeader) {
581+
StringBuilder stringBuilder = new StringBuilder();
582+
Arrays.sort(allResponseHeader);
583+
584+
for (BunqBasicHeader header : allResponseHeader) {
585+
if (header.getName().startsWith(HEADER_NAME_PREFIX_X_BUNQ) &&
586+
!header.getName().equals(HEADER_SERVER_SIGNATURE)) {
587+
if (stringBuilder.length() != 0) {
588+
stringBuilder.append(NEWLINE);
589+
}
590+
591+
stringBuilder
592+
.append(header.getName())
593+
.append(DELIMITER_HEADER_NAME_AND_VALUE)
594+
.append(header.getValue());
595+
}
596+
}
597+
598+
return stringBuilder.toString();
574599
}
575600

576601
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.bunq.sdk.util;
2+
3+
import java.util.Map;
4+
5+
public class BunqUtil {
6+
public static <T> T getValueFromMapOrDefault(Map<String, T> map, String key, T defaultValue) {
7+
if (map.containsKey(key)) {
8+
return map.get(key);
9+
} else {
10+
return defaultValue;
11+
}
12+
}
13+
14+
}

0 commit comments

Comments
 (0)