diff --git a/config/config.go b/config/config.go index edecda25853..5cd2b1e463f 100644 --- a/config/config.go +++ b/config/config.go @@ -358,6 +358,9 @@ type DisabledMetrics struct { // server establishes with bidder servers such as the number of connections // that were created or reused. AdapterConnectionMetrics bool `mapstructure:"adapter_connections_metrics"` + + // True if we don't want to collect the per adapter GDPR request blocked metric + AdapterGDPRRequestBlocked bool `mapstructure:"adapter_gdpr_request_blocked"` } func (cfg *Metrics) validate(errs []error) []error { @@ -728,6 +731,7 @@ func SetupViper(v *viper.Viper, filename string) { // no metrics configured by default (metrics{host|database|username|password}) v.SetDefault("metrics.disabled_metrics.account_adapter_details", false) v.SetDefault("metrics.disabled_metrics.adapter_connections_metrics", true) + v.SetDefault("metrics.disabled_metrics.adapter_gdpr_request_blocked", false) v.SetDefault("metrics.influxdb.host", "") v.SetDefault("metrics.influxdb.database", "") v.SetDefault("metrics.influxdb.username", "") diff --git a/config/config_test.go b/config/config_test.go index 43ee8fa21df..1d4c00a5cd1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -135,6 +135,7 @@ func TestDefaults(t *testing.T) { cmpInts(t, "metrics.influxdb.collection_rate_seconds", cfg.Metrics.Influxdb.MetricSendInterval, 20) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, false) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) + cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, false) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "") cmpBools(t, "stored_requests.filesystem.enabled", false, cfg.StoredRequests.Files.Enabled) cmpStrings(t, "stored_requests.filesystem.directorypath", "./stored_requests/data/by_id", cfg.StoredRequests.Files.Path) @@ -197,6 +198,7 @@ metrics: disabled_metrics: account_adapter_details: true adapter_connections_metrics: true + adapter_gdpr_request_blocked: true datacache: type: postgres filename: /usr/db/db.db @@ -413,6 +415,7 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "auto_gen_source_tid", cfg.AutoGenSourceTID, false) cmpBools(t, "account_adapter_details", cfg.Metrics.Disabled.AccountAdapterDetails, true) cmpBools(t, "adapter_connections_metrics", cfg.Metrics.Disabled.AdapterConnectionMetrics, true) + cmpBools(t, "adapter_gdpr_request_blocked", cfg.Metrics.Disabled.AdapterGDPRRequestBlocked, true) cmpStrings(t, "certificates_file", cfg.PemCertsFile, "/etc/ssl/cert.pem") cmpStrings(t, "request_validation.ipv4_private_networks", cfg.RequestValidation.IPv4PrivateNetworks[0], "1.1.1.0/24") cmpStrings(t, "request_validation.ipv6_private_networks", cfg.RequestValidation.IPv6PrivateNetworks[0], "1111::/16") diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index f8b4f9d9b77..2062589e895 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -441,8 +441,9 @@ func TestShouldUsersync(t *testing.T) { type auctionMockPermissions struct { allowBidderSync bool allowHostCookies bool - allowGeo bool - allowID bool + allowBidRequest bool + passGeo bool + passID bool } func (m *auctionMockPermissions) HostCookiesAllowed(ctx context.Context, gdprSignal gdpr.Signal, consent string) (bool, error) { @@ -453,8 +454,8 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return m.allowGeo, m.allowID, nil +func (m *auctionMockPermissions) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + return m.allowBidRequest, m.passGeo, m.passID, nil } func TestBidSizeValidate(t *testing.T) { diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index bfd6c507de5..561b4ed7101 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -254,6 +254,6 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return true, true, nil +func (g *gdprPerms) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest, passGeo bool, passID bool, err error) { + return true, true, true, nil } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index 45ea2490c5b..caeb09a858c 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -439,8 +439,8 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return g.personalInfoAllowed, g.personalInfoAllowed, nil +func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + return g.personalInfoAllowed, g.personalInfoAllowed, g.personalInfoAllowed, nil } func newFakeSyncer(familyName string) usersync.Usersyncer { diff --git a/exchange/exchange.go b/exchange/exchange.go index ae9ddb093bd..848340b2cea 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -182,7 +182,7 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog * usersyncIfAmbiguous := e.parseUsersyncIfAmbiguous(r.BidRequest) // Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, usersyncIfAmbiguous, e.privacyConfig, &r.Account) + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.gDPR, e.me, usersyncIfAmbiguous, e.privacyConfig, &r.Account) e.me.RecordRequestPrivacy(privacyLabels) diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index fbce03f6810..7a2020c1819 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -1771,7 +1771,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string] me: metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames()), cache: &wellBehavedCache{}, cacheTime: 0, - gDPR: gdpr.AlwaysFail{}, + gDPR: &permissionsMock{allowAllBidders: true}, currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)), UsersyncIfAmbiguous: privacyConfig.GDPR.UsersyncIfAmbiguous, privacyConfig: privacyConfig, diff --git a/exchange/utils.go b/exchange/utils.go index 494c77e7a62..0b7ee28f484 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -56,9 +56,10 @@ func cleanOpenRTBRequests(ctx context.Context, req AuctionRequest, requestExt *openrtb_ext.ExtRequest, gDPR gdpr.Permissions, + metricsEngine metrics.MetricsEngine, usersyncIfAmbiguous bool, privacyConfig config.Privacy, - account *config.Account) (bidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { + account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { impsByBidder, err := splitImps(req.BidRequest.Imp) if err != nil { @@ -71,9 +72,10 @@ func cleanOpenRTBRequests(ctx context.Context, return } - bidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases) + var allBidderRequests []BidderRequest + allBidderRequests, errs = getAuctionBidderRequests(req, requestExt, impsByBidder, aliases) - if len(bidderRequests) == 0 { + if len(allBidderRequests) == 0 { return } @@ -117,7 +119,10 @@ func cleanOpenRTBRequests(ctx context.Context, } // bidder level privacy policies - for _, bidderRequest := range bidderRequests { + allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests)) + for _, bidderRequest := range allBidderRequests { + bidRequestAllowed := true + // CCPA privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) @@ -133,7 +138,9 @@ func cleanOpenRTBRequests(ctx context.Context, } } var publisherID = req.LegacyLabels.PubID - geo, id, err := gDPR.PersonalInfoAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) + bidReq, geo, id, err := gDPR.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, publisherID, gdprSignal, consent, weakVendorEnforcement) + bidRequestAllowed = bidReq + if err == nil { privacyEnforcement.GDPRGeo = !geo privacyEnforcement.GDPRID = !id @@ -141,9 +148,16 @@ func cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.GDPRGeo = true privacyEnforcement.GDPRID = true } + + if !bidRequestAllowed { + metricsEngine.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) + } } - privacyEnforcement.Apply(bidderRequest.BidRequest) + if bidRequestAllowed { + privacyEnforcement.Apply(bidderRequest.BidRequest) + allowedBidderRequests = append(allowedBidderRequests, bidderRequest) + } } return diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 9f6041e9e4a..50636d35ccd 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -14,14 +14,16 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" ) // permissionsMock mocks the Permissions interface for tests -// -// It only allows appnexus for GDPR consent type permissionsMock struct { - personalInfoAllowed bool - personalInfoAllowedError error + allowAllBidders bool + allowedBidders []openrtb_ext.BidderName + passGeo bool + passID bool + activitiesError error } func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, gdpr gdpr.Signal, consent string) (bool, error) { @@ -32,8 +34,18 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return p.personalInfoAllowed, p.personalInfoAllowed, p.personalInfoAllowedError +func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + if p.allowAllBidders { + return true, p.passGeo, p.passID, p.activitiesError + } + + for _, allowedBidder := range p.allowedBidders { + if bidder == allowedBidder { + allowBidRequest = true + } + } + + return allowBidRequest, p.passGeo, p.passID, p.activitiesError } func assertReq(t *testing.T, bidderRequests []BidderRequest, @@ -465,7 +477,9 @@ func TestCleanOpenRTBRequests(t *testing.T) { } for _, test := range testCases { - bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + metricsMock := metrics.MetricsEngineMock{} + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, &permissions, &metricsMock, true, privacyConfig, nil) if test.hasError { assert.NotNil(t, err, "Error shouldn't be nil") } else { @@ -620,7 +634,8 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) { context.Background(), auctionReq, nil, - &permissionsMock{personalInfoAllowed: true}, + &permissionsMock{allowAllBidders: true, passGeo: true, passID: true}, + &metrics.MetricsEngineMock{}, true, privacyConfig, nil) @@ -681,7 +696,9 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) { Enforce: true, }, } - _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + _, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, &permissions, &metrics, true, privacyConfig, nil) assert.ElementsMatch(t, []error{test.expectError}, errs, test.description) } @@ -721,7 +738,9 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{}, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, config.Privacy{}, nil) result := bidderRequests[0] assert.Nil(t, errs) @@ -828,7 +847,9 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) { UserSyncs: &emptyUsersync{}, } - bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissionsMock{}, true, config.Privacy{}, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, &permissions, &metrics, true, config.Privacy{}, nil) if test.hasError == true { assert.NotNil(t, errs) assert.Len(t, bidderRequests, 0) @@ -1409,7 +1430,9 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { }, } - results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig, nil) + permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true} + metrics := metrics.MetricsEngineMock{} + results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, &permissions, &metrics, true, privacyConfig, nil) result := results[0] assert.Nil(t, errs) @@ -1424,7 +1447,7 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) { } } -func TestCleanOpenRTBRequestsGDPR(t *testing.T) { +func TestCleanOpenRTBRequestsGDPRScrub(t *testing.T) { tcf1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY" tcf2Consent := "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA" trueValue, falseValue := true, false @@ -1624,7 +1647,8 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { context.Background(), auctionReq, nil, - &permissionsMock{personalInfoAllowed: !test.gdprScrub, personalInfoAllowedError: test.permissionsError}, + &permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError}, + &metrics.MetricsEngineMock{}, test.userSyncIfAmbiguous, privacyConfig, nil) @@ -1647,6 +1671,97 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) { } } +func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) { + testCases := []struct { + description string + gdprEnforced bool + gdprAllowedBidders []openrtb_ext.BidderName + expectedBidders []openrtb_ext.BidderName + expectedBlockedBidders []openrtb_ext.BidderName + }{ + { + description: "gdpr enforced, one request allowed and one request blocked", + gdprEnforced: true, + gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, + expectedBlockedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderRubicon}, + }, + { + description: "gdpr enforced, two requests allowed and no requests blocked", + gdprEnforced: true, + gdprAllowedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBlockedBidders: []openrtb_ext.BidderName{}, + }, + { + description: "gdpr not enforced, two requests allowed and no requests blocked", + gdprEnforced: false, + gdprAllowedBidders: []openrtb_ext.BidderName{}, + expectedBidders: []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus, openrtb_ext.BidderRubicon}, + expectedBlockedBidders: []openrtb_ext.BidderName{}, + }, + } + + for _, test := range testCases { + req := newBidRequest(t) + req.Regs = &openrtb2.Regs{ + Ext: json.RawMessage(`{"gdpr":1}`), + } + req.Imp[0].Ext = json.RawMessage(`{"appnexus": {"placementId": 1}, "rubicon": {}}`) + + privacyConfig := config.Privacy{ + GDPR: config.GDPR{ + Enabled: test.gdprEnforced, + UsersyncIfAmbiguous: true, + TCF2: config.TCF2{ + Enabled: true, + }, + }, + } + + accountConfig := config.Account{ + GDPR: config.AccountGDPR{ + Enabled: nil, + }, + } + + auctionReq := AuctionRequest{ + BidRequest: req, + UserSyncs: &emptyUsersync{}, + Account: accountConfig, + } + + metricsMock := metrics.MetricsEngineMock{} + metricsMock.Mock.On("RecordAdapterGDPRRequestBlocked", mock.Anything).Return() + + results, _, errs := cleanOpenRTBRequests( + context.Background(), + auctionReq, + nil, + &permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil}, + &metricsMock, + true, + privacyConfig, + nil) + + // extract bidder name from each request in the results + bidders := []openrtb_ext.BidderName{} + for _, req := range results { + bidders = append(bidders, req.BidderName) + } + + assert.Empty(t, errs, test.description) + assert.ElementsMatch(t, bidders, test.expectedBidders, test.description) + + for _, blockedBidder := range test.expectedBlockedBidders { + metricsMock.AssertCalled(t, "RecordAdapterGDPRRequestBlocked", blockedBidder) + } + for _, allowedBidder := range test.expectedBidders { + metricsMock.AssertNotCalled(t, "RecordAdapterGDPRRequestBlocked", allowedBidder) + } + } +} + // newAdapterAliasBidRequest builds a BidRequest with aliases func newAdapterAliasBidRequest(t *testing.T) *openrtb2.BidRequest { dnt := int8(1) diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 2b450160b62..ffd5ced462a 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -25,7 +25,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) + AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidReq bool, passGeo bool, passID bool, err error) } // Versions of the GDPR TCF technical specification. diff --git a/gdpr/impl.go b/gdpr/impl.go index a5583225e5d..312d60e14c5 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -58,35 +58,35 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, +func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, - weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { + weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok { - return true, true, nil + return true, true, true, nil } gdprSignal = p.normalizeGDPR(gdprSignal) if gdprSignal == SignalNo { - return true, true, nil + return true, true, true, nil } if consent == "" && gdprSignal == SignalYes { - return false, false, nil + return false, false, false, nil } if id, ok := p.vendorIDs[bidder]; ok { - return p.allowPI(ctx, id, consent, weakVendorEnforcement) + return p.allowActivities(ctx, id, consent, weakVendorEnforcement) } return p.defaultVendorPermissions() } -func (p *permissionsImpl) defaultVendorPermissions() (allowGeo bool, allowID bool, err error) { - return false, false, nil +func (p *permissionsImpl) defaultVendorPermissions() (allowBidRequest bool, passGeo bool, passID bool, err error) { + return false, false, false, nil } func (p *permissionsImpl) normalizeGDPR(gdprSignal Signal) Signal { @@ -139,48 +139,51 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen return false, nil } -func (p *permissionsImpl) allowPI(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, consent) if err != nil { - return false, false, err + return false, false, false, err } if vendor == nil { - return false, false, nil + return false, false, false, nil } if parsedConsent.Version() == 2 { if p.cfg.TCF2.Enabled { - return p.allowPITCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) + return p.allowActivitiesTCF2(parsedConsent, vendor, vendorID, weakVendorEnforcement) } if (vendor.Purpose(consentconstants.InfoStorageAccess) || vendor.LegitimateInterest(consentconstants.InfoStorageAccess) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.InfoStorageAccess) && (vendor.Purpose(consentconstants.PersonalizationProfile) || vendor.LegitimateInterest(consentconstants.PersonalizationProfile) || weakVendorEnforcement) && parsedConsent.PurposeAllowed(consentconstants.PersonalizationProfile) && (parsedConsent.VendorConsent(vendorID) || weakVendorEnforcement) { - return true, true, nil + return true, true, true, nil } } else { if (vendor.Purpose(tcf1constants.InfoStorageAccess) || vendor.LegitimateInterest(tcf1constants.InfoStorageAccess)) && parsedConsent.PurposeAllowed(tcf1constants.InfoStorageAccess) && (vendor.Purpose(tcf1constants.AdSelectionDeliveryReporting) || vendor.LegitimateInterest(tcf1constants.AdSelectionDeliveryReporting)) && parsedConsent.PurposeAllowed(tcf1constants.AdSelectionDeliveryReporting) && parsedConsent.VendorConsent(vendorID) { - return true, true, nil + return true, true, true, nil } } - return false, false, nil + return true, false, false, nil } -func (p *permissionsImpl) allowPITCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { +func (p *permissionsImpl) allowActivitiesTCF2(parsedConsent api.VendorConsents, vendor api.Vendor, vendorID uint16, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { consent, ok := parsedConsent.(tcf2.ConsentMetadata) - err = nil - allowGeo = false - allowID = false if !ok { err = fmt.Errorf("Unable to access TCF2 parsed consent") return } + if p.cfg.TCF2.SpecialPurpose1.Enabled { - allowGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + passGeo = consent.SpecialFeatureOptIn(1) && (vendor.SpecialPurpose(1) || weakVendorEnforcement) + } else { + passGeo = true + } + if p.cfg.TCF2.Purpose2.Enabled { + allowBidRequest = p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(2), weakVendorEnforcement) } else { - allowGeo = true + allowBidRequest = true } for i := 2; i <= 10; i++ { if p.checkPurpose(consent, vendor, vendorID, tcf1constants.Purpose(i), weakVendorEnforcement) { - allowID = true + passID = true break } } @@ -258,23 +261,8 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return true, true, nil -} - -// Exporting to allow for easy test setups -type AlwaysFail struct{} - -func (a AlwaysFail) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) { - return false, nil -} - -func (a AlwaysFail) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) { - return false, nil -} - -func (a AlwaysFail) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowGeo bool, allowID bool, err error) { - return false, false, nil +func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) { + return true, true, true, nil } func (a AlwaysAllow) AMPException() bool { diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index cc08aedd06b..3b974ffa3bb 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -169,7 +169,7 @@ func TestMalformedConsent(t *testing.T) { assertBoolsEqual(t, false, sync) } -func TestAllowPersonalInfo(t *testing.T) { +func TestAllowActivities(t *testing.T) { bidderAllowedByConsent := openrtb_ext.BidderAppnexus bidderBlockedByConsent := openrtb_ext.BidderRubicon consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA" @@ -181,7 +181,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous bool gdpr Signal consent string - allowID bool + passID bool weakVendorEnforcement bool }{ { @@ -191,7 +191,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalYes, consent: consent, - allowID: true, + passID: true, }, { description: "Allow PI - known vendor with No GDPR", @@ -199,7 +199,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalNo, consent: consent, - allowID: true, + passID: true, }, { description: "Allow PI - known vendor with Yes GDPR", @@ -207,7 +207,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalYes, consent: consent, - allowID: true, + passID: true, }, { description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and empty consent", @@ -215,7 +215,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: true, gdpr: SignalAmbiguous, consent: "", - allowID: true, + passID: true, }, { description: "PI allowed according to host setting UserSyncIfAmbiguous true - known vendor with ambiguous GDPR and non-empty consent", @@ -223,7 +223,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: true, gdpr: SignalAmbiguous, consent: consent, - allowID: true, + passID: true, }, { description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and empty consent", @@ -231,7 +231,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalAmbiguous, consent: "", - allowID: false, + passID: false, }, { description: "PI allowed according to host setting UserSyncIfAmbiguous false - known vendor with ambiguous GDPR and non-empty consent", @@ -239,7 +239,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalAmbiguous, consent: consent, - allowID: true, + passID: true, }, { description: "Don't allow PI - known vendor with Yes GDPR and empty consent", @@ -247,7 +247,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalYes, consent: "", - allowID: false, + passID: false, }, { description: "Don't allow PI - default vendor with Yes GDPR and non-empty consent", @@ -255,7 +255,7 @@ func TestAllowPersonalInfo(t *testing.T) { userSyncIfAmbiguous: false, gdpr: SignalYes, consent: consent, - allowID: false, + passID: false, }, } @@ -286,10 +286,10 @@ func TestAllowPersonalInfo(t *testing.T) { for _, tt := range tests { perms.cfg.UsersyncIfAmbiguous = tt.userSyncIfAmbiguous - _, allowID, err := perms.PersonalInfoAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) + _, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement) assert.Nil(t, err, tt.description) - assert.Equal(t, tt.allowID, allowID, tt.description) + assert.Equal(t, tt.passID, passID, tt.description) } } @@ -347,12 +347,13 @@ type tcf2TestDef struct { description string bidder openrtb_ext.BidderName consent string - allowGeo bool - allowID bool + allowBid bool + passGeo bool + passID bool weakVendorEnforcement bool } -func TestAllowPersonalInfoTCF2(t *testing.T) { +func TestAllowActivitiesTCF2(t *testing.T) { vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, @@ -372,36 +373,39 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { } // COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8 - // PI needs all purposes to succeed testDefs := []tcf2TestDef{ { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowGeo: false, - allowID: false, + allowBid: false, + passGeo: false, + passID: false, }, { description: "Appnexus vendor test, insufficient purposes claimed, basic enforcement", bidder: openrtb_ext.BidderAppnexus, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowGeo: true, - allowID: true, + allowBid: true, + passGeo: true, + passID: true, weakVendorEnforcement: true, }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowGeo: true, - allowID: true, + allowBid: true, + passGeo: true, + passID: true, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", bidder: openrtb_ext.BidderRubicon, consent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", - allowGeo: false, - allowID: true, + allowBid: true, + passGeo: false, + passID: true, }, { // This requires publisher restrictions on any claimed purposes, 2-10. Vendor must declare all claimed purposes @@ -410,20 +414,22 @@ func TestAllowPersonalInfoTCF2(t *testing.T) { description: "OpenX vendor test, Specific purposes/LIs claimed, no geo claimed, Publisher restrictions apply", bidder: openrtb_ext.BidderOpenx, consent: "CPAavcCPAavcCAGABCFRBKCsAP_AAH_AAAqIHFNf_X_fb3_j-_59_9t0eY1f9_7_v-0zjgeds-8Nyd_X_L8X5mM7vB36pq4KuR4Eu3LBAQdlHOHcTUmw6IkVqTPsbk2Mr7NKJ7PEinMbe2dYGH9_n9XT_ZKY79_____7__-_____7_f__-__3_vp9V---wOJAIMBAUAgAEMAAQIFCIQAAQhiQAAAABBCIBQJIAEqgAWVwEdoIEACAxAQgQAgBBQgwCAAQAAJKAgBACwQCAAiAQAAgAEAIAAEIAILACQEAAAEAJCAAiACECAgiAAg5DAgIgCCAFABAAAuJDACAMooASBAPGQGAAKAAqACGAEwALgAjgBlgDUAHZAPsA_ACMAFLAK2AbwBMQCbAFogLYAYEAw8BkQDOQGeAM-EQHwAVABWAC4AIYAZAAywBqADZAHYAPwAgABGAClgFPANYAdUA-QCGwEOgIvASIAmwBOwCkQFyAMCAYSAw8Bk4DOQGfCQAYADgBzgN_CQTgAEAALgAoACoAGQAOAAeABAACIAFQAMIAaABqADyAIYAigBMgCqAKwAWAAuABvADmAHoAQ0AiACJgEsAS4AmgBSgC3AGGAMgAZcA1ADVAGyAO8AewA-IB9gH6AQAAjABQQClgFPAL8AYoA1gBtADcAG8AOIAegA-QCGwEOgIqAReAkQBMQCZQE2AJ2AUOApEBYoC2AFyALvAYEAwYBhIDDQGHgMiAZIAycBlwDOQGfANIAadA1gDWQoAEAYQaBIACoAKwAXABDADIAGWANQAbIA7AB-AEAAIKARgApYBT4C0ALSAawA3gB1QD5AIbAQ6Ai8BIgCbAE7AKRAXIAwIBhIDDwGMAMnAZyAzwBnwcAEAA4Bv4qA2ABQAFQAQwAmABcAEcAMsAagA7AB-AEYAKXAWgBaQDeAJBATEAmwBTYC2AFyAMCAYeAyIBnIDPAGfANyHQWQAFwAUABUADIAHAAQAAiABdADAAMYAaABqADwAH0AQwBFACZAFUAVgAsABcADEAGYAN4AcwA9ACGAERAJYAmABNACjAFKALEAW4AwwBkADKAGiANQAbIA3wB3gD2gH2AfoBGACVAFBAKeAWKAtAC0gFzALyAX4AxQBuADiQHTAdQA9ACGwEOgIiAReAkEBIgCbAE7AKHAU0AqwBYsC2ALZAXAAuQBdoC7wGEgMNAYeAxIBjADHgGSAMnAZUAywBlwDOQGfANEgaQBpIDSwGnANYAbGPABAIqAb-QgZgALAAoABkAEQALgAYgBDACYAFUALgAYgAzABvAD0AI4AWIAygBqADfAHfAPsA_ACMAFBAKGAU-AtAC0gF-AMUAdQA9ACQQEiAJsAU0AsUBaMC2ALaAXAAuQBdoDDwGJAMiAZOAzkBngDPgGiANJAaWA4AlAyAAQAAsACgAGQAOAAigBgAGIAPAAiABMACqAFwAMQAZgA2gCGgEQARIAowBSgC3AGEAMoAaoA2QB3gD8AIwAU-AtAC0gGKANwAcQA6gCHQEXgJEATYAsUBbAC7QGHgMiAZOAywBnIDPAGfANIAawA4AmACARUA38pBBAAXABQAFQAMgAcABAACKAGAAYwA0ADUAHkAQwBFACYAFIAKoAWAAuABiADMAHMAQwAiABRgClAFiALcAZQA0QBqgDZAHfAPsA_ACMAFBAKGAVsAuYBeQDaAG4APQAh0BF4CRAE2AJ2AUOApoBWwCxQFsALgAXIAu0BhoDDwGMAMiAZIAycBlwDOQGeAM-gaQBpMDWANZAbGVABAA-Ab-A.YAAAAAAAAAAA", - allowGeo: false, - allowID: true, + allowBid: true, + passGeo: false, + passID: true, }, } for _, td := range testDefs { - allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowGeo failure on %s", td.description) + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } -func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { +func TestAllowActivitiesWhitelistTCF2(t *testing.T) { vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, @@ -441,13 +447,13 @@ func TestAllowPersonalInfoWhitelistTCF2(t *testing.T) { } // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}} - allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed") - assert.EqualValuesf(t, true, allowGeo, "AllowGeo failure") - assert.EqualValuesf(t, true, allowID, "AllowID failure") + _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed") + assert.EqualValuesf(t, true, passGeo, "PassGeo failure") + assert.EqualValuesf(t, true, passID, "PassID failure") } -func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { +func TestAllowActivitiesTCF2PubRestrict(t *testing.T) { vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) perms := permissionsImpl{ cfg: tcf2Config, @@ -471,135 +477,30 @@ func TestAllowPersonalInfoTCF2PubRestrict(t *testing.T) { description: "Appnexus vendor test, insufficient purposes claimed", bidder: openrtb_ext.BidderAppnexus, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowGeo: false, - allowID: false, + passGeo: false, + passID: false, }, { description: "Pubmatic vendor test, flex purposes claimed", bidder: openrtb_ext.BidderPubmatic, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowGeo: false, - allowID: false, + passGeo: false, + passID: false, }, { description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", bidder: openrtb_ext.BidderRubicon, consent: "COwAdDhOwAdDhN4ABAENAPCgAAQAAv___wAAAFP_AAp_4AI6ACACAA", - allowGeo: false, - allowID: true, + passGeo: false, + passID: true, }, } for _, td := range testDefs { - allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) - } -} - -func TestAllowPersonalInfoTCF2PurposeOneTrue(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 10, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } - perms.cfg.TCF2.PurposeOneTreatment.Enabled = true - perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = true - - // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set - testDefs := []tcf2TestDef{ - { - description: "Appnexus vendor test, insufficient purposes claimed", - bidder: openrtb_ext.BidderAppnexus, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: false, - allowID: false, - }, - { - description: "Pubmatic vendor test, flex purposes claimed", - bidder: openrtb_ext.BidderPubmatic, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: true, - allowID: true, - }, - { - description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", - bidder: openrtb_ext.BidderRubicon, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: false, - allowID: true, - }, - } - - for _, td := range testDefs { - allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) - } -} - -func TestAllowPersonalInfoTCF2PurposeOneFalse(t *testing.T) { - vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) - perms := permissionsImpl{ - cfg: tcf2Config, - vendorIDs: map[openrtb_ext.BidderName]uint16{ - openrtb_ext.BidderAppnexus: 2, - openrtb_ext.BidderPubmatic: 10, - openrtb_ext.BidderRubicon: 8, - }, - fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ - tcf1SpecVersion: nil, - tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ - 34: parseVendorListDataV2(t, vendorListData), - }), - }, - } - perms.cfg.TCF2.PurposeOneTreatment.Enabled = true - perms.cfg.TCF2.PurposeOneTreatment.AccessAllowed = false - - // COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA Purpose one flag set - // Purpose one treatment will fail PI, but allow passing the IDs. - testDefs := []tcf2TestDef{ - { - description: "Appnexus vendor test, insufficient purposes claimed", - bidder: openrtb_ext.BidderAppnexus, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: false, - allowID: false, - }, - { - description: "Pubmatic vendor test, flex purposes claimed", - bidder: openrtb_ext.BidderPubmatic, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: true, - allowID: true, - }, - { - description: "Rubicon vendor test, Specific purposes/LIs claimed, no geo claimed", - bidder: openrtb_ext.BidderRubicon, - consent: "COzqiL3OzqiL3NIAAAENAiCMAP_AAH_AAIAAAQEX2S5MAICL7JcmAAA", - allowGeo: false, - allowID: true, - }, - } - - for _, td := range testDefs { - allowGeo, allowID, err := perms.PersonalInfoAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) - assert.NoErrorf(t, err, "Error processing PersonalInfoAllowed for %s", td.description) - assert.EqualValuesf(t, td.allowGeo, allowGeo, "AllowGeo failure on %s", td.description) - assert.EqualValuesf(t, td.allowID, allowID, "AllowID failure on %s", td.description) + _, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) } } @@ -811,3 +712,78 @@ func TestNormalizeGDPR(t *testing.T) { assert.Equal(t, tt.wantSignal, normalizedSignal, tt.description) } } + +func TestAllowActivitiesTCF2BidRequests(t *testing.T) { + purpose2AndVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAADAQAAAAAA" + purpose2ConsentWithoutVendorConsent := "CPF_61ePF_61eFxAAAENAiCAAEAAAAAAAAAAABIAAAAA" + + testDefs := []struct { + description string + purpose2Enabled bool + bidder openrtb_ext.BidderName + consent string + allowBid bool + passGeo bool + passID bool + weakVendorEnforcement bool + }{ + { + description: "Bid blocked - p2 enabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: false, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 disabled, user consents to p2 but not vendor, vendor consents to p2", + purpose2Enabled: false, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2ConsentWithoutVendorConsent, + allowBid: true, + passGeo: false, + passID: false, + }, + { + description: "Bid allowed - p2 enabled, user consents to p2 and vendor, vendor consents to p2", + purpose2Enabled: true, + bidder: openrtb_ext.BidderPubmatic, + consent: purpose2AndVendorConsent, + allowBid: true, + passGeo: false, + passID: true, + }, + } + + for _, td := range testDefs { + vendorListData := tcf2MarshalVendorList(buildTCF2VendorList34()) + perms := permissionsImpl{ + cfg: config.GDPR{ + HostVendorID: 2, + TCF2: config.TCF2{ + Enabled: true, + Purpose1: config.PurposeDetail{Enabled: true}, + Purpose2: config.PurposeDetail{Enabled: td.purpose2Enabled}, + Purpose7: config.PurposeDetail{Enabled: true}, + SpecialPurpose1: config.PurposeDetail{Enabled: true}, + }, + }, + vendorIDs: map[openrtb_ext.BidderName]uint16{ + openrtb_ext.BidderPubmatic: 6, + }, + fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){ + tcf1SpecVersion: nil, + tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{ + 34: parseVendorListDataV2(t, vendorListData), + }), + }, + } + + allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement) + assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description) + assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description) + assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description) + assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description) + } +} diff --git a/metrics/config/metrics_test.go b/metrics/config/metrics_test.go index 5b70b53bb1a..7f9643330e3 100644 --- a/metrics/config/metrics_test.go +++ b/metrics/config/metrics_test.go @@ -118,6 +118,8 @@ func TestMultiMetricsEngine(t *testing.T) { metricsEngine.RecordStoredImpCacheResult(metrics.CacheHit, 5) metricsEngine.RecordAccountCacheResult(metrics.CacheHit, 6) + metricsEngine.RecordAdapterGDPRRequestBlocked(openrtb_ext.BidderAppnexus) + metricsEngine.RecordRequestQueueTime(false, metrics.ReqTypeVideo, time.Duration(1)) //Make the metrics engine, instantiated here with goEngine, fill its RequestStatuses[RequestType][metrics.RequestStatusXX] with the new boolean values added to metrics.Labels @@ -161,6 +163,8 @@ func TestMultiMetricsEngine(t *testing.T) { VerifyMetrics(t, "StoredReqCache.Hit", goEngine.StoredReqCacheMeter[metrics.CacheHit].Count(), 4) VerifyMetrics(t, "StoredImpCache.Hit", goEngine.StoredImpCacheMeter[metrics.CacheHit].Count(), 5) VerifyMetrics(t, "AccountCache.Hit", goEngine.AccountCacheMeter[metrics.CacheHit].Count(), 6) + + VerifyMetrics(t, "AdapterMetrics.AppNexus.GDPRRequestBlocked", goEngine.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), 1) } func VerifyMetrics(t *testing.T, name string, actual int64, expected int64) { diff --git a/metrics/go_metrics_test.go b/metrics/go_metrics_test.go index 7ebe2f3c2fe..7a636752747 100644 --- a/metrics/go_metrics_test.go +++ b/metrics/go_metrics_test.go @@ -592,6 +592,45 @@ func TestRecordRequestPrivacy(t *testing.T) { assert.Equal(t, m.PrivacyTCFRequestVersion[TCFVersionV2].Count(), int64(1), "TCF V2") } +func TestRecordAdapterGDPRRequestBlocked(t *testing.T) { + var fakeBidder openrtb_ext.BidderName = "fooAdvertising" + + tests := []struct { + description string + metricsDisabled bool + adapterName openrtb_ext.BidderName + expectedCount int64 + }{ + { + description: "", + metricsDisabled: false, + adapterName: openrtb_ext.BidderAppnexus, + expectedCount: 1, + }, + { + description: "", + metricsDisabled: false, + adapterName: fakeBidder, + expectedCount: 0, + }, + { + description: "", + metricsDisabled: true, + adapterName: openrtb_ext.BidderAppnexus, + expectedCount: 0, + }, + } + + for _, tt := range tests { + registry := metrics.NewRegistry() + m := NewMetrics(registry, []openrtb_ext.BidderName{openrtb_ext.BidderAppnexus}, config.DisabledMetrics{AdapterGDPRRequestBlocked: tt.metricsDisabled}) + + m.RecordAdapterGDPRRequestBlocked(tt.adapterName) + + assert.Equal(t, tt.expectedCount, m.AdapterMetrics[openrtb_ext.BidderAppnexus].GDPRRequestBlocked.Count(), tt.description) + } +} + func ensureContainsBidTypeMetrics(t *testing.T, registry metrics.Registry, prefix string, mdm map[openrtb_ext.BidType]*MarkupDeliveryMetrics) { ensureContains(t, registry, prefix+".banner.adm_bids_received", mdm[openrtb_ext.BidTypeBanner].AdmMeter) ensureContains(t, registry, prefix+".banner.nurl_bids_received", mdm[openrtb_ext.BidTypeBanner].NurlMeter) diff --git a/metrics/prometheus/preload.go b/metrics/prometheus/preload.go index f4dfe43469d..621ad808619 100644 --- a/metrics/prometheus/preload.go +++ b/metrics/prometheus/preload.go @@ -178,6 +178,12 @@ func preloadLabelValues(m *Metrics) { sourceLabel: sourceValues, versionLabel: tcfVersionsAsString(), }) + + if !m.metricsDisabled.AdapterGDPRRequestBlocked { + preloadLabelValuesForCounter(m.adapterGDPRBlockedRequests, map[string][]string{ + adapterLabel: adapterValues, + }) + } } func preloadLabelValuesForCounter(counter *prometheus.CounterVec, labelsWithValues map[string][]string) {