Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GDPR: Don't Call Bidder If It Lacks Purpose 2 Legal Basis #1851

Merged
merged 6 commits into from
May 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,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 {
Expand Down Expand Up @@ -718,6 +721,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", "")
Expand Down
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down
9 changes: 5 additions & 4 deletions endpoints/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions endpoints/cookie_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions endpoints/setuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,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)

Expand Down
2 changes: 1 addition & 1 deletion exchange/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1770,7 +1770,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,
Expand Down
26 changes: 20 additions & 6 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down Expand Up @@ -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())

Expand All @@ -133,17 +138,26 @@ 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
} else {
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
Expand Down
143 changes: 129 additions & 14 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
Loading