Skip to content

Commit

Permalink
Merge pull request #395 from IABTechLab/ccm-UID2-2165-log-Origin-fiel…
Browse files Browse the repository at this point in the history
…d-for-InvalidHttpOrigin-errors

UID2-2165 Log origin field for InvalidHttpOrigin errors
  • Loading branch information
caroline-ttd authored Mar 8, 2024
2 parents d9dfd2d + 00f63a5 commit 1d00fb7
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 4 deletions.
3 changes: 2 additions & 1 deletion conf/local-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@
"optout_partition_interval": 86400,
"client_side_token_generate": true,
"client_side_token_generate_domain_name_check_enabled": true,
"key_sharing_endpoint_provide_site_domain_names": true
"key_sharing_endpoint_provide_site_domain_names": true,
"client_side_token_generate_log_invalid_http_origins": true
}
1 change: 1 addition & 0 deletions conf/local-e2e-docker-public-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"enable_v2_encryption": true,
"client_side_token_generate": true,
"client_side_token_generate_domain_name_check_enabled": true,
"client_side_token_generate_log_invalid_http_origins": true,
"key_sharing_endpoint_provide_site_domain_names": true,
"validate_service_links": true,
"optout_s3_bucket": "test-optout-bucket",
Expand Down
3 changes: 2 additions & 1 deletion conf/local-e2e-private-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@
"optout_max_partitions": 30,
"optout_partition_interval": 86400,
"client_side_token_generate": true,
"client_side_token_generate_domain_name_check_enabled": false
"client_side_token_generate_domain_name_check_enabled": false,
"client_side_token_generate_log_invalid_http_origins": true
}
3 changes: 2 additions & 1 deletion conf/local-e2e-public-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@
"optout_partition_interval": 86400,
"client_side_token_generate": true,
"client_side_token_generate_domain_name_check_enabled": true,
"key_sharing_endpoint_provide_site_domain_names": true
"key_sharing_endpoint_provide_site_domain_names": true,
"client_side_token_generate_log_invalid_http_origins": true
}
1 change: 1 addition & 0 deletions conf/validator-latest-e2e-docker-public-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"enable_v2_encryption": true,
"client_side_token_generate": true,
"client_side_token_generate_domain_name_check_enabled": true,
"client_side_token_generate_log_invalid_http_origins": true,
"key_sharing_endpoint_provide_site_domain_names": true,
"validate_service_links": true,
"optout_s3_bucket": "test-optout-bucket",
Expand Down
44 changes: 44 additions & 0 deletions src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,13 @@ public class UIDOperatorVerticle extends AbstractVerticle {
private final KeyManager keyManager;
private final SecureLinkValidatorService secureLinkValidatorService;
private final boolean cstgDoDomainNameCheck;
private final boolean clientSideTokenGenerateLogInvalidHttpOrigin;
public final static int MASTER_KEYSET_ID_FOR_SDKS = 9999999; //this is because SDKs have an issue where they assume keyset ids are always positive; that will be fixed.
public final static long OPT_OUT_CHECK_CUTOFF_DATE = Instant.parse("2023-09-01T00:00:00.00Z").getEpochSecond();

protected boolean keySharingEndpointProvideSiteDomainNames;
protected Map<Integer, Set<String>> siteIdToInvalidOrigins = new HashMap<>();
protected Instant lastInvalidOriginProcessTime = Instant.now();

public UIDOperatorVerticle(JsonObject config,
boolean clientSideTokenGenerate,
Expand Down Expand Up @@ -144,6 +147,7 @@ public UIDOperatorVerticle(JsonObject config,
this.keySharingEndpointProvideSiteDomainNames = config.getBoolean("key_sharing_endpoint_provide_site_domain_names", false);
this._statsCollectorQueue = statsCollectorQueue;
this.clientKeyProvider = clientKeyProvider;
this.clientSideTokenGenerateLogInvalidHttpOrigin = config.getBoolean("client_side_token_generate_log_invalid_http_origins", false);
}

@Override
Expand Down Expand Up @@ -309,6 +313,9 @@ private void handleClientSideTokenGenerateImpl(RoutingContext rc) throws NoSuchA

boolean allowedDomain = DomainNameCheckUtil.isDomainNameAllowed(origin, domainNames);
if (!allowedDomain) {
if (clientSideTokenGenerateLogInvalidHttpOrigin) {
handleInvalidHttpOriginError(clientSideKeypair.getSiteId(), origin);
}
SendClientErrorResponseAndRecordStats(ResponseStatus.InvalidHttpOrigin, 403, rc, "unexpected http origin", clientSideKeypair.getSiteId(), TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2, TokenResponseStatsCollector.ResponseStatus.InvalidHttpOrigin, siteProvider);
return;
}
Expand Down Expand Up @@ -1758,6 +1765,43 @@ private void sendJsonResponse(RoutingContext rc, JsonArray json) {
.end(json.encode());
}

private void handleInvalidHttpOriginError(int siteId, String origin) {
Set<String> uniqueInvalidOrigins = siteIdToInvalidOrigins.computeIfAbsent(siteId, k -> new HashSet<>());
uniqueInvalidOrigins.add(origin);

if (Duration.between(lastInvalidOriginProcessTime, Instant.now()).compareTo(Duration.ofMinutes(60)) >= 0) {
lastInvalidOriginProcessTime = Instant.now();
LOGGER.error(generateInvalidHttpOriginMessage(siteIdToInvalidOrigins));
siteIdToInvalidOrigins.clear();
}
}

private String generateInvalidHttpOriginMessage(Map<Integer, Set<String>> siteIdToInvalidOrigins) {
StringBuilder invalidHttpOriginMessage = new StringBuilder();
invalidHttpOriginMessage.append("InvalidHttpOrigin: ");
boolean mapHasFirstElement = false;
for (Map.Entry<Integer, Set<String>> entry : siteIdToInvalidOrigins.entrySet()) {
if(mapHasFirstElement) {
invalidHttpOriginMessage.append(" | ");
}
mapHasFirstElement = true;
int siteId = entry.getKey();
Set<String> origins = entry.getValue();
String siteName = getSiteName(siteProvider, siteId);
String site = "site " + siteName + " (" + siteId + "): ";
invalidHttpOriginMessage.append(site);
boolean setHasFirstElement = false;
for (String origin : origins) {
if(setHasFirstElement) {
invalidHttpOriginMessage.append(", ");
}
setHasFirstElement = true;
invalidHttpOriginMessage.append(origin);
}
}
return invalidHttpOriginMessage.toString();
}

public enum UserConsentStatus {
SUFFICIENT,
INSUFFICIENT,
Expand Down
11 changes: 10 additions & 1 deletion src/test/java/com/uid2/operator/ExtendedUIDOperatorVerticle.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import com.uid2.operator.monitoring.IStatsCollectorQueue;
import com.uid2.operator.service.IUIDOperatorService;
import com.uid2.operator.service.SecureLinkValidatorService;
import com.uid2.operator.service.UIDOperatorService;
import com.uid2.operator.store.IOptOutStore;
import com.uid2.operator.vertx.UIDOperatorVerticle;
import com.uid2.shared.store.*;
import io.vertx.core.json.JsonObject;

import java.time.Clock;
import java.time.Instant;
import java.util.Map;
import java.util.Set;

//An extended UIDOperatorVerticle to expose classes for testing purposes
public class ExtendedUIDOperatorVerticle extends UIDOperatorVerticle {
Expand All @@ -36,4 +38,11 @@ public void setKeySharingEndpointProvideSiteDomainNames(boolean enable) {
this.keySharingEndpointProvideSiteDomainNames = enable;
}

public void setLastInvalidOriginProcessTime(Instant lastInvalidOriginProcessTime) {
this.lastInvalidOriginProcessTime = lastInvalidOriginProcessTime;
}

public void setSiteIdToInvalidOrigins(Map<Integer, Set<String>> siteIdToInvalidOrigins) {
this.siteIdToInvalidOrigins = siteIdToInvalidOrigins;
}
}
79 changes: 79 additions & 0 deletions src/test/java/com/uid2/operator/UIDOperatorVerticleTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.uid2.operator;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.read.ListAppender;
import com.uid2.operator.model.*;
import com.uid2.operator.model.IdentityScope;
import com.uid2.operator.monitoring.IStatsCollectorQueue;
Expand Down Expand Up @@ -48,6 +51,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.slf4j.LoggerFactory;

import javax.crypto.SecretKey;
import java.math.BigInteger;
Expand Down Expand Up @@ -148,6 +152,7 @@ private void setupConfig(JsonObject config) {
config.put("identity_v3", useIdentityV3());
config.put("client_side_token_generate", true);
config.put("key_sharing_endpoint_provide_site_domain_names", true);
config.put("client_side_token_generate_log_invalid_http_origins", true);
}

private static byte[] makeAesKey(String prefix) {
Expand Down Expand Up @@ -2723,6 +2728,80 @@ void cstgDomainNameCheckFails(boolean setOptoutCheckFlagInRequest, String httpOr
});
}

@ParameterizedTest
@CsvSource({
"true,http://gototest.com",
"false,http://gototest.com",
})
void cstgDomainNameCheckFailsAndLogInvalidHttpOrigin(boolean setOptoutCheckFlagInRequest, String httpOrigin, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException {
ListAppender<ILoggingEvent> logWatcher = new ListAppender<>();
logWatcher.start();
((Logger) LoggerFactory.getLogger(UIDOperatorVerticle.class)).addAppender(logWatcher);
this.uidOperatorVerticle.setLastInvalidOriginProcessTime(Instant.now().minusSeconds(3600));

setupCstgBackend();
Tuple.Tuple2<JsonObject, SecretKey> data = createClientSideTokenGenerateRequest(IdentityType.Email, "[email protected]", Instant.now().toEpochMilli(), setOptoutCheckFlagInRequest);
sendCstg(vertx,
"v2/token/client-generate",
httpOrigin,
data.getItem1(),
data.getItem2(),
403,
testContext,
respJson -> {
assertFalse(respJson.containsKey("body"));
assertEquals("unexpected http origin", respJson.getString("message"));
assertEquals("invalid_http_origin", respJson.getString("status"));
Assertions.assertTrue(logWatcher.list.get(0).getFormattedMessage().contains("InvalidHttpOrigin: site test (123): http://gototest.com"));
assertTokenStatusMetrics(
clientSideTokenGenerateSiteId,
TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2,
TokenResponseStatsCollector.ResponseStatus.InvalidHttpOrigin);
testContext.completeNow();
});
}

@ParameterizedTest
@CsvSource({
"true,http://gototest.com",
"false,http://gototest.com",
})
void cstgDomainNameCheckFailsAndLogSeveralInvalidHttpOrigin(boolean setOptoutCheckFlagInRequest, String httpOrigin, Vertx vertx, VertxTestContext testContext) throws NoSuchAlgorithmException, InvalidKeyException {
ListAppender<ILoggingEvent> logWatcher = new ListAppender<>();
logWatcher.start();
((Logger) LoggerFactory.getLogger(UIDOperatorVerticle.class)).addAppender(logWatcher);
this.uidOperatorVerticle.setLastInvalidOriginProcessTime(Instant.now().minusSeconds(3600));

Map<Integer, Set<String>> siteIdToInvalidOrigins = new HashMap<>();
siteIdToInvalidOrigins.put(clientSideTokenGenerateSiteId, new HashSet<>(Arrays.asList("http://localhost1.com", "http://localhost2.com")));
siteIdToInvalidOrigins.put(124, new HashSet<>(Arrays.asList("http://xyz1.com", "http://xyz2.com")));

this.uidOperatorVerticle.setSiteIdToInvalidOrigins(siteIdToInvalidOrigins);

setupCstgBackend();
when(siteProvider.getSite(124)).thenReturn(new Site(124, "test2", true, new HashSet<>()));

Tuple.Tuple2<JsonObject, SecretKey> data = createClientSideTokenGenerateRequest(IdentityType.Email, "[email protected]", Instant.now().toEpochMilli(), setOptoutCheckFlagInRequest);
sendCstg(vertx,
"v2/token/client-generate",
httpOrigin,
data.getItem1(),
data.getItem2(),
403,
testContext,
respJson -> {
assertFalse(respJson.containsKey("body"));
assertEquals("unexpected http origin", respJson.getString("message"));
assertEquals("invalid_http_origin", respJson.getString("status"));
Assertions.assertTrue(logWatcher.list.get(0).getFormattedMessage().contains("InvalidHttpOrigin: site test (123): http://localhost1.com, http://gototest.com, http://localhost2.com | site test2 (124): http://xyz1.com, http://xyz2.com"));
assertTokenStatusMetrics(
clientSideTokenGenerateSiteId,
TokenResponseStatsCollector.Endpoint.ClientSideTokenGenerateV2,
TokenResponseStatsCollector.ResponseStatus.InvalidHttpOrigin);
testContext.completeNow();
});
}

@ParameterizedTest
@CsvSource({
"true,https://cstg.co.uk",
Expand Down

0 comments on commit 1d00fb7

Please sign in to comment.