From 5155ddf040eb49b71083aa22b8cea487df2b1f90 Mon Sep 17 00:00:00 2001 From: sean wibisono Date: Fri, 5 Jun 2026 09:28:23 +1000 Subject: [PATCH 1/3] Assert /attest 401 reasons in CoreTest Covers unrecognized and disabled operator keys returning HTTP 401 with a reason in the response body. Adds Core.attestWithApiKey and HttpException.getCode helpers. The disabled-key case relies on a disabled operator key in the admin localstack seed, and both cases require the corresponding uid2-core change for the new 401 body. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/test/java/app/component/Core.java | 5 +++ src/test/java/common/HttpClient.java | 4 ++ src/test/java/suite/core/CoreTest.java | 56 +++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/test/java/app/component/Core.java b/src/test/java/app/component/Core.java index 27f32cd..ccd794c 100644 --- a/src/test/java/app/component/Core.java +++ b/src/test/java/app/component/Core.java @@ -30,6 +30,11 @@ public JsonNode attest(String attestationRequest) throws Exception { return OBJECT_MAPPER.readTree(response); } + public JsonNode attestWithApiKey(String attestationRequest, String apiKey) throws Exception { + String response = HttpClient.post(getBaseUrl() + "/attest", attestationRequest, apiKey); + return OBJECT_MAPPER.readTree(response); + } + public JsonNode getWithCoreApiToken(String path) throws Exception { return getWithCoreApiToken(path, false); } diff --git a/src/test/java/common/HttpClient.java b/src/test/java/common/HttpClient.java index 47ab050..739f398 100644 --- a/src/test/java/common/HttpClient.java +++ b/src/test/java/common/HttpClient.java @@ -42,6 +42,10 @@ public JsonNode getResponseJson() throws Exception { return OBJECT_MAPPER.readTree(response); } + public int getCode() { + return code; + } + private static String createErrorMessage(HttpMethod method, String url, int code, String message, String response) { return "Unsuccessful %s request - URL: %s - Code: %d %s - Response body: %s".formatted( method, url, code, message, response diff --git a/src/test/java/suite/core/CoreTest.java b/src/test/java/suite/core/CoreTest.java index 04e95b8..ce397fc 100644 --- a/src/test/java/suite/core/CoreTest.java +++ b/src/test/java/suite/core/CoreTest.java @@ -30,9 +30,63 @@ public void testAttest_EmptyAttestationRequest(Core core) { assertEquals("Unsuccessful POST request - URL: " + coreUrl + "/attest - Code: 400 Bad Request - Response body: {\"status\":\"no attestation_request attached\"}", exception.getMessage()); } + /** + * Tests that an unknown / mistyped operator key is rejected with HTTP 401 and a body that + * names the cause (reason=unrecognized_key) instead of a bare "Unauthorized". + * This is the 4eyes.ai scenario (UID2-6717 / UID2-7235): a single transcription error in the + * operator key. The actionable message propagates verbatim into the operator's startup log. + */ + @ParameterizedTest(name = "/attest unrecognized key - {0}") + @MethodSource({ + "suite.core.TestData#baseArgs" + }) + public void testAttest_UnrecognizedOperatorKey(Core core) { + // A well-formed but unknown key - not present in the operators store. + String bogusOperatorKey = "UID2-O-L-000-thisKeyDoesNotExist000000000000000000000000="; + + HttpClient.HttpException exception = assertThrows( + HttpClient.HttpException.class, + () -> core.attestWithApiKey("{\"attestation_request\":\"AA==\"}", bogusOperatorKey) + ); + + assertEquals(401, exception.getCode(), "unknown operator key should be rejected with 401"); + JsonNode body = assertDoesNotThrow(exception::getResponseJson); + assertAll("401 body should name the rejection cause", + () -> assertEquals("unauthorized", body.get("status").asText()), + () -> assertEquals("unrecognized_key", body.get("reason").asText()), + () -> assertTrue(body.get("message").asText().toLowerCase().contains("not recognized"), + "message should tell the operator the key was not recognized")); + } + + /** + * Tests that a recognized-but-disabled operator key is rejected with HTTP 401 and reason=key_disabled, + * distinguishing it from an unknown key. Relies on the disabled operator key seeded in + * uid2-admin localstack (site_id 998, "Disabled Operator (E2E)"). + */ + @ParameterizedTest(name = "/attest disabled key - {0}") + @MethodSource({ + "suite.core.TestData#baseArgs" + }) + public void testAttest_DisabledOperatorKey(Core core) { + String disabledOperatorKey = "UID2-O-L-998-d1sabledKeyForE2ETestOnly00000000000000000000="; + + HttpClient.HttpException exception = assertThrows( + HttpClient.HttpException.class, + () -> core.attestWithApiKey("{\"attestation_request\":\"AA==\"}", disabledOperatorKey) + ); + + assertEquals(401, exception.getCode(), "disabled operator key should be rejected with 401"); + JsonNode body = assertDoesNotThrow(exception::getResponseJson); + assertAll("401 body should identify the key as disabled", + () -> assertEquals("unauthorized", body.get("status").asText()), + () -> assertEquals("key_disabled", body.get("reason").asText()), + () -> assertTrue(body.get("message").asText().toLowerCase().contains("disabled"), + "message should tell the operator the key is disabled")); + } + /** * Tests valid attestation request with JWT signing. - * + * * Since LocalStack generates its own RSA key material, * we dynamically fetch the public key from LocalStack's * KMS using GetPublicKey API to validate JWT signatures. From 573e236fba53002a7fbeb3f9125fcbbcb421719a Mon Sep 17 00:00:00 2001 From: Release Workflow Date: Thu, 4 Jun 2026 23:37:45 +0000 Subject: [PATCH 2/3] [CI Pipeline] Released Snapshot version: 4.2.18-alpha-96-SNAPSHOT --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 42111a6..2179a39 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.uid2 uid2-e2e - 4.2.17 + 4.2.18-alpha-96-SNAPSHOT 21 From ba1cf10732a7058d2e44d470b31d9d665987e4ff Mon Sep 17 00:00:00 2001 From: sean wibisono Date: Fri, 5 Jun 2026 13:10:10 +1000 Subject: [PATCH 3/3] update comments --- src/test/java/suite/core/CoreTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/test/java/suite/core/CoreTest.java b/src/test/java/suite/core/CoreTest.java index ce397fc..4916c37 100644 --- a/src/test/java/suite/core/CoreTest.java +++ b/src/test/java/suite/core/CoreTest.java @@ -32,9 +32,7 @@ public void testAttest_EmptyAttestationRequest(Core core) { /** * Tests that an unknown / mistyped operator key is rejected with HTTP 401 and a body that - * names the cause (reason=unrecognized_key) instead of a bare "Unauthorized". - * This is the 4eyes.ai scenario (UID2-6717 / UID2-7235): a single transcription error in the - * operator key. The actionable message propagates verbatim into the operator's startup log. + * names the cause (reason=unrecognized_key). */ @ParameterizedTest(name = "/attest unrecognized key - {0}") @MethodSource({