diff --git a/src/main/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactory.java b/src/main/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactory.java index 087b72a67c6..61477f66084 100644 --- a/src/main/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactory.java +++ b/src/main/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactory.java @@ -1,5 +1,6 @@ package org.prebid.server.auction.privacy.contextfactory; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Geo; @@ -101,13 +102,17 @@ private static AccountGdprConfig accountGdprConfig(Account account) { } private static RequestLogInfo requestLogInfo(MetricName requestType, BidRequest bidRequest, String accountId) { - final String referrerUrl = MetricName.openrtb2web == requestType - ? Optional.ofNullable(bidRequest.getSite()) - .map(Site::getRef) - .orElse(null) - : null; - - return RequestLogInfo.of(requestType, referrerUrl, accountId); + final String source = switch (requestType) { + case MetricName.openrtb2web -> Optional.ofNullable(bidRequest.getSite()) + .map(Site::getRef) + .orElse(null); + case MetricName.openrtb2app -> Optional.ofNullable(bidRequest.getApp()) + .map(App::getBundle) + .orElse(null); + case null, default -> null; + }; + + return RequestLogInfo.of(requestType, source, accountId); } private static TcfContext logWarnings(List debugWarnings, TcfContext tcfContext) { diff --git a/src/main/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictness.java b/src/main/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictness.java index 13a5a5018a7..7d6716c10e8 100644 --- a/src/main/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictness.java +++ b/src/main/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictness.java @@ -24,6 +24,7 @@ public DisclosedVendorsStrictness(GdprConfig gdprConfig) { public boolean isValid(TCString consent) { return !strictnessEnabled + || consent.getVersion() == 1 || isCreatedBeforeTcfV2M3EnforcementCutoff(consent) || !consent.getDisclosedVendors().isEmpty(); } @@ -38,6 +39,7 @@ private boolean isCreatedBeforeTcfV2M3EnforcementCutoff(TCString consent) { public boolean isVendorDisclosed(TCString consent, Integer vendorId) { return !strictnessEnabled + || consent.getVersion() == 1 || (vendorId != null && (isCreatedBeforeTcfV2M3EnforcementCutoff(consent) || consent.getDisclosedVendors().contains(vendorId))); diff --git a/src/main/java/org/prebid/server/privacy/gdpr/TcfDefinerService.java b/src/main/java/org/prebid/server/privacy/gdpr/TcfDefinerService.java index 91e89d0e2b3..2fa6d20b837 100644 --- a/src/main/java/org/prebid/server/privacy/gdpr/TcfDefinerService.java +++ b/src/main/java/org/prebid/server/privacy/gdpr/TcfDefinerService.java @@ -421,8 +421,11 @@ private static void logWarn(String consent, String message, RequestLogInfo reque } private static String logMessage(String consent, String type, RequestLogInfo requestLogInfo, String message) { - return "Parsing consent string: \"%s\" failed for: %s type for account id: %s with ref: %s with exception: %s" - .formatted(consent, type, requestLogInfo.getAccountId(), requestLogInfo.getRefUrl(), message); + return """ + Parsing consent string: "%s" failed for: \ + %s type for account id: %s from source: \ + %s with exception: %s""" + .formatted(consent, type, requestLogInfo.getAccountId(), requestLogInfo.getSource(), message); } private static boolean isConsentValid(TCString consent) { diff --git a/src/main/java/org/prebid/server/privacy/gdpr/model/RequestLogInfo.java b/src/main/java/org/prebid/server/privacy/gdpr/model/RequestLogInfo.java index 55eb248b739..07993be1163 100644 --- a/src/main/java/org/prebid/server/privacy/gdpr/model/RequestLogInfo.java +++ b/src/main/java/org/prebid/server/privacy/gdpr/model/RequestLogInfo.java @@ -8,7 +8,7 @@ public class RequestLogInfo { MetricName requestType; - String refUrl; + String source; String accountId; } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/tcf/TcfGeneralSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/tcf/TcfGeneralSpec.groovy index d0c6d258b0e..4892e19fc37 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/tcf/TcfGeneralSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/tcf/TcfGeneralSpec.groovy @@ -1,16 +1,20 @@ package org.prebid.server.functional.tests.privacy.tcf import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.config.AccountGdprConfig import org.prebid.server.functional.model.config.Purpose +import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.privacy.EnforcementRequirement import org.prebid.server.functional.model.response.auction.NoBidResponse import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.util.PBSUtils +import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.functional.util.privacy.TcfUtils import java.time.ZoneId import java.time.ZonedDateTime +import static org.prebid.server.functional.model.config.PurposeEnforcement.NO import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID @@ -416,8 +420,53 @@ class TcfGeneralSpec extends TcfBaseSpec { assert metrics[TCF_NO_DISCLOSED_VENDORS_METRIC] == 1 where: - pbsService | serviceName - pbsWithoutGvlVendorsWithStrictDvTreatment | "without GVL vendors" - pbsWithMultipleGvlListsWithStrictDvTreatment | "with multiple GVL lists" + pbsService | serviceName + pbsWithoutGvlVendorsWithStrictDvTreatment | "without GVL vendors" + pbsWithMultipleGvlListsWithStrictDvTreatment | "with multiple GVL lists" + } + + def "PBS should reject auction for deprecated TCF v1 consent regardless of disclosed vendors configuration #serviceName"() { + given: "BidRequest with deprecated TCF v1 consent" + def tcfConsent = new TcfConsent.Builder() + .setPurposesConsent(TcfConsent.PurposeId.BASIC_ADS) + .setVersion(1) + .setDisclosedVendors([GENERIC_VENDOR_ID]) + .setCreateTime(ZonedDateTime.now()) + .setUpdatedTime(ZonedDateTime.now()) + .build() + def bidRequest = getGdprBidRequest(tcfConsent) + + and: "Account GDPR config with disabled vendor enforcement" + def purposes = [(Purpose.P2): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Flush metric" + flushMetrics(pbsService) + + when: "PBS processes auction requests" + def response = pbsService.sendAuctionRequest(bidRequest) + + then: "Response shouldn't contain seatBid" + assert response?.seatbid?.bid?.flatten()?.isEmpty() + + and: "PBS should emit proper nbr code" + assert response.noBidResponse == NoBidResponse.UNKNOWN_ERROR + + and: "PBS response shouldn't provide proper error" + assert !response.ext.errors + + and: "PBS should contain TCF deprecation warning" + assert response.ext.warnings[PREBID].message == ["Parsing consent string:\"${tcfConsent}\" failed. " + + "TCF version 1 is deprecated and treated as corrupted TCF version 2"] + + and: "PBS should not send bid request to bidder" + assert !bidder.getBidderRequests(bidRequest.id) + + where: + pbsService | serviceName + pbsWithoutGvlVendorsWithStrictDvTreatment | "without GVL vendors" + pbsWithMultipleGvlListsWithStrictDvTreatment | "with multiple GVL lists" } } diff --git a/src/test/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactoryTest.java b/src/test/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactoryTest.java index 79c1af860af..d61b421d5c0 100644 --- a/src/test/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactoryTest.java +++ b/src/test/java/org/prebid/server/auction/privacy/contextfactory/AuctionPrivacyContextFactoryTest.java @@ -1,5 +1,6 @@ package org.prebid.server.auction.privacy.contextfactory; +import com.iab.openrtb.request.App; import com.iab.openrtb.request.BidRequest; import com.iab.openrtb.request.Device; import com.iab.openrtb.request.Site; @@ -183,7 +184,9 @@ public void contextFromShouldAddRefUrlWhenPresentAndRequestTypeIsWeb() { given(tcfDefinerService.resolveTcfContext(any(), any(), any(), any(), any(), any(), any(), any())) .willReturn(Future.succeededFuture(TcfContext.empty())); - final BidRequest bidRequest = givenBidRequest(request -> request.site(Site.builder().ref("refUrl").build())); + final BidRequest bidRequest = givenBidRequest(request -> request + .site(Site.builder().ref("refUrl").build()) + .app(App.builder().bundle("bundle").build())); final AuctionContext auctionContext = givenAuctionContext(context -> context .requestTypeMetric(MetricName.openrtb2web) .httpRequest(givenHttpRequestContext("invalid")) @@ -198,6 +201,37 @@ public void contextFromShouldAddRefUrlWhenPresentAndRequestTypeIsWeb() { .resolveTcfContext(any(), any(), any(), any(), any(), eq(expectedRequestLogInfo), any(), any()); } + @Test + public void contextFromShouldAddBundleWhenPresentAndRequestTypeIsApp() { + // given + final Privacy privacy = Privacy.builder() + .gdpr("1") + .consentString("consent_string") + .ccpa(Ccpa.EMPTY) + .coppa(0) + .build(); + given(privacyExtractor.validPrivacyFrom(any(), any())).willReturn(privacy); + + given(tcfDefinerService.resolveTcfContext(any(), any(), any(), any(), any(), any(), any(), any())) + .willReturn(Future.succeededFuture(TcfContext.empty())); + + final BidRequest bidRequest = givenBidRequest(request -> request + .site(Site.builder().ref("refUrl").build()) + .app(App.builder().bundle("bundle").build())); + final AuctionContext auctionContext = givenAuctionContext(context -> context + .requestTypeMetric(MetricName.openrtb2app) + .httpRequest(givenHttpRequestContext("invalid")) + .bidRequest(bidRequest)); + + // when + target.contextFrom(auctionContext); + + // then + final RequestLogInfo expectedRequestLogInfo = RequestLogInfo.of(MetricName.openrtb2app, "bundle", null); + verify(tcfDefinerService) + .resolveTcfContext(any(), any(), any(), any(), any(), eq(expectedRequestLogInfo), any(), any()); + } + private static AuctionContext givenAuctionContext( UnaryOperator auctionContextCustomizer) { diff --git a/src/test/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictnessTest.java b/src/test/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictnessTest.java index 83730e20e0f..2559ea8fcd1 100644 --- a/src/test/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictnessTest.java +++ b/src/test/java/org/prebid/server/privacy/gdpr/DisclosedVendorsStrictnessTest.java @@ -52,6 +52,17 @@ public void isValidShouldReturnFalseIfStrictnessEnabledByDefault() { assertThat(target.isValid(tcString)).isFalse(); } + @Test + public void isValidShouldReturnTrueIfTCStringVersionEquals1() { + // given + final TCString tcString = TCStringEncoder.newBuilder() + .version(1) + .toTCString(); + + // when and then + assertThat(target.isValid(tcString)).isTrue(); + } + @Test public void isValidShouldReturnTrueOnInvalidIfLatestUpdateBeforeCutoffDate() { // given @@ -125,6 +136,17 @@ public void isVendorDisclosedShouldReturnFalseOnNull() { assertThat(target.isVendorDisclosed(tcString, null)).isFalse(); } + @Test + public void isVendorDisclosedShouldReturnTrueIfTCStringVersionEquals1() { + // given + final TCString tcString = TCStringEncoder.newBuilder() + .version(1) + .toTCString(); + + // when and then + assertThat(target.isVendorDisclosed(tcString, 1)).isTrue(); + } + @Test public void isVendorDisclosedShouldReturnTrueOnNotDisclosedIfLatestUpdateBeforeCutoffDate() { // given