diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy index 59fb0b34c25..9bdb94f007d 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/PublicCountryIp.groovy @@ -4,7 +4,8 @@ enum PublicCountryIp { USA_IP("209.232.44.21", "d646:2414:17b2:f371:9b62:f176:b4c0:51cd"), UKR_IP("193.238.111.14", "3080:f30f:e4bc:0f56:41be:6aab:9d0a:58e2"), - CAN_IP("70.71.245.39", "f9b2:c742:1922:7d4b:7122:c7fc:8b75:98c8") + CAN_IP("70.71.245.39", "f9b2:c742:1922:7d4b:7122:c7fc:8b75:98c8"), + BGR_IP("31.211.128.0", "2002:1fd3:8000:0000:0000:0000:0000:0000") final String v4 final String v6 diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy index 9a9c62fde81..d7e9cb2242e 100644 --- a/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy +++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/RegsExt.groovy @@ -10,6 +10,7 @@ class RegsExt { @Deprecated(since = "enabling support of ortb 2.6") Integer gdpr + Integer coppa @Deprecated(since = "enabling support of ortb 2.6") String usPrivacy String gpc diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy index d4cfc7bbda9..34242d43cec 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAmpSpec.groovy @@ -8,7 +8,11 @@ import org.prebid.server.functional.model.config.AccountPrivacyConfig import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.db.StoredRequest +import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.request.auction.BidRequest +import org.prebid.server.functional.model.request.auction.DistributionChannel +import org.prebid.server.functional.model.request.auction.Regs +import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer import org.prebid.server.functional.util.PBSUtils @@ -27,6 +31,7 @@ import static org.prebid.server.functional.model.config.Purpose.P4 import static org.prebid.server.functional.model.config.PurposeEnforcement.BASIC import static org.prebid.server.functional.model.config.PurposeEnforcement.NO import static org.prebid.server.functional.model.mock.services.vendorlist.GvlSpecificationVersion.V3 +import static org.prebid.server.functional.model.pricefloors.Country.BULGARIA import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_ADAPTER_DISALLOWED_COUNT import static org.prebid.server.functional.model.privacy.Metric.TEMPLATE_REQUEST_DISALLOWED_COUNT import static org.prebid.server.functional.model.request.amp.ConsentType.BOGUS @@ -36,6 +41,7 @@ import static org.prebid.server.functional.model.request.auction.ActivityType.FE import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_EIDS import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD +import static org.prebid.server.functional.model.request.auction.PublicCountryIp.BGR_IP import static org.prebid.server.functional.model.response.auction.ErrorType.PREBID import static org.prebid.server.functional.util.privacy.CcpaConsent.Signal.ENFORCED import static org.prebid.server.functional.util.privacy.TcfConsent.GENERIC_VENDOR_ID @@ -686,4 +692,162 @@ class GdprAmpSpec extends PrivacyBaseSpec { where: tcfPolicyVersion << [TCF_POLICY_V4, TCF_POLICY_V5] } + + def "PBS should process with GDPR enforcement when GDPR and COPPA configurations are present in request"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Amp default request" + def ampRequest = getGdprAmpRequest(validConsentString) + + and: "Bid request with gdpr and coppa config" + def ampStoredRequest = getGdprBidRequest(DistributionChannel.SITE, validConsentString).tap { + regs = new Regs(gdpr: gdpr, coppa: coppa, ext: new RegsExt(gdpr: extGdpr, coppa: extCoppa)) + setAccountId(ampRequest.account) + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: PBSUtils.getRandomEnum(Country.class, [BULGARIA])) + def account = getAccountWithGdpr(ampRequest.account, accountGdprConfig) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes amp request" + privacyPbsService.sendAmpRequest(ampRequest) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(ampStoredRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + + where: + gdpr | coppa | extGdpr | extCoppa + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 0 + 1 | 1 | 1 | null + 1 | 1 | 0 | 1 + 1 | 1 | 0 | 0 + 1 | 1 | 0 | null + 1 | 1 | null | 1 + 1 | 1 | null | 0 + 1 | 1 | null | null + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 0 + 1 | 0 | 1 | null + 1 | 0 | 0 | 1 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | null + 1 | 0 | null | 1 + 1 | 0 | null | 0 + 1 | 0 | null | null + 1 | null | 1 | 1 + 1 | null | 1 | 0 + 1 | null | 1 | null + 1 | null | 0 | 1 + 1 | null | 0 | 0 + 1 | null | 0 | null + 1 | null | null | 1 + 1 | null | null | 0 + 1 | null | null | null + + null | 1 | 1 | 1 + null | 1 | 1 | 0 + null | 1 | 1 | null + null | 0 | 1 | 1 + null | 0 | 1 | 0 + null | 0 | 1 | null + null | null | 1 | 1 + null | null | 1 | 0 + null | null | 1 | null + } + + def "PBS should process with GDPR enforcement when request comes from EEA IP with COPPA enabled"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Amp default request" + def ampRequest = getGdprAmpRequest(validConsentString) + + and: "Bid request with gdpr and coppa config" + def ampStoredRequest = getGdprBidRequest(DistributionChannel.SITE, validConsentString).tap { + regs = new Regs(gdpr: 1, coppa: 1, ext: new RegsExt(gdpr: 1, coppa: 1)) + device.geo.country = requestCountry + device.geo.region = null + device.ip = requestIpV4 + device.ipv6 = requestIpV6 + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: accountCountry) + def account = getAccountWithGdpr(ampRequest.account, accountGdprConfig) + accountDao.save(account) + + and: "Stored request in DB" + def storedRequest = StoredRequest.getStoredRequest(ampRequest, ampStoredRequest) + storedRequestDao.save(storedRequest) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes amp request" + privacyPbsService.sendAmpRequest(ampRequest, header) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(ampStoredRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(ampStoredRequest, FETCH_BIDS)] == 1 + + where: + requestCountry | accountCountry | requestIpV4 | requestIpV6 | header + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | BGR_IP.v4 | null | [:] + BULGARIA | null | BGR_IP.v4 | null | [:] + BULGARIA | BULGARIA | null | BGR_IP.v6 | [:] + BULGARIA | null | null | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | null | null | [:] + BULGARIA | null | null | null | [:] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + null | null | BGR_IP.v4 | BGR_IP.v6 | [:] + null | BULGARIA | BGR_IP.v4 | null | [:] + null | null | BGR_IP.v4 | null | [:] + null | BULGARIA | null | BGR_IP.v6 | [:] + null | null | null | BGR_IP.v6 | [:] + null | BULGARIA | null | null | [:] + null | null | null | null | [:] + } } diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy index c4a21342ab9..66003fa2716 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GdprAuctionSpec.groovy @@ -5,7 +5,10 @@ import org.prebid.server.functional.model.ChannelType import org.prebid.server.functional.model.config.AccountGdprConfig import org.prebid.server.functional.model.config.PurposeConfig import org.prebid.server.functional.model.config.PurposeEnforcement +import org.prebid.server.functional.model.pricefloors.Country import org.prebid.server.functional.model.request.auction.DistributionChannel +import org.prebid.server.functional.model.request.auction.Regs +import org.prebid.server.functional.model.request.auction.RegsExt import org.prebid.server.functional.model.response.auction.ErrorType import org.prebid.server.functional.service.PrebidServerService import org.prebid.server.functional.testcontainers.container.PrebidServerContainer @@ -36,6 +39,7 @@ import static org.prebid.server.functional.model.request.auction.ActivityType.TR import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_PRECISE_GEO import static org.prebid.server.functional.model.request.auction.ActivityType.TRANSMIT_UFPD import static org.prebid.server.functional.model.request.auction.Prebid.Channel +import static org.prebid.server.functional.model.request.auction.PublicCountryIp.BGR_IP import static org.prebid.server.functional.model.request.auction.TraceLevel.BASIC import static org.prebid.server.functional.model.request.auction.TraceLevel.VERBOSE import static org.prebid.server.functional.model.response.auction.BidRejectionReason.REQUEST_BLOCKED_PRIVACY @@ -805,4 +809,147 @@ class GdprAuctionSpec extends PrivacyBaseSpec { where: tcfPolicyVersion << [TCF_POLICY_V4, TCF_POLICY_V5] } + + def "PBS should process with GDPR enforcement when GDPR and COPPA configurations are present in request"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Bid request with gdpr and coppa config" + def bidRequest = getGdprBidRequest(DistributionChannel.APP, validConsentString).tap { + regs = new Regs(gdpr: gdpr, coppa: coppa, ext: new RegsExt(gdpr: extGdpr, coppa: extCoppa)) + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: PBSUtils.getRandomEnum(Country.class, [BULGARIA])) + def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(bidRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + + where: + gdpr | coppa | extGdpr | extCoppa + 1 | 1 | 1 | 1 + 1 | 1 | 1 | 0 + 1 | 1 | 1 | null + 1 | 1 | 0 | 1 + 1 | 1 | 0 | 0 + 1 | 1 | 0 | null + 1 | 1 | null | 1 + 1 | 1 | null | 0 + 1 | 1 | null | null + 1 | 0 | 1 | 1 + 1 | 0 | 1 | 0 + 1 | 0 | 1 | null + 1 | 0 | 0 | 1 + 1 | 0 | 0 | 0 + 1 | 0 | 0 | null + 1 | 0 | null | 1 + 1 | 0 | null | 0 + 1 | 0 | null | null + 1 | null | 1 | 1 + 1 | null | 1 | 0 + 1 | null | 1 | null + 1 | null | 0 | 1 + 1 | null | 0 | 0 + 1 | null | 0 | null + 1 | null | null | 1 + 1 | null | null | 0 + 1 | null | null | null + + null | 1 | 1 | 1 + null | 1 | 1 | 0 + null | 1 | 1 | null + null | 0 | 1 | 1 + null | 0 | 1 | 0 + null | 0 | 1 | null + null | null | 1 | 1 + null | null | 1 | 0 + null | null | 1 | null + } + + def "PBS should process with GDPR enforcement when request comes from EEA IP with COPPA enabled"() { + given: "Valid consent string without basic ads" + def validConsentString = new TcfConsent.Builder() + .setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + + and: "Bid request with gdpr and coppa config" + def bidRequest = getGdprBidRequest(DistributionChannel.APP, validConsentString).tap { + regs = new Regs(gdpr: 1, coppa: 1, ext: new RegsExt(gdpr: 1, coppa: 1)) + device.geo.country = requestCountry + device.geo.region = null + device.ip = requestIpV4 + device.ipv6 = requestIpV6 + } + + and: "Save account config without eea countries into DB" + def accountGdprConfig = new AccountGdprConfig(enabled: true, eeaCountries: accountCountry) + def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig) + accountDao.save(account) + + and: "Flush metrics" + flushMetrics(privacyPbsService) + + when: "PBS processes auction request" + privacyPbsService.sendAuctionRequest(bidRequest, header) + + then: "Bidder shouldn't be called" + assert !bidder.getBidderRequests(bidRequest.id) + + then: "Metrics processed across activities should be updated" + def metrics = privacyPbsService.sendCollectedMetricsRequest() + assert metrics[TEMPLATE_ADAPTER_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + assert metrics[TEMPLATE_REQUEST_DISALLOWED_COUNT.getValue(bidRequest, FETCH_BIDS)] == 1 + + where: + requestCountry | accountCountry | requestIpV4 | requestIpV6 | header + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | BGR_IP.v4 | null | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | BGR_IP.v6 | ["X-Forwarded-For": BGR_IP.v4] + null | BULGARIA | null | null | ["X-Forwarded-For": BGR_IP.v4] + null | null | null | null | ["X-Forwarded-For": BGR_IP.v4] + BULGARIA | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | null | BGR_IP.v4 | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | BGR_IP.v4 | null | [:] + BULGARIA | null | BGR_IP.v4 | null | [:] + BULGARIA | BULGARIA | null | BGR_IP.v6 | [:] + BULGARIA | null | null | BGR_IP.v6 | [:] + BULGARIA | BULGARIA | null | null | [:] + BULGARIA | null | null | null | [:] + null | BULGARIA | BGR_IP.v4 | BGR_IP.v6 | [:] + null | null | BGR_IP.v4 | BGR_IP.v6 | [:] + null | BULGARIA | BGR_IP.v4 | null | [:] + null | null | BGR_IP.v4 | null | [:] + null | BULGARIA | null | BGR_IP.v6 | [:] + null | null | null | BGR_IP.v6 | [:] + null | BULGARIA | null | null | [:] + null | null | null | null | [:] + } }