Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.uid2</groupId>
<artifactId>uid2-e2e</artifactId>
<version>4.1.0</version>
<version>4.1.2-alpha-72-SNAPSHOT</version>

<properties>
<maven.compiler.source>21</maven.compiler.source>
Expand Down
74 changes: 72 additions & 2 deletions src/test/java/app/component/Operator.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import lombok.Getter;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
Expand Down Expand Up @@ -68,6 +70,7 @@ private record V2Envelope(String envelope, byte[] nonce) {
// When running via IntelliJ, environment variables are defined in the uid2-dev-workspace repo under .idea/runConfigurations.
// Test data is defined in the uid2-admin repo.

private static final Logger LOGGER = LoggerFactory.getLogger(Operator.class);
private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final int TIMESTAMP_LENGTH = 8;
Expand All @@ -78,6 +81,27 @@ private record V2Envelope(String envelope, byte[] nonce) {

public static final String CLIENT_API_KEY = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY);
public static final String CLIENT_API_SECRET = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_SECRET);

static {
// Log operator configuration at class initialization
String maskedKey = CLIENT_API_KEY != null && CLIENT_API_KEY.length() > 20
? CLIENT_API_KEY.substring(0, 10) + "..." + CLIENT_API_KEY.substring(CLIENT_API_KEY.length() - 10)
: "[null or too short]";
String maskedSecret = CLIENT_API_SECRET != null && CLIENT_API_SECRET.length() > 20
? CLIENT_API_SECRET.substring(0, 10) + "..." + CLIENT_API_SECRET.substring(CLIENT_API_SECRET.length() - 10)
: "[null or too short]";
LOGGER.info(() -> String.format(
"[OPERATOR CONFIG] Initialized with:%n" +
" CLIENT_API_KEY: %s (length: %d)%n" +
" CLIENT_API_SECRET: %s (length: %d)%n" +
" E2E_ENV: %s%n" +
" IDENTITY_SCOPE: %s",
maskedKey, CLIENT_API_KEY != null ? CLIENT_API_KEY.length() : 0,
maskedSecret, CLIENT_API_SECRET != null ? CLIENT_API_SECRET.length() : 0,
EnvUtil.getEnv(Const.Config.ENV, false),
EnvUtil.getEnv(Const.Config.IDENTITY_SCOPE, false)
));
}

// Local only - Sharing
public static final String CLIENT_API_KEY_SHARING_RECIPIENT = EnvUtil.getEnv(Const.Config.Operator.CLIENT_API_KEY_SHARING_RECIPIENT, EnabledCondition.isLocal());
Expand Down Expand Up @@ -274,9 +298,55 @@ public IdentityMapResponse v2IdentityMap(IdentityMapInput input) {

// Need to use the manual mapping for error cases - SDK won't allow creating input with bad emails
public JsonNode v3IdentityMap(String payload) throws Exception {
String baseUrl = getBaseUrl();
String endpoint = baseUrl + "/v3/identity/map";

LOGGER.info(() -> String.format(
"[v3IdentityMap] Preparing request:%n" +
" Operator Name: %s%n" +
" Operator Type: %s%n" +
" Base URL: %s%n" +
" Full Endpoint: %s%n" +
" CLIENT_API_KEY: %s (length: %d)%n" +
" CLIENT_API_SECRET: %s (length: %d)%n" +
" Payload (raw): %s",
getName(), type, baseUrl, endpoint,
CLIENT_API_KEY != null ? CLIENT_API_KEY.substring(0, Math.min(20, CLIENT_API_KEY.length())) + "..." : "[null]",
CLIENT_API_KEY != null ? CLIENT_API_KEY.length() : 0,
CLIENT_API_SECRET != null ? CLIENT_API_SECRET.substring(0, Math.min(20, CLIENT_API_SECRET.length())) + "..." : "[null]",
CLIENT_API_SECRET != null ? CLIENT_API_SECRET.length() : 0,
payload != null && payload.length() > 200 ? payload.substring(0, 200) + "..." : payload
));

V2Envelope envelope = v2CreateEnvelope(payload, CLIENT_API_SECRET);
String encryptedResponse = HttpClient.post(getBaseUrl() + "/v3/identity/map", envelope.envelope(), CLIENT_API_KEY);
return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), CLIENT_API_SECRET);

LOGGER.info(() -> String.format(
"[v3IdentityMap] Created envelope:%n" +
" Envelope length: %d%n" +
" Nonce length: %d",
envelope.envelope().length(),
envelope.nonce().length
));

try {
String encryptedResponse = HttpClient.post(endpoint, envelope.envelope(), CLIENT_API_KEY);
LOGGER.info(() -> String.format(
"[v3IdentityMap] Request successful, response length: %d",
encryptedResponse != null ? encryptedResponse.length() : 0
));
return v2DecryptEncryptedResponse(encryptedResponse, envelope.nonce(), CLIENT_API_SECRET);
} catch (Exception e) {
final String errorMsg = e.getMessage();
final String errorType = e.getClass().getName();
LOGGER.error(() -> String.format(
"[v3IdentityMap] Request failed:%n" +
" Endpoint: %s%n" +
" Error: %s%n" +
" Error Type: %s",
endpoint, errorMsg, errorType
));
throw e;
}
}

public IdentityMapV3Response v3IdentityMap(IdentityMapV3Input input) {
Expand Down
90 changes: 87 additions & 3 deletions src/test/java/common/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import com.uid2.shared.util.Mapper;
import lombok.Getter;
import okhttp3.*;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;

import java.util.Map;
import java.util.Objects;

public final class HttpClient {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpClient.class);
private static final ObjectMapper OBJECT_MAPPER = Mapper.getInstance();

public static final OkHttpClient RAW_CLIENT = new OkHttpClient();
Expand Down Expand Up @@ -71,13 +73,95 @@ public static String post(String url, String body, String bearerToken) throws Ex
}

public static String execute(Request request, HttpMethod method) throws Exception {
final String url = request.url().toString();
final String requestBodyForLog = extractRequestBodyForLog(request);
final String authHeader = extractAuthHeader(request);
final Headers headers = request.headers();

LOGGER.info(() -> String.format(
"[HTTP REQUEST] %s %s%n" +
" Authorization: %s%n" +
" Request Body: %s%n" +
" Headers: %s",
method, url,
authHeader,
requestBodyForLog,
headers.toString()
));

try (Response response = RAW_CLIENT.newCall(request).execute()) {
final ResponseBody body = response.body();
// Read the FULL response body - don't truncate the actual data!
final String fullResponseBody = (body != null) ? body.string() : "";
final int statusCode = response.code();
final String statusMessage = response.message();
final Headers responseHeaders = response.headers();

// Only truncate for logging display
final String responseBodyForLog = truncateForLog(fullResponseBody, 1000);

LOGGER.info(() -> String.format(
"[HTTP RESPONSE] %s %s%n" +
" Status: %d %s%n" +
" Response Headers: %s%n" +
" Response Body: %s",
method, url,
statusCode, statusMessage,
responseHeaders.toString(),
responseBodyForLog
));

if (!response.isSuccessful()) {
throw new HttpException(method, request.url().toString(), response.code(), response.message(), Objects.requireNonNull(response.body()).string());
LOGGER.error(() -> String.format(
"[HTTP ERROR] Request failed: %s %s - Status: %d %s - Response: %s",
method, url, statusCode, statusMessage, responseBodyForLog
));
throw new HttpException(method, url, statusCode, statusMessage, fullResponseBody);
}
return Objects.requireNonNull(response.body()).string();
// Return the FULL response body, not truncated
return fullResponseBody;
}
}

private static String truncateForLog(String text, int maxLength) {
if (text == null || text.isEmpty()) {
return "[empty]";
}
if (text.length() > maxLength) {
return text.substring(0, maxLength) + "... [truncated, total length=" + text.length() + "]";
}
return text;
}

private static String extractRequestBodyForLog(Request request) {
if (request.body() == null) {
return "[empty]";
}
try {
okio.Buffer buffer = new okio.Buffer();
request.body().writeTo(buffer);
String body = buffer.readUtf8();
return truncateForLog(body, 500);
} catch (Exception e) {
return "[unable to read request body: " + e.getMessage() + "]";
}
}

private static String extractAuthHeader(Request request) {
Headers headers = request.headers();
for (int i = 0; i < headers.size(); i++) {
if ("Authorization".equalsIgnoreCase(headers.name(i))) {
String authValue = headers.value(i);
// Mask the token for security, but show first/last few chars
if (authValue.length() > 20) {
return authValue.substring(0, 10) + "..." + authValue.substring(authValue.length() - 10);
} else {
return "[masked]";
}
}
}
return "[none]";
}

private static Request buildRequest(HttpMethod method, String url, String body, String bearerToken) {
return buildRequest(method, url, body, bearerToken, null);
Expand Down