From de4084f2188aa16fea4ee694b01a6f370d463f04 Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Tue, 3 Feb 2026 10:31:18 +1100 Subject: [PATCH 1/3] Remove SF1 (precise geolocation) consent validation for EUID The Special Feature 1 validation was a legacy requirement from LiveRamp that is no longer needed. This change allows EUID token generation for users who have the required TCF purpose consents but don't have SF1 (precise geolocation) enabled. Resolves: UID2-6532 Co-authored-by: Cursor --- .../java/com/uid2/operator/vertx/UIDOperatorVerticle.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java index 7b36e8eea..5f879d768 100644 --- a/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java +++ b/src/main/java/com/uid2/operator/vertx/UIDOperatorVerticle.java @@ -12,7 +12,6 @@ import com.uid2.operator.privacy.tcf.TransparentConsent; import com.uid2.operator.privacy.tcf.TransparentConsentParseResult; import com.uid2.operator.privacy.tcf.TransparentConsentPurpose; -import com.uid2.operator.privacy.tcf.TransparentConsentSpecialFeature; import com.uid2.operator.service.*; import com.uid2.operator.store.*; import com.uid2.operator.store.IConfigStore; @@ -1632,9 +1631,7 @@ private UserConsentStatus validateUserConsent(JsonObject req, String apiContact) TransparentConsentPurpose.MEASURE_AD_PERFORMANCE, // 7 TransparentConsentPurpose.DEVELOP_AND_IMPROVE_PRODUCTS // 10 ); - final boolean allowPreciseGeo = tcResult.getTCString().hasSpecialFeature(TransparentConsentSpecialFeature.PreciseGeolocationData); - - if (!userConsent || !allowPreciseGeo) { + if (!userConsent) { return UserConsentStatus.INSUFFICIENT; } } From 17d7b31624ec59f28841d10a2e130d5db413a592 Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Tue, 3 Feb 2026 10:57:43 +1100 Subject: [PATCH 2/3] Add test to verify consent passes when PreciseGeolocation special feature is missing Ensures TCF consent validation does not require SF1 (PreciseGeolocationData) opt-in. Co-authored-by: Cursor --- .../operator/EUIDOperatorVerticleTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java b/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java index d9a4ef419..3d6cbc692 100644 --- a/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java +++ b/src/test/java/com/uid2/operator/EUIDOperatorVerticleTest.java @@ -2,6 +2,8 @@ import org.junit.jupiter.api.Test; +import com.iabtcf.encoder.TCStringEncoder; +import com.iabtcf.utils.BitSetIntIterable; import com.uid2.operator.model.IdentityScope; import com.uid2.shared.auth.Role; @@ -9,6 +11,8 @@ import io.vertx.core.json.JsonObject; import io.vertx.junit5.VertxTestContext; +import java.time.Instant; + import static org.junit.jupiter.api.Assertions.*; class EUIDOperatorVerticleTest extends UIDOperatorVerticleTest { @@ -75,5 +79,54 @@ void noContentOnInsufficientTcfConsent(Vertx vertx, VertxTestContext testContext testContext.completeNow(); }); } + + @Test + void consentPassesWhenPreciseGeolocationSpecialFeatureIsMissing(Vertx vertx, VertxTestContext testContext) { + final int clientSiteId = 201; + fakeAuth(clientSiteId, Role.GENERATOR); + setupSalts(); + setupKeys(); + + final String emailAddress = "test@uid2.com"; + final JsonObject v2Payload = new JsonObject(); + v2Payload.put("email", emailAddress); + // TCF string with all required purposes but WITHOUT PreciseGeolocation special feature (feature 1) + String tcfStringWithoutPreciseGeolocation = createTcfConsentString( + new int[] { 21 }, // vendor consent + new int[] { 21 }, // vendor LI + new int[] { 1, 3, 4 }, // purpose consents (1, 3, 4) + new int[] { 2, 7, 10 }, // purpose LI (2, 7, 10) + new int[] {} // NO special features - PreciseGeolocation (1) is missing + ); + v2Payload.put("tcf_consent_string", tcfStringWithoutPreciseGeolocation); + sendTokenGenerate(vertx, v2Payload, 200, json -> { + assertTrue(json.containsKey("body")); + assertEquals("success", json.getString("status")); + testContext.completeNow(); + }); + } + + private String createTcfConsentString(int[] vendorConsent, int[] vendorLI, int[] purposesConsent, int[] purposesLI, int[] specialFeatureOptIns) { + return TCStringEncoder.newBuilder() + .version(2) + .created(Instant.now()) + .lastUpdated(Instant.now()) + .cmpId(1) + .cmpVersion(12) + .consentScreen(1) + .consentLanguage("FR") + .vendorListVersion(2) + .tcfPolicyVersion(1) + .isServiceSpecific(true) + .useNonStandardStacks(false) + .addSpecialFeatureOptIns(BitSetIntIterable.from(specialFeatureOptIns)) + .publisherCC("DE") + .addVendorConsent(BitSetIntIterable.from(vendorConsent)) + .addVendorLegitimateInterest(BitSetIntIterable.from(vendorLI)) + .purposeOneTreatment(true) + .addPurposesConsent(BitSetIntIterable.from(purposesConsent)) + .addPurposesLITransparency(BitSetIntIterable.from(purposesLI)) + .encode(); + } } From 49e6f5e9980ff5b2fdc0b5ef1da76bfde67ce914 Mon Sep 17 00:00:00 2001 From: Sunny Wu Date: Tue, 3 Feb 2026 15:01:29 +1100 Subject: [PATCH 3/3] Removed TransparentConsentSpecialFeature.java --- .../uid2/operator/privacy/tcf/TransparentConsent.java | 4 ---- .../privacy/tcf/TransparentConsentSpecialFeature.java | 11 ----------- 2 files changed, 15 deletions(-) delete mode 100644 src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentSpecialFeature.java diff --git a/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsent.java b/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsent.java index 08640305c..c6aaf17c5 100644 --- a/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsent.java +++ b/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsent.java @@ -39,8 +39,4 @@ public boolean hasConsent(int vendorId, TransparentConsentPurpose ... purposes) .reduce(0, (f, x) -> (f | (1 << x))) & requiredBits) == requiredBits; } - - public boolean hasSpecialFeature(TransparentConsentSpecialFeature feature) { - return this.tcString.getSpecialFeatureOptIns().contains(feature.value); - } } diff --git a/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentSpecialFeature.java b/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentSpecialFeature.java deleted file mode 100644 index 3c1d1efbc..000000000 --- a/src/main/java/com/uid2/operator/privacy/tcf/TransparentConsentSpecialFeature.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.uid2.operator.privacy.tcf; - -public enum TransparentConsentSpecialFeature { - PreciseGeolocationData (1), - ActiveScanDeviceCharacteristics (2); - - public final int value; - private TransparentConsentSpecialFeature(int value) { - this.value = value; - } -}