From 038311d413b4e9d21dee0aa94eb88d1bc0550a8a Mon Sep 17 00:00:00 2001 From: Danylo Date: Thu, 17 Apr 2025 20:08:44 +0200 Subject: [PATCH 1/3] Pad Gpp sections --- .../auction/gpp/model/GppModelWrapper.java | 28 ++++++++++++++++-- .../gpp/model/GppModelWrapperTest.java | 29 ++++++++++++++++++- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java b/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java index e8f7d4e008b..bfe07f42199 100644 --- a/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java +++ b/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java @@ -11,13 +11,37 @@ public class GppModelWrapper extends GppModel { + private static final String SECTIONS_DELIMITER = "~"; + private static final String BASE64_PADDING_CHAR = "A"; + private static final int TCF_EU_V2_ID = 2; private static final int USP_V1_ID = 6; private IntObjectMap sectionIdToEncodedString; public GppModelWrapper(String encodedString) throws DecodingException { - super(encodedString); + super(padSections(encodedString)); + } + + private static String padSections(String encodedString) { + final String[] sections = sections(encodedString); + + boolean modified = false; + for (int i = 1; i < sections.length; i++) { + final String currentSection = sections[i]; + if (currentSection.length() % 4 > 0 && !currentSection.endsWith("=")) { + sections[i] = currentSection + BASE64_PADDING_CHAR; + modified = true; + } + } + + return modified + ? String.join(SECTIONS_DELIMITER, sections) + : encodedString; + } + + private static String[] sections(String encodedString) { + return encodedString.split(SECTIONS_DELIMITER); } private void init() { @@ -32,7 +56,7 @@ public void decode(String str) throws DecodingException { super.decode(str); init(); - final String[] encodedSections = str.split("~"); + final String[] encodedSections = sections(str); final List sectionIds = ((HeaderV1) getSection(HeaderV1.NAME)).getSectionsIds(); for (int i = 0; i < sectionIds.size(); i++) { diff --git a/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java b/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java index 587721151d5..57f4ea97b95 100644 --- a/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java +++ b/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java @@ -9,8 +9,11 @@ import org.junit.jupiter.api.Test; import java.util.Comparator; +import java.util.List; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatNoException; public class GppModelWrapperTest { @@ -35,7 +38,7 @@ public class GppModelWrapperTest { + "1YN-"; @Test - public void test() throws DecodingException, EncodingException { + public void wrapperShouldStoreSomeOfOriginalSections() throws DecodingException, EncodingException { // given and when final GppModel originalGpp = new GppModel(GPP_STRING); final GppModel wrappedGpp = new GppModelWrapper(GPP_STRING); @@ -48,6 +51,30 @@ public void test() throws DecodingException, EncodingException { assertThat(wrappedGpp.encodeSection(UspV1.ID)).isEqualTo(originalGpp.encodeSection(UspV1.ID)); } + @Test + public void wrapperShouldPadSectionsIfNeeded() { + // given + final List samples = List.of( + "DBABLA~BVQqAAAAAg", + "DBABLA~BVVqCAAACg", + "DBABLA~BVVVBAAABg", + "DBABLA~BVVqCACACg", + "DBABLA~BVQVAAAAAg", + "DBABLA~BVVVBABABg"); + + for (String sample : samples) { + // when + final GppModel originalGpp = new GppModel(sample); + final GppModel wrappedGpp = new GppModelWrapper(sample); + + // then + assertThatExceptionOfType(DecodingException.class) + .isThrownBy(() -> originalGpp.getUsNatSection().getMspaCoveredTransaction()); + assertThatNoException() + .isThrownBy(() -> wrappedGpp.getUsNatSection().getMspaCoveredTransaction()); + } + } + public static String normalizeEncodedTcfEuV2Section(String encodedSection) { try { final GppModel normalizer = new GppModel(); From d1be90baed0d2c88676801da1f48018d04054606 Mon Sep 17 00:00:00 2001 From: Danylo Date: Tue, 22 Apr 2025 16:03:39 +0200 Subject: [PATCH 2/3] Add support for subsections --- .../auction/gpp/model/GppModelWrapper.java | 38 ++++++++-------- .../gpp/model/GppContextCreatorTest.java | 2 +- .../gpp/model/GppModelWrapperTest.java | 44 +++++++++++++++++++ 3 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java b/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java index bfe07f42199..1bde141d72f 100644 --- a/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java +++ b/src/main/java/org/prebid/server/auction/gpp/model/GppModelWrapper.java @@ -11,9 +11,6 @@ public class GppModelWrapper extends GppModel { - private static final String SECTIONS_DELIMITER = "~"; - private static final String BASE64_PADDING_CHAR = "A"; - private static final int TCF_EU_V2_ID = 2; private static final int USP_V1_ID = 6; @@ -23,25 +20,30 @@ public GppModelWrapper(String encodedString) throws DecodingException { super(padSections(encodedString)); } - private static String padSections(String encodedString) { - final String[] sections = sections(encodedString); + private static String padSections(String gpp) { + final StringBuilder gppBuilder = new StringBuilder(gpp); + + int subsectionStart = 0; + int offset = 0; + for (int i = 1; i < gpp.length(); i++) { + final char currentChar = gpp.charAt(i); + + if (currentChar == '~' || currentChar == '.') { + if ((i - subsectionStart) % 4 != 0 && gpp.charAt(i - 1) != '=') { + gppBuilder.insert(i + offset, "A"); + offset++; + } - boolean modified = false; - for (int i = 1; i < sections.length; i++) { - final String currentSection = sections[i]; - if (currentSection.length() % 4 > 0 && !currentSection.endsWith("=")) { - sections[i] = currentSection + BASE64_PADDING_CHAR; - modified = true; + subsectionStart = i + 1; } } - return modified - ? String.join(SECTIONS_DELIMITER, sections) - : encodedString; - } + final int lastSubsectionLength = gpp.length() - subsectionStart; + if (lastSubsectionLength > 0 && lastSubsectionLength % 4 != 0 && !gpp.endsWith("=")) { + gppBuilder.append("A"); + } - private static String[] sections(String encodedString) { - return encodedString.split(SECTIONS_DELIMITER); + return gppBuilder.toString(); } private void init() { @@ -56,7 +58,7 @@ public void decode(String str) throws DecodingException { super.decode(str); init(); - final String[] encodedSections = sections(str); + final String[] encodedSections = str.split("~"); final List sectionIds = ((HeaderV1) getSection(HeaderV1.NAME)).getSectionsIds(); for (int i = 0; i < sectionIds.size(); i++) { diff --git a/src/test/java/org/prebid/server/auction/gpp/model/GppContextCreatorTest.java b/src/test/java/org/prebid/server/auction/gpp/model/GppContextCreatorTest.java index dd5efdbe4c2..2d977ac65e7 100644 --- a/src/test/java/org/prebid/server/auction/gpp/model/GppContextCreatorTest.java +++ b/src/test/java/org/prebid/server/auction/gpp/model/GppContextCreatorTest.java @@ -37,7 +37,7 @@ public void fromShouldReturnGppContextWrapperWithErrorOnInvalidGpp() { assertThat(gppContext.regions()).isEqualTo(GppContext.Regions.builder().build()); }); assertThat(gppContextWrapper.getErrors()) - .containsExactly("GPP string invalid: Unable to decode 'invalid'"); + .containsExactly("GPP string invalid: Unable to decode 'invalidA'"); } @Test diff --git a/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java b/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java index 57f4ea97b95..4f921fae0c3 100644 --- a/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java +++ b/src/test/java/org/prebid/server/auction/gpp/model/GppModelWrapperTest.java @@ -5,6 +5,7 @@ import com.iab.gpp.encoder.error.EncodingException; import com.iab.gpp.encoder.section.HeaderV1; import com.iab.gpp.encoder.section.TcfEuV2; +import com.iab.gpp.encoder.section.UsNat; import com.iab.gpp.encoder.section.UspV1; import org.junit.jupiter.api.Test; @@ -75,6 +76,49 @@ public void wrapperShouldPadSectionsIfNeeded() { } } + @Test + public void wrapperShouldNotModifyValidBase64SubsectionsWithPadChars() { + // given + final String gpp = "DBABLA~BVVVQAAARlA=.QA=="; + + // when + final GppModel wrappedGpp = new GppModelWrapper(gpp); + + // then + assertThat(wrappedGpp.encodeSection(UsNat.ID)).isEqualTo("BVVVQAAARlA=.QA=="); + } + + @Test + public void wrapperShouldNotModifyValidBase64SubsectionsWithoutPadChars() { + // given + final String gpp = "DBABLA~CqqqgAAAAIJo.YA=="; + + // when + final GppModel wrappedGpp = new GppModelWrapper(gpp); + + // then + assertThat(wrappedGpp.encodeSection(UsNat.ID)).isEqualTo("CqqqgAAAAIJo.YA=="); + assertThatNoException() + .isThrownBy(() -> wrappedGpp.getUsNatSection().getMspaCoveredTransaction()); + } + + @Test + public void wrapperShouldPadSubsections() { + // given + final String gpp = "DBABLA~BVVVQAAARl.Q"; + + // when + final GppModel originalGpp = new GppModel(gpp); + final GppModel wrappedGpp = new GppModelWrapper(gpp); + + // then + assertThat(wrappedGpp.encodeSection(UsNat.ID)).isEqualTo("BVVVQAAARlA.QA"); + assertThatExceptionOfType(DecodingException.class) + .isThrownBy(() -> originalGpp.getUsNatSection().getMspaCoveredTransaction()); + assertThatNoException() + .isThrownBy(() -> wrappedGpp.getUsNatSection().getMspaCoveredTransaction()); + } + public static String normalizeEncodedTcfEuV2Section(String encodedSection) { try { final GppModel normalizer = new GppModel(); From 9d6772ccd06682ff911351704d0e931b750f03bf Mon Sep 17 00:00:00 2001 From: osulzhenko <125548596+osulzhenko@users.noreply.github.com> Date: Fri, 25 Apr 2025 13:19:33 +0300 Subject: [PATCH 3/3] Tests: GPP decode error shouldn't fails entire auction (#3925) --- .../privacy/GppSyncUserActivitiesSpec.groovy | 68 ++++++++++ .../GppTransmitEidsActivitiesSpec.groovy | 89 +++++++++++++ .../GppTransmitUfpdActivitiesSpec.groovy | 123 +++++++++++++++++- .../tests/privacy/PrivacyBaseSpec.groovy | 1 + 4 files changed, 275 insertions(+), 6 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy index a6d9d6e7aad..657e0fef1e5 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppSyncUserActivitiesSpec.groovy @@ -483,6 +483,39 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS cookie sync call when privacy module contain invalid GPP string should exclude bidders URLs"() { + given: "Cookie sync request with link to account" + def accountId = PBSUtils.randomString + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = US_NAT_V1.value + it.account = accountId + it.gpp = INVALID_GPP_STRING + } + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = activityPbsService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should not contain any URLs for bidders" + assert !response.bidderStatus.userSync.url + + and: "Response should not contain any warning" + assert !response.warnings + } + def "PBS cookie sync call when request have different gpp consent but match and rejecting should exclude bidders URLs"() { given: "Cookie sync request with link to account" def accountId = PBSUtils.randomString @@ -1326,6 +1359,41 @@ class GppSyncUserActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS setuid request when privacy module contain invalid GPP string should reject bidders with status code invalidStatusCode"() { + given: "Cookie sync SetuidRequest with accountId" + def accountId = PBSUtils.randomString + def setuidRequest = SetuidRequest.defaultSetuidRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.gpp = INVALID_GPP_STRING + } + + and: "UIDS Cookie" + def uidsCookie = UidsCookie.defaultUidsCookie + + and: "Activities set for cookie sync with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(SYNC_USER, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with cookie sync and allow activities setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes cookie sync request" + activityPbsService.sendSetUidRequest(setuidRequest, uidsCookie) + + then: "Request should fail with error" + def exception = thrown(PrebidServerException) + assert exception.statusCode == INVALID_STATUS_CODE + assert exception.responseBody == INVALID_STATUS_MESSAGE + } + def "PBS setuid request when request have different gpp consent but match and rejecting should reject bidders with status code invalidStatusCode"() { given: "Cookie sync SetuidRequest with accountId" def accountId = PBSUtils.randomString diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy index 73dfda85a23..edd720700ca 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitEidsActivitiesSpec.groovy @@ -77,6 +77,7 @@ import static org.prebid.server.functional.model.request.auction.PrivacyModule.I import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_CUSTOM_LOGIC import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ONTARIO @@ -842,6 +843,45 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS auction call when privacy module contain invalid GPP string should remove EIDS fields in request"() { + given: "Default Generic BidRequests with EIDS fields and account id" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + regs.gppSid = [US_NAT_V1.intValue] + regs.gpp = INVALID_GPP_STRING + } + + and: "Activities set for transmitEIDS with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + def response = activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have empty EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(bidRequest.id) + verifyAll { + !genericBidderRequest.user.eids + !genericBidderRequest.user?.ext?.eids + } + + and: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should not contain any errors" + assert !response.ext.errors + } + def "PBS auction call when request have different gpp consent but match and rejecting should remove EIDS fields in request"() { given: "Default Generic BidRequests with EIDS fields and account id" def accountId = PBSUtils.randomNumber as String @@ -1830,6 +1870,55 @@ class GppTransmitEidsActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS amp call when privacy module contain invalid GPP string should remove EIDS fields in request"() { + given: "Default Generic BidRequest with EIDS fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = INVALID_GPP_STRING + it.consentType = GPP + } + + and: "Activities set for transmitEIDS with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_EIDS, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have empty EIDS fields" + def genericBidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + !genericBidderRequest.user.eids + !genericBidderRequest.user?.ext?.eids + } + + and: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should contain amp error" + assert response.ext?.errors[PREBID]*.code == [999] + assert response.ext?.errors[PREBID]*.message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + } + def "PBS amp call when request have different gpp consent but match and rejecting should remove EIDS fields in request"() { given: "Default Generic BidRequest with EIDS fields field and account id" def accountId = PBSUtils.randomNumber as String diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy index 920196ea576..0eaafecc6f0 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppTransmitUfpdActivitiesSpec.groovy @@ -15,16 +15,10 @@ import org.prebid.server.functional.model.request.amp.AmpRequest import org.prebid.server.functional.model.request.auction.Activity import org.prebid.server.functional.model.request.auction.ActivityRule import org.prebid.server.functional.model.request.auction.AllowActivities -import org.prebid.server.functional.model.request.auction.BidRequest import org.prebid.server.functional.model.request.auction.Condition -import org.prebid.server.functional.model.request.auction.Data import org.prebid.server.functional.model.request.auction.Device -import org.prebid.server.functional.model.request.auction.Eid import org.prebid.server.functional.model.request.auction.Geo import org.prebid.server.functional.model.request.auction.RegsExt -import org.prebid.server.functional.model.request.auction.User -import org.prebid.server.functional.model.request.auction.UserExt -import org.prebid.server.functional.model.request.auction.UserExtData import org.prebid.server.functional.service.PrebidServerException import org.prebid.server.functional.util.PBSUtils import org.prebid.server.functional.util.privacy.gpp.UsCaV1Consent @@ -87,6 +81,7 @@ import static org.prebid.server.functional.model.request.auction.PrivacyModule.I import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_CUSTOM_LOGIC import static org.prebid.server.functional.model.request.auction.PrivacyModule.IAB_US_GENERAL import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE +import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.util.privacy.model.State.ALABAMA import static org.prebid.server.functional.util.privacy.model.State.ONTARIO @@ -1114,6 +1109,59 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS auction call when privacy module contain invalid GPP string should remove UFPD fields in request"() { + given: "Default Generic BidRequests with UFPD fields and account id" + def accountId = PBSUtils.randomNumber as String + def bidRequest = getBidRequestWithPersonalData(accountId).tap { + regs.gppSid = [US_NAT_V1.intValue] + regs.gpp = INVALID_GPP_STRING + } + + and: "Activities set for transmitUfpd with rejecting privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + when: "PBS processes auction requests" + def response= activityPbsService.sendAuctionRequest(bidRequest) + + then: "Generic bidder request should have empty UFPD fields" + def bidderRequest = bidder.getBidderRequest(bidRequest.id) + verifyAll { + !bidderRequest.device.didsha1 + !bidderRequest.device.didmd5 + !bidderRequest.device.dpidsha1 + !bidderRequest.device.ifa + !bidderRequest.device.macsha1 + !bidderRequest.device.macmd5 + !bidderRequest.device.dpidmd5 + !bidderRequest.user.id + !bidderRequest.user.buyeruid + !bidderRequest.user.yob + !bidderRequest.user.gender + !bidderRequest.user.data + !bidderRequest.user.ext + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == bidRequest.user.eids + + and: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should not contain any errors" + assert !response.ext.errors + } + def "PBS auction call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { given: "Default Generic BidRequests with UFPD fields and account id" def accountId = PBSUtils.randomNumber as String @@ -2387,6 +2435,69 @@ class GppTransmitUfpdActivitiesSpec extends PrivacyBaseSpec { ] } + def "PBS amp call when privacy module contain invalid GPP string should remove UFPD fields in request"() { + given: "Default Generic BidRequest with UFPD fields field and account id" + def accountId = PBSUtils.randomNumber as String + def ampStoredRequest = getBidRequestWithPersonalData(accountId) + + and: "amp request with link to account" + def ampRequest = AmpRequest.defaultAmpRequest.tap { + it.account = accountId + it.gppSid = US_NAT_V1.value + it.consentString = INVALID_GPP_STRING + it.consentType = GPP + } + + and: "Activities set for transmitUfpd with allowing privacy regulation" + def rule = new ActivityRule().tap { + it.privacyRegulation = [IAB_US_GENERAL] + } + + def activities = AllowActivities.getDefaultAllowActivities(TRANSMIT_UFPD, Activity.getDefaultActivity([rule])) + + and: "Account gpp configuration" + def accountGppConfig = new AccountGppConfig(code: IAB_US_GENERAL, enabled: true) + + and: "Existed account with privacy regulation setup" + def account = getAccountWithAllowActivitiesAndPrivacyModule(accountId, activities, [accountGppConfig]) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + when: "PBS processes amp request" + def response = activityPbsService.sendAmpRequest(ampRequest) + + then: "Generic bidder request should have empty UFPD fields" + def bidderRequest = bidder.getBidderRequest(ampStoredRequest.id) + verifyAll { + !bidderRequest.device.didsha1 + !bidderRequest.device.didmd5 + !bidderRequest.device.dpidsha1 + !bidderRequest.device.ifa + !bidderRequest.device.macsha1 + !bidderRequest.device.macmd5 + !bidderRequest.device.dpidmd5 + !bidderRequest.user.id + !bidderRequest.user.buyeruid + !bidderRequest.user.yob + !bidderRequest.user.gender + !bidderRequest.user.data + !bidderRequest.user.ext + } + + and: "Generic bidder request should have data in EIDS fields" + assert bidderRequest.user.eids == ampStoredRequest.user.eids + + and: "Response should not contain any warnings" + assert !response.ext.warnings + + and: "Response should contain amp error" + assert response.ext?.errors[PREBID]*.code == [999] + assert response.ext?.errors[PREBID]*.message == ["Amp request parameter consent_string has invalid format: $INVALID_GPP_STRING"] + } + def "PBS amp call when request have different gpp consent but match and rejecting should remove UFPD fields in request"() { given: "Default Generic BidRequest with UFPD fields field and account id" def accountId = PBSUtils.randomNumber as String diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy index ed633ec5316..aff17c4d49b 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/PrivacyBaseSpec.groovy @@ -95,6 +95,7 @@ abstract class PrivacyBaseSpec extends BaseSpec { private static final Map GDPR_EEA_COUNTRY = ["gdpr.eea-countries": "$BULGARIA.ISOAlpha2, SK, VK" as String] protected static final String VENDOR_LIST_PATH = "/app/prebid-server/data/vendorlist-v{VendorVersion}/{VendorVersion}.json" + protected static final String INVALID_GPP_STRING = "DBABLA~BVQqAAAAAg.YA" // TODO replace BVQqAAAAAg with ${PBSUtils.getRandomString(7)} when proper fix is ready protected static final String VALID_VALUE_FOR_GPC_HEADER = "1" protected static final GppConsent SIMPLE_GPC_DISALLOW_LOGIC = new UsNatV1Consent.Builder().setGpc(true).build() protected static final VendorList vendorListResponse = new VendorList(networkServiceContainer)