diff --git a/pom.xml b/pom.xml
index 4637e30d..bf211285 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
com.uid2
uid2-shared
- 7.19.0
+ 7.19.2-alpha-151-SNAPSHOT
${project.groupId}:${project.artifactId}
Library for all the shared uid2 operations
https://github.com/IABTechLab/uid2docs
@@ -285,6 +285,9 @@
org.apache.maven.plugins
maven-surefire-plugin
3.2.5
+
+ -XX:+EnableDynamicAgentLoading
+
org.sonatype.plugins
diff --git a/src/main/java/com/uid2/shared/attest/AttestationResponseCode.java b/src/main/java/com/uid2/shared/attest/AttestationResponseCode.java
new file mode 100644
index 00000000..38359b25
--- /dev/null
+++ b/src/main/java/com/uid2/shared/attest/AttestationResponseCode.java
@@ -0,0 +1,7 @@
+package com.uid2.shared.attest;
+
+public enum AttestationResponseCode {
+ AttestationFailure,
+ RetryableFailure,
+ Success
+}
diff --git a/src/main/java/com/uid2/shared/attest/AttestationResponseHandler.java b/src/main/java/com/uid2/shared/attest/AttestationResponseHandler.java
index ff230cd4..183a4f29 100644
--- a/src/main/java/com/uid2/shared/attest/AttestationResponseHandler.java
+++ b/src/main/java/com/uid2/shared/attest/AttestationResponseHandler.java
@@ -7,6 +7,7 @@
import io.vertx.core.Vertx;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonObject;
+import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.utils.Pair;
@@ -34,7 +35,7 @@ public class AttestationResponseHandler {
private final AtomicReference attestationToken;
private final AtomicReference optOutJwt;
private final AtomicReference coreJwt;
- private final Handler> responseWatcher;
+ private final Handler> responseWatcher;
private final String attestationEndpoint;
private final byte[] encodedAttestationEndpoint;
private final IClock clock;
@@ -46,6 +47,7 @@ public class AttestationResponseHandler {
private Instant attestationTokenExpiresAt = Instant.MAX;
private final Lock lock;
private final AttestationTokenDecryptor attestationTokenDecryptor;
+ @Getter
private final String appVersionHeader;
private final int attestCheckMilliseconds;
private final AtomicReference optOutUrl;
@@ -56,17 +58,18 @@ public AttestationResponseHandler(Vertx vertx,
String operatorType,
ApplicationVersion appVersion,
IAttestationProvider attestationProvider,
- Handler> responseWatcher,
+ Handler> responseWatcher,
Proxy proxy) {
this(vertx, attestationEndpoint, clientApiToken, operatorType, appVersion, attestationProvider, responseWatcher, proxy, new InstantClock(), null, null, 60000);
}
+
public AttestationResponseHandler(Vertx vertx,
String attestationEndpoint,
String clientApiToken,
String operatorType,
ApplicationVersion appVersion,
IAttestationProvider attestationProvider,
- Handler> responseWatcher,
+ Handler> responseWatcher,
Proxy proxy,
IClock clock,
URLConnectionHttpClient httpClient,
@@ -131,11 +134,7 @@ private void attestationExpirationCheck(long timerId) {
}
attest();
- } catch (AttestationResponseHandlerException e) {
- notifyResponseWatcher(401, e.getMessage());
- LOGGER.info("Re-attest failed: ", e);
- } catch (IOException e){
- notifyResponseWatcher(500, e.getMessage());
+ } catch (AttestationResponseHandlerException | IOException e) {
LOGGER.info("Re-attest failed: ", e);
} finally {
this.isAttesting.set(false);
@@ -180,30 +179,32 @@ public void attest() throws IOException, AttestationResponseHandlerException {
int statusCode = response.statusCode();
String responseBody = response.body();
- notifyResponseWatcher(statusCode, responseBody);
- if (statusCode < 200 || statusCode >= 300) {
- LOGGER.warn("attestation failed with UID2 Core returning statusCode={}", statusCode);
- throw new AttestationResponseHandlerException(statusCode, "unexpected status code from uid core service");
+ AttestationResponseCode responseCode = this.getAttestationResponseCodeFromHttpStatus(statusCode);
+
+ notifyResponseWatcher(responseCode, responseBody);
+
+ if (responseCode != AttestationResponseCode.Success) {
+ throw new AttestationResponseHandlerException(responseCode, "Non-success response from Core on attest");
}
JsonObject responseJson = (JsonObject) Json.decodeValue(responseBody);
if (isFailed(responseJson)) {
- throw new AttestationResponseHandlerException(statusCode, "response did not return a successful status");
+ throw new AttestationResponseHandlerException(AttestationResponseCode.RetryableFailure, "response did not return a successful status");
}
JsonObject innerBody = responseJson.getJsonObject("body");
if (innerBody == null) {
- throw new AttestationResponseHandlerException(statusCode, "response did not contain a body object");
+ throw new AttestationResponseHandlerException(AttestationResponseCode.RetryableFailure, "response did not contain a body object");
}
String atoken = getAttestationToken(innerBody);
if (atoken == null) {
- throw new AttestationResponseHandlerException(statusCode, "response json does not contain body.attestation_token");
+ throw new AttestationResponseHandlerException(AttestationResponseCode.RetryableFailure, "response json does not contain body.attestation_token");
}
String expiresAt = getAttestationTokenExpiresAt(innerBody);
if (expiresAt == null) {
- throw new AttestationResponseHandlerException(statusCode, "response json does not contain body.expiresAt");
+ throw new AttestationResponseHandlerException(AttestationResponseCode.RetryableFailure, "response json does not contain body.expiresAt");
}
atoken = new String(attestationTokenDecryptor.decrypt(Base64.getDecoder().decode(atoken), keyPair.getPrivate()), StandardCharsets.UTF_8);
@@ -215,8 +216,8 @@ public void attest() throws IOException, AttestationResponseHandlerException {
setOptoutURLFromResponse(innerBody);
scheduleAttestationExpirationCheck();
- } catch (IOException ioe) {
- throw ioe;
+ } catch (AttestationResponseHandlerException | IOException e) {
+ throw e;
} catch (Exception e) {
throw new AttestationResponseHandlerException(e);
}
@@ -242,10 +243,6 @@ public String getOptOutUrl() {
return this.optOutUrl.get();
}
- public String getAppVersionHeader() {
- return this.appVersionHeader;
- }
-
private void setAttestationTokenExpiresAt(String expiresAt) {
this.attestationTokenExpiresAt = Instant.parse(expiresAt);
}
@@ -299,11 +296,15 @@ private static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
return gen.generateKeyPair();
}
- private void notifyResponseWatcher(int statusCode, String responseBody) {
+ private void notifyResponseWatcher(AttestationResponseCode responseCode, String responseBody) {
+ if (responseCode != AttestationResponseCode.Success) {
+ LOGGER.warn("Received a non-success response code on Attestation: ResponseCode: {}, Message: {}", responseCode, responseBody);
+ }
+
this.lock.lock();
try {
if (this.responseWatcher != null)
- this.responseWatcher.handle(Pair.of(statusCode, responseBody));
+ this.responseWatcher.handle(Pair.of(responseCode, responseBody));
} finally {
lock.unlock();
}
@@ -318,4 +319,16 @@ private byte[] encodeStringUnicodeAttestationEndpoint(String data) {
ByteBuffer buffer = StandardCharsets.UTF_8.encode(data);
return Arrays.copyOf(buffer.array(), buffer.limit());
}
+
+ private AttestationResponseCode getAttestationResponseCodeFromHttpStatus(int httpStatus) {
+ if (httpStatus == 401 || httpStatus == 403) {
+ return AttestationResponseCode.AttestationFailure;
+ }
+
+ if (httpStatus == 200) {
+ return AttestationResponseCode.Success;
+ }
+
+ return AttestationResponseCode.RetryableFailure;
+ }
}
diff --git a/src/main/java/com/uid2/shared/attest/AttestationResponseHandlerException.java b/src/main/java/com/uid2/shared/attest/AttestationResponseHandlerException.java
index 774fbc8d..b133a512 100644
--- a/src/main/java/com/uid2/shared/attest/AttestationResponseHandlerException.java
+++ b/src/main/java/com/uid2/shared/attest/AttestationResponseHandlerException.java
@@ -1,7 +1,10 @@
package com.uid2.shared.attest;
+import lombok.Getter;
+
+@Getter
public class AttestationResponseHandlerException extends Exception {
- private int statusCode = 0;
+ private AttestationResponseCode responseCode;
public AttestationResponseHandlerException(Throwable t) {
super(t);
@@ -11,12 +14,13 @@ public AttestationResponseHandlerException(String message) {
super(message);
}
- public AttestationResponseHandlerException(int statusCode, String message) {
- super("http status: " + String.valueOf(statusCode) + ", " + message);
- this.statusCode = statusCode;
+ public AttestationResponseHandlerException(AttestationResponseCode responseCode, String message) {
+ super("AttestationResponseCode: " + String.valueOf(responseCode) + ", " + message);
+ this.responseCode = responseCode;
}
public boolean isAttestationFailure() {
- return statusCode == 401;
+ return responseCode == AttestationResponseCode.AttestationFailure;
}
+
}
diff --git a/src/main/java/com/uid2/shared/attest/UidCoreClient.java b/src/main/java/com/uid2/shared/attest/UidCoreClient.java
index fca09eaa..57b8acf0 100644
--- a/src/main/java/com/uid2/shared/attest/UidCoreClient.java
+++ b/src/main/java/com/uid2/shared/attest/UidCoreClient.java
@@ -80,9 +80,8 @@ private InputStream internalDownload(String path) throws CloudStorageException {
}
return inputStream;
} catch (Exception e) {
- throw new CloudStorageException("download " + path + " error: " + e.getMessage(), e);
+ throw new CloudStorageException("download error: " + e.getMessage(), e);
}
-
}
private InputStream readContentFromLocalFileSystem(String path, Proxy proxy) throws IOException {
@@ -99,14 +98,6 @@ private InputStream getWithAttest(String path) throws IOException, AttestationRe
HttpResponse httpResponse;
httpResponse = sendHttpRequest(path, attestationToken);
- // This should never happen, but keeping this part of the code just to be extra safe.
- if (httpResponse.statusCode() == 401) {
- LOGGER.info("Initial response from UID2 Core returned 401, performing attestation");
- attestationResponseHandler.attest();
- attestationToken = attestationResponseHandler.getAttestationToken();
- httpResponse = sendHttpRequest(path, attestationToken);
- }
-
return Utils.convertHttpResponseToInputStream(httpResponse);
}
diff --git a/src/main/java/com/uid2/shared/secure/AttestationClientException.java b/src/main/java/com/uid2/shared/secure/AttestationClientException.java
index ce43f61f..15096125 100644
--- a/src/main/java/com/uid2/shared/secure/AttestationClientException.java
+++ b/src/main/java/com/uid2/shared/secure/AttestationClientException.java
@@ -1,7 +1,12 @@
package com.uid2.shared.secure;
-public class AttestationClientException extends AttestationException
-{
+import lombok.Getter;
+
+@Getter
+public class AttestationClientException extends AttestationException {
+ // This exception should be used when the error is as a result of invalid or bad data from the caller.
+ // It will result in a return code in the 400s
+
private final AttestationFailure attestationFailure;
public AttestationClientException(Throwable cause) {
@@ -14,7 +19,4 @@ public AttestationClientException(String message, AttestationFailure attestation
this.attestationFailure = attestationFailure;
}
- public AttestationFailure getAttestationFailure() {
- return this.attestationFailure;
- }
}
diff --git a/src/main/java/com/uid2/shared/secure/AttestationException.java b/src/main/java/com/uid2/shared/secure/AttestationException.java
index e6aa0077..175f0d8c 100644
--- a/src/main/java/com/uid2/shared/secure/AttestationException.java
+++ b/src/main/java/com/uid2/shared/secure/AttestationException.java
@@ -1,6 +1,10 @@
package com.uid2.shared.secure;
public class AttestationException extends Exception {
+ // Used to indicate an error in the processing of Attestation due to internal server errors
+ // It will result in a response code of 500.
+ // If the error is as a result in invalid input from the caller, use the AttestationClientException
+
private final boolean isClientError;
public boolean IsClientError() {
diff --git a/src/main/java/com/uid2/shared/secure/AttestationFailure.java b/src/main/java/com/uid2/shared/secure/AttestationFailure.java
index 2cfcd6b6..7eba1592 100644
--- a/src/main/java/com/uid2/shared/secure/AttestationFailure.java
+++ b/src/main/java/com/uid2/shared/secure/AttestationFailure.java
@@ -7,6 +7,10 @@ public enum AttestationFailure {
BAD_CERTIFICATE,
FORBIDDEN_ENCLAVE,
UNKNOWN_ATTESTATION_URL,
+ INVALID_PROTOCOL,
+ INTERNAL_ERROR,
+ INVALID_TYPE,
+ RESPONSE_ENCRYPTION_ERROR,
UNKNOWN;
public String explain() {
@@ -23,6 +27,14 @@ public String explain() {
return "The enclave identifier is unknown";
case UNKNOWN_ATTESTATION_URL:
return "The given attestation URL is unknown";
+ case INVALID_PROTOCOL:
+ return "The given protocol is not valid";
+ case INTERNAL_ERROR:
+ return "There was an internal processing error";
+ case INVALID_TYPE:
+ return "Invalid Operator Type";
+ case RESPONSE_ENCRYPTION_ERROR:
+ return "Error encrypting the response";
default:
return "Unknown reason";
}
diff --git a/src/main/java/com/uid2/shared/secure/AttestationResult.java b/src/main/java/com/uid2/shared/secure/AttestationResult.java
index 2e3f239d..c4ee89ba 100644
--- a/src/main/java/com/uid2/shared/secure/AttestationResult.java
+++ b/src/main/java/com/uid2/shared/secure/AttestationResult.java
@@ -16,7 +16,7 @@ public AttestationResult(AttestationFailure reasonToFail) {
}
public AttestationResult(AttestationClientException exception) {
- this.failure = AttestationFailure.UNKNOWN;
+ this.failure = exception.getAttestationFailure();
this.publicKey = null;
this.enclaveId = "Failed attestation, enclave Id unknown";
this.attestationClientException = exception;
diff --git a/src/main/java/com/uid2/shared/secure/BadFormatException.java b/src/main/java/com/uid2/shared/secure/BadFormatException.java
deleted file mode 100644
index 731dd5df..00000000
--- a/src/main/java/com/uid2/shared/secure/BadFormatException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.uid2.shared.secure;
-
-public class BadFormatException extends Exception {
- public BadFormatException(Throwable cause) {
- super(cause);
- }
- public BadFormatException(String message, Throwable cause) {
- super(message, cause);
- }
-}
diff --git a/src/main/java/com/uid2/shared/secure/NitroCoreAttestationService.java b/src/main/java/com/uid2/shared/secure/NitroCoreAttestationService.java
index 5488d176..700379c9 100644
--- a/src/main/java/com/uid2/shared/secure/NitroCoreAttestationService.java
+++ b/src/main/java/com/uid2/shared/secure/NitroCoreAttestationService.java
@@ -20,7 +20,7 @@
public class NitroCoreAttestationService implements ICoreAttestationService {
private final String attestationUrl;
- private Set allowedEnclaveIds;
+ private final Set allowedEnclaveIds;
private final ICertificateProvider certificateProvider;
private static final Logger LOGGER = LoggerFactory.getLogger(NitroCoreAttestationService.class);
@@ -37,6 +37,8 @@ public void attest(byte[] attestationRequest, byte[] publicKey, Handler()
- {{
+ private static final ApplicationVersion APP_VERSION = new ApplicationVersion("appName", "appVersion", new HashMap() {{
put("Component1", "Value1");
put("Component2", "Value2");
}});
private final IAttestationProvider attestationProvider = mock(IAttestationProvider.class);
- private final Handler> responseWatcher = mock(Handler.class);
+ private final Handler> responseWatcher = mock(Handler.class);
private final IClock clock = mock(IClock.class);
private final URLConnectionHttpClient mockHttpClient = mock(URLConnectionHttpClient.class);
private Proxy proxy = CloudUtils.defaultProxy;
@@ -62,31 +66,32 @@ void setUp() {
@Test
public void attest_succeed_attestationTokenSet(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
HttpResponse mockHttpResponse = mock(HttpResponse.class);
- String expectedResponseBody = "{\"body\": {\"attestation_token\": \"test\",\"expiresAt\": \"2023-08-03T09:09:30.608597Z\",\"attestation_jwt_optout\": \"\",\"attestation_jwt_core\": \"\"},\"status\": \"success\"}";
+ String token = java.util.Base64.getEncoder().encodeToString("testToken".getBytes());
+ String expectedResponseBody = "{\"body\": {\"attestation_token\": \"" + token + "\",\"expiresAt\": \"2023-08-03T09:09:30.608597Z\",\"attestation_jwt_optout\": \"\",\"attestation_jwt_core\": \"\"},\"status\": \"success\"}";
when(mockHttpResponse.body()).thenReturn(expectedResponseBody);
when(mockHttpResponse.statusCode()).thenReturn(200);
when(mockHttpClient.post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class))).thenReturn(mockHttpResponse);
- when(mockAttestationTokenDecryptor.decrypt(any(), any())).thenReturn("test_attestation_token".getBytes(StandardCharsets.UTF_8));
+ when(mockAttestationTokenDecryptor.decrypt(eq(java.util.Base64.getDecoder().decode(token.getBytes())), any())).thenReturn("test_attestation_token".getBytes(StandardCharsets.UTF_8));
when(clock.now()).thenReturn(Instant.parse("2023-08-01T00:00:00.111Z"));
attestationResponseHandler.attest();
- Assertions.assertEquals("test_attestation_token", attestationResponseHandler.getAttestationToken());
- Assertions.assertEquals("appName=appVersion;Component1=Value1;Component2=Value2", attestationResponseHandler.getAppVersionHeader());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ assertEquals("test_attestation_token", attestationResponseHandler.getAttestationToken());
+ assertEquals("appName=appVersion;Component1=Value1;Component2=Value2", attestationResponseHandler.getAppVersionHeader());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_expiryCheckCallsAttestFixedIntervalUntilSuccess(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -107,14 +112,18 @@ public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_expiryChe
testContext.awaitCompletion(1100, TimeUnit.MILLISECONDS);
// Verify on httpClient because we can't mock attestationTokenRetriever
verify(mockHttpClient, times(4)).post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class));
- verify(this.responseWatcher, times(2)).handle(Pair.of(200, expectedResponseBody));
- verify(this.responseWatcher, times(2)).handle(Pair.of(500, "bad"));
+ ArgumentCaptor> notifyArgument = ArgumentCaptor.forClass(Pair.class);
+ verify(this.responseWatcher, times(4)).handle(notifyArgument.capture());
+ List> calls = notifyArgument.getAllValues();
+ assertEquals(2, calls.stream().filter(c -> c.left() == AttestationResponseCode.Success && c.right().equals(expectedResponseBody)).count());
+ assertEquals(2, calls.stream().filter(c -> c.left() == AttestationResponseCode.RetryableFailure && c.right().equals("bad")).count());
+
testContext.completeNow();
}
@Test
public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_expiryCheckDoesNotCallAttest(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -133,13 +142,13 @@ public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_expiryChe
testContext.awaitCompletion(1, TimeUnit.SECONDS);
// Verify on httpClient because we can't mock attestationTokenRetriever
verify(mockHttpClient, times(1)).post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class));
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_providerNotReadyDoesNotCallAttest(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
// isReady will be called twice:
// The first is by attest() with true returned so that expiration check will be scheduled.
@@ -161,14 +170,14 @@ public void attest_currentTimeAfterTenMinsBeforeAttestationTokenExpiry_providerN
testContext.awaitCompletion(1, TimeUnit.SECONDS);
// Verify on httpClient because we can't mock attestationTokenRetriever
verify(mockHttpClient, times(1)).post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class));
- verify(this.responseWatcher, only()).handle(Pair.of(200, expectedResponseBody));
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ verify(this.responseWatcher, only()).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
public void attest_responseBodyHasNoAttestationToken_exceptionThrown(Vertx vertx, VertxTestContext testContext) throws IOException, AttestationException, AttestationResponseHandlerException, InterruptedException {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -190,15 +199,15 @@ public void attest_responseBodyHasNoAttestationToken_exceptionThrown(Vertx vertx
AttestationResponseHandlerException result = Assertions.assertThrows(AttestationResponseHandlerException.class, () -> {
attestationResponseHandler.attest();
});
- String expectedExceptionMessage = "com.uid2.shared.attest.AttestationResponseHandlerException: http status: 200, response json does not contain body.attestation_token";
- Assertions.assertEquals(expectedExceptionMessage, result.getMessage());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ String expectedExceptionMessage = "AttestationResponseCode: RetryableFailure, response json does not contain body.attestation_token";
+ assertEquals(expectedExceptionMessage, result.getMessage());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
public void attest_responseBodyHasNoExpiredAt_exceptionThrown(Vertx vertx, VertxTestContext testContext) throws IOException, AttestationException, AttestationResponseHandlerException, InterruptedException {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -220,16 +229,16 @@ public void attest_responseBodyHasNoExpiredAt_exceptionThrown(Vertx vertx, Vertx
AttestationResponseHandlerException result = Assertions.assertThrows(AttestationResponseHandlerException.class, () -> {
attestationResponseHandler.attest();
});
- String expectedExceptionMessage = "com.uid2.shared.attest.AttestationResponseHandlerException: http status: 200, response json does not contain body.expiresAt";
+ String expectedExceptionMessage = "AttestationResponseCode: RetryableFailure, response json does not contain body.expiresAt";
- Assertions.assertEquals(expectedExceptionMessage, result.getMessage());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ assertEquals(expectedExceptionMessage, result.getMessage());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
public void attest_providerNotReady_exceptionThrown(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(false);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -238,18 +247,20 @@ public void attest_providerNotReady_exceptionThrown(Vertx vertx, VertxTestContex
attestationResponseHandler.attest();
});
String expectedExceptionMessage = "attestation provider is not ready";
- Assertions.assertEquals(expectedExceptionMessage, result.getMessage());
+ assertEquals(expectedExceptionMessage, result.getMessage());
testContext.completeNow();
}
+
@Test
public void attest_succeed_optOutJwtSet(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
- HttpResponse mockHttpResponse = mock(HttpResponse.class); String expectedResponseBody = "{\"body\": {\"attestation_token\": \"test\",\"expiresAt\": \"2023-08-03T09:09:30.608597Z\",\"attestation_jwt_optout\": \"test_jwt\",\"attestation_jwt_core\": \"\"},\"status\": \"success\"}";
+ HttpResponse mockHttpResponse = mock(HttpResponse.class);
+ String expectedResponseBody = "{\"body\": {\"attestation_token\": \"test\",\"expiresAt\": \"2023-08-03T09:09:30.608597Z\",\"attestation_jwt_optout\": \"test_jwt\",\"attestation_jwt_core\": \"\"},\"status\": \"success\"}";
when(mockHttpResponse.body()).thenReturn(expectedResponseBody);
when(mockHttpResponse.statusCode()).thenReturn(200);
@@ -257,13 +268,14 @@ public void attest_succeed_optOutJwtSet(Vertx vertx, VertxTestContext testContex
when(mockAttestationTokenDecryptor.decrypt(any(), any())).thenReturn("test_attestation_token".getBytes(StandardCharsets.UTF_8));
attestationResponseHandler.attest();
- Assertions.assertEquals("test_jwt", attestationResponseHandler.getOptOutJWT());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ assertEquals("test_jwt", attestationResponseHandler.getOptOutJWT());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
+
@Test
public void attest_succeed_coreJwtSet(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -277,13 +289,14 @@ public void attest_succeed_coreJwtSet(Vertx vertx, VertxTestContext testContext)
when(mockAttestationTokenDecryptor.decrypt(any(), any())).thenReturn("test_attestation_token".getBytes(StandardCharsets.UTF_8));
attestationResponseHandler.attest();
- Assertions.assertEquals("test_jwt_core", attestationResponseHandler.getCoreJWT());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ assertEquals("test_jwt_core", attestationResponseHandler.getCoreJWT());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
+
@Test
public void attest_succeed_jwtsNull(Vertx vertx, VertxTestContext testContext) throws Exception {
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), any())).thenReturn(new byte[1]);
@@ -299,13 +312,13 @@ public void attest_succeed_jwtsNull(Vertx vertx, VertxTestContext testContext) t
attestationResponseHandler.attest();
Assertions.assertNull(attestationResponseHandler.getOptOutJWT());
Assertions.assertNull(attestationResponseHandler.getCoreJWT());
- verify(this.responseWatcher, times(1)).handle(Pair.of(200, expectedResponseBody));
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.Success, expectedResponseBody));
testContext.completeNow();
}
@Test
- public void attest_succeed_jsonRequest_includes_expected_properties(Vertx vertx, VertxTestContext testContext) throws Exception{
- attestationResponseHandler = getAttestationTokenRetriever(vertx);
+ public void attest_succeed_jsonRequest_includes_expected_properties(Vertx vertx, VertxTestContext testContext) throws Exception {
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
when(attestationProvider.isReady()).thenReturn(true);
when(attestationProvider.getAttestationRequest(any(), eq(ENCODED_ATTESTATION_ENDPOINT))).thenReturn(ENCODED_ATTESTATION_ENDPOINT);
@@ -330,17 +343,83 @@ public void attest_succeed_jsonRequest_includes_expected_properties(Vertx vertx,
String base64Content = jsonBody.getString("attestation_request");
byte[] data = Base64.decode(base64Content);
String decodedUrl = new String(data, StandardCharsets.UTF_8);
- Assertions.assertEquals(ATTESTATION_ENDPOINT, decodedUrl);
+ assertEquals(ATTESTATION_ENDPOINT, decodedUrl);
Assertions.assertNotNull(jsonBody.getString("operator_type"));
- Assertions.assertEquals(OPERATOR_TYPE, jsonBody.getString("operator_type"));
+ assertEquals(OPERATOR_TYPE, jsonBody.getString("operator_type"));
verify(attestationProvider, times(1)).getAttestationRequest(any(), eq(ENCODED_ATTESTATION_ENDPOINT));
testContext.completeNow();
}
- private AttestationResponseHandler getAttestationTokenRetriever(Vertx vertx) {
+ @ParameterizedTest
+ @ValueSource(ints = {401, 403})
+ public void attest_response_throws_AttestationFailure_on_auth_failure(Integer responseCode, Vertx vertx, VertxTestContext testContext) throws Exception {
+ // Arrange
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
+
+ when(attestationProvider.isReady()).thenReturn(true);
+ when(attestationProvider.getAttestationRequest(any(), eq(ENCODED_ATTESTATION_ENDPOINT))).thenReturn(ENCODED_ATTESTATION_ENDPOINT);
+
+ HttpResponse mockHttpResponse = mock(HttpResponse.class);
+ String expectedResponseBody = "Failed attestation";
+ when(mockHttpResponse.body()).thenReturn(expectedResponseBody);
+ when(mockHttpResponse.statusCode()).thenReturn(responseCode);
+
+ when(mockHttpClient.post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class))).thenReturn(mockHttpResponse);
+
+ // Act
+ AttestationResponseHandlerException result = Assertions.assertThrows(AttestationResponseHandlerException.class, () -> {
+ attestationResponseHandler.attest();
+ });
+
+ // Assert
+ assertEquals("AttestationResponseCode: AttestationFailure, Non-success response from Core on attest", result.getMessage());
+ assertEquals(AttestationResponseCode.AttestationFailure, result.getResponseCode());
+ assertTrue(result.isAttestationFailure());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.AttestationFailure, "Failed attestation"));
+ verify(this.mockAttestationTokenDecryptor, never()).decrypt(any(), any());
+ Assertions.assertNull(attestationResponseHandler.getOptOutJWT());
+ Assertions.assertNull(attestationResponseHandler.getCoreJWT());
+
+ testContext.completeNow();
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {100, 199, 404, 500, 502, 503})
+ public void attest_response_throws_AttestationRetryable(Integer responseCode, Vertx vertx, VertxTestContext testContext) throws Exception {
+ // Arrange
+ attestationResponseHandler = getAttestationResponseHandler(vertx);
+
+ when(attestationProvider.isReady()).thenReturn(true);
+ when(attestationProvider.getAttestationRequest(any(), eq(ENCODED_ATTESTATION_ENDPOINT))).thenReturn(ENCODED_ATTESTATION_ENDPOINT);
+
+ HttpResponse mockHttpResponse = mock(HttpResponse.class);
+ String expectedResponseBody = "Some error";
+ when(mockHttpResponse.body()).thenReturn(expectedResponseBody);
+ when(mockHttpResponse.statusCode()).thenReturn(responseCode);
+
+ when(mockHttpClient.post(eq(ATTESTATION_ENDPOINT), any(String.class), any(HashMap.class))).thenReturn(mockHttpResponse);
+
+ // Act
+ AttestationResponseHandlerException result = Assertions.assertThrows(AttestationResponseHandlerException.class, () -> {
+ attestationResponseHandler.attest();
+ });
+
+ // Assert
+ assertEquals("AttestationResponseCode: RetryableFailure, Non-success response from Core on attest", result.getMessage());
+ assertEquals(AttestationResponseCode.RetryableFailure, result.getResponseCode());
+ assertFalse(result.isAttestationFailure());
+ verify(this.responseWatcher, times(1)).handle(Pair.of(AttestationResponseCode.RetryableFailure, "Some error"));
+ verify(this.mockAttestationTokenDecryptor, never()).decrypt(any(), any());
+ Assertions.assertNull(attestationResponseHandler.getOptOutJWT());
+ Assertions.assertNull(attestationResponseHandler.getCoreJWT());
+
+ testContext.completeNow();
+ }
+
+ private AttestationResponseHandler getAttestationResponseHandler(Vertx vertx) {
return new AttestationResponseHandler(vertx, ATTESTATION_ENDPOINT, "testApiKey", OPERATOR_TYPE, APP_VERSION, attestationProvider, responseWatcher, proxy, clock, mockHttpClient, mockAttestationTokenDecryptor, 250);
}
}
\ No newline at end of file
diff --git a/src/test/java/com/uid2/shared/attest/UidCoreClientTest.java b/src/test/java/com/uid2/shared/attest/UidCoreClientTest.java
index 8e1f1916..16927e56 100644
--- a/src/test/java/com/uid2/shared/attest/UidCoreClientTest.java
+++ b/src/test/java/com/uid2/shared/attest/UidCoreClientTest.java
@@ -62,18 +62,18 @@ public void Download_Succeed_RequestSentWithExpectedParameters() throws IOExcept
@Test
public void Download_AttestInternalFail_ExceptionThrown() throws IOException, AttestationResponseHandlerException {
- AttestationResponseHandlerException exception = new AttestationResponseHandlerException(401, "test failure");
+ AttestationResponseHandlerException exception = new AttestationResponseHandlerException(AttestationResponseCode.AttestationFailure, "test failure");
doThrow(exception).when(mockAttestationResponseHandler).attest();
CloudStorageException result = assertThrows(CloudStorageException.class, () -> {
uidCoreClient.download("https://download");
});
- String expectedExceptionMessage = "download https://download error: http status: 401, test failure";
+ String expectedExceptionMessage = "download error: AttestationResponseCode: AttestationFailure, test failure";
assertEquals(expectedExceptionMessage, result.getMessage());
}
@Test
- public void Download_Attest401_AttestCalledTwice() throws CloudStorageException, IOException, InterruptedException, AttestationResponseHandlerException {
+ public void Download_Attest401_getOptOut_NotCalled() throws CloudStorageException, IOException, AttestationResponseHandlerException {
HttpResponse mockHttpResponse = mock(HttpResponse.class);
when(mockHttpResponse.statusCode()).thenReturn(401);
@@ -83,7 +83,7 @@ public void Download_Attest401_AttestCalledTwice() throws CloudStorageException,
when(mockHttpClient.get(eq("https://download"), any(HashMap.class))).thenReturn(mockHttpResponse);
uidCoreClient.download("https://download");
- verify(mockAttestationResponseHandler, times(2)).attest();
+ verify(mockAttestationResponseHandler, times(1)).attest();
verify(mockAttestationResponseHandler, never()).getOptOutUrl();
}