diff --git a/config/bidderinfo.go b/config/bidderinfo.go index d6d0ba79ba0..409393c2b0c 100644 --- a/config/bidderinfo.go +++ b/config/bidderinfo.go @@ -124,6 +124,14 @@ type Syncer struct { // SupportCORS identifies if CORS is supported for the user syncing endpoints. SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` + + // SkipWhen allows bidders to specify when they don't want to sync + SkipWhen *SkipWhen `yaml:"skipwhen" mapstructure:"skipwhen"` +} + +type SkipWhen struct { + GDPR bool `yaml:"gdpr" mapstructure:"gdpr"` + GPPSID []string `yaml:"gpp_sid" mapstructure:"gpp_sid"` } // SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index f2ffb2fb4d2..6d69aa8241e 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -61,7 +61,7 @@ func NewCookieSyncEndpoint( } return &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder, bidderHashSet), + chooser: usersync.NewChooser(syncersByBidder, bidderHashSet, config.BidderInfos), config: config, privacyConfig: usersyncPrivacyConfig{ gdprConfig: config.GDPR, @@ -180,8 +180,10 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma ccpaParsedPolicy: ccpaParsedPolicy, activityControl: activityControl, activityRequest: privacy.NewRequestFromPolicies(privacyPolicies), + gdprSignal: gdprSignal, }, SyncTypeFilter: syncTypeFilter, + GPPSID: request.GPPSID, } return rx, privacyMacros, nil } @@ -554,6 +556,7 @@ type usersyncPrivacy struct { ccpaParsedPolicy ccpa.ParsedPolicy activityControl privacy.ActivityControl activityRequest privacy.ActivityRequest + gdprSignal gdpr.Signal } func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { @@ -577,3 +580,7 @@ func (p usersyncPrivacy) ActivityAllowsUserSync(bidder string) bool { privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidder}, p.activityRequest) } + +func (p usersyncPrivacy) GDPRInScope() bool { + return p.gdprSignal == gdpr.SignalYes +} diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 182a6666436..050e137ffed 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -45,16 +45,18 @@ func TestNewCookieSyncEndpoint(t *testing.T) { analytics = MockAnalyticsRunner{} fetcher = FakeAccountsFetcher{} bidders = map[string]openrtb_ext.BidderName{"bidderA": openrtb_ext.BidderName("bidderA"), "bidderB": openrtb_ext.BidderName("bidderB")} + bidderInfo = map[string]config.BidderInfo{"bidderA": {}, "bidderB": {}} biddersKnown = map[string]struct{}{"bidderA": {}, "bidderB": {}} ) endpoint := NewCookieSyncEndpoint( syncersByBidder, &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, gdprPermsBuilder, tcf2ConfigBuilder, @@ -66,12 +68,13 @@ func TestNewCookieSyncEndpoint(t *testing.T) { result := endpoint.(*cookieSyncEndpoint) expected := &cookieSyncEndpoint{ - chooser: usersync.NewChooser(syncersByBidder, biddersKnown), + chooser: usersync.NewChooser(syncersByBidder, biddersKnown, bidderInfo), config: &config.Configuration{ - UserSync: configUserSync, - HostCookie: configHostCookie, - GDPR: configGDPR, - CCPA: config.CCPA{Enforce: configCCPAEnforce}, + UserSync: configUserSync, + HostCookie: configHostCookie, + GDPR: configGDPR, + CCPA: config.CCPA{Enforce: configCCPAEnforce}, + BidderInfos: bidderInfo, }, privacyConfig: usersyncPrivacyConfig{ gdprConfig: configGDPR, @@ -571,11 +574,13 @@ func TestCookieSyncParseRequest(t *testing.T) { gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, activityRequest: privacy.NewRequestFromPolicies(privacy.Policies{GPPSID: []int8{2}}), + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), Redirect: usersync.NewSpecificBidderFilter([]string{"b"}, usersync.BidderFilterModeExclude), }, + GPPSID: "2", }, }, { @@ -611,6 +616,7 @@ func TestCookieSyncParseRequest(t *testing.T) { gdprPermissions: &fakePermissions{}, ccpaParsedPolicy: expectedCCPAParsedPolicy, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -628,6 +634,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -655,6 +662,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -682,6 +690,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -709,6 +718,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -736,6 +746,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -763,6 +774,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -790,6 +802,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -807,6 +820,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -826,6 +840,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -866,6 +881,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -892,6 +908,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -937,6 +954,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -969,6 +987,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -1001,6 +1020,7 @@ func TestCookieSyncParseRequest(t *testing.T) { Privacy: usersyncPrivacy{ gdprPermissions: &fakePermissions{}, activityRequest: emptyActivityPoliciesRequest, + gdprSignal: 0, }, SyncTypeFilter: usersync.SyncTypeFilter{ IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), @@ -1974,6 +1994,39 @@ func TestCookieSyncActivityControlIntegration(t *testing.T) { } } +func TestUsersyncPrivacyGDPRInScope(t *testing.T) { + testCases := []struct { + description string + givenGdprSignal gdpr.Signal + expected bool + }{ + { + description: "GDPR Signal Yes", + givenGdprSignal: gdpr.SignalYes, + expected: true, + }, + { + description: "GDPR Signal No", + givenGdprSignal: gdpr.SignalNo, + expected: false, + }, + { + description: "GDPR Signal Ambigious", + givenGdprSignal: gdpr.SignalAmbiguous, + expected: false, + }, + } + + for _, test := range testCases { + privacy := usersyncPrivacy{ + gdprSignal: test.givenGdprSignal, + } + + result := privacy.GDPRInScope() + assert.Equal(t, test.expected, result, test.description) + } +} + func TestCombineErrors(t *testing.T) { testCases := []struct { description string diff --git a/usersync/chooser.go b/usersync/chooser.go index 0615be56c3d..68acf195ffe 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -3,6 +3,7 @@ package usersync import ( "strings" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/openrtb_ext" ) @@ -14,7 +15,7 @@ type Chooser interface { } // NewChooser returns a new instance of the standard chooser implementation. -func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}) Chooser { +func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}, bidderInfo map[string]config.BidderInfo) Chooser { bidders := make([]string, 0, len(bidderSyncerLookup)) for k := range bidderSyncerLookup { @@ -27,6 +28,7 @@ func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]st bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, normalizeValidBidderName: openrtb_ext.NormalizeBidderName, biddersKnown: biddersKnown, + bidderInfo: bidderInfo, } } @@ -37,6 +39,7 @@ type Request struct { Limit int Privacy Privacy SyncTypeFilter SyncTypeFilter + GPPSID string Debug bool } @@ -92,6 +95,9 @@ const ( // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities StatusBlockedByPrivacy + // StatusBlockedByRegulationScope specifies the bidder chose to not sync given GDPR being in scope or because of a GPPSID + StatusBlockedByRegulationScope + // StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server StatusUnconfiguredBidder ) @@ -99,6 +105,7 @@ const ( // Privacy determines which privacy policies will be enforced for a user sync request. type Privacy interface { GDPRAllowsHostCookie() bool + GDPRInScope() bool GDPRAllowsBidderSync(bidder string) bool CCPAAllowsBidderSync(bidder string) bool ActivityAllowsUserSync(bidder string) bool @@ -111,6 +118,7 @@ type standardChooser struct { bidderChooser bidderChooser normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) biddersKnown map[string]struct{} + bidderInfo map[string]config.BidderInfo } // Choose randomly selects user syncers which are permitted by the user's privacy settings and @@ -136,7 +144,7 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { if _, ok := biddersSeen[bidders[i]]; ok { continue } - syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) + syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie, request.GPPSID) biddersEvaluated = append(biddersEvaluated, evaluation) if evaluation.Status == StatusOK { @@ -148,7 +156,7 @@ func (c standardChooser) Choose(request Request, cookie *Cookie) Result { return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} } -func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { +func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie, GPPSID string) (Syncer, BidderEvaluation) { bidderNormalized, exists := c.normalizeValidBidderName(bidder) if !exists { return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} @@ -186,5 +194,17 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} } + if privacy.GDPRInScope() && c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil && c.bidderInfo[bidder].Syncer.SkipWhen.GDPR { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} + } + + if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil { + for _, gppSID := range c.bidderInfo[bidder].Syncer.SkipWhen.GPPSID { + if gppSID == GPPSID { + return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} + } + } + } + return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} } diff --git a/usersync/chooser_test.go b/usersync/chooser_test.go index 4e5d4acdc9c..5b67361d341 100644 --- a/usersync/chooser_test.go +++ b/usersync/chooser_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/prebid/prebid-server/v2/config" "github.com/prebid/prebid-server/v2/openrtb_ext" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -15,6 +16,7 @@ func TestNewChooser(t *testing.T) { testCases := []struct { description string bidderSyncerLookup map[string]Syncer + bidderInfo map[string]config.BidderInfo expectedBiddersAvailable []string }{ { @@ -40,7 +42,7 @@ func TestNewChooser(t *testing.T) { } for _, test := range testCases { - chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{})).(standardChooser) + chooser, _ := NewChooser(test.bidderSyncerLookup, make(map[string]struct{}), test.bidderInfo).(standardChooser) assert.ElementsMatch(t, test.expectedBiddersAvailable, chooser.biddersAvailable, test.description) } } @@ -72,6 +74,7 @@ func TestChooserChoose(t *testing.T) { givenRequest Request givenChosenBidders []string givenCookie Cookie + givenBidderInfo map[string]config.BidderInfo bidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expected Result }{ @@ -340,6 +343,55 @@ func TestChooserChoose(t *testing.T) { SyncersChosen: []SyncerChoice{{Bidder: "AppNexus", Syncer: fakeSyncerA}}, }, }, + { + description: "Regulation Scope GDPR", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + Limit: 0, + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, + { + description: "Regulation Scope GPP", + givenRequest: Request{ + Privacy: &fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + Limit: 0, + GPPSID: "2", + }, + givenChosenBidders: []string{"a"}, + givenCookie: Cookie{}, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + bidderNamesLookup: normalizedBidderNamesLookup, + expected: Result{ + Status: StatusOK, + BiddersEvaluated: []BidderEvaluation{{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}}, + SyncersChosen: []SyncerChoice{}, + }, + }, } bidders := []string{"anyRequested"} @@ -355,12 +407,17 @@ func TestChooserChoose(t *testing.T) { On("choose", test.givenRequest.Bidders, biddersAvailable, cooperativeConfig). Return(test.givenChosenBidders) + if test.givenBidderInfo == nil { + test.givenBidderInfo = map[string]config.BidderInfo{} + } + chooser := standardChooser{ bidderSyncerLookup: bidderSyncerLookup, biddersAvailable: biddersAvailable, bidderChooser: mockBidderChooser, normalizeValidBidderName: test.bidderNamesLookup, biddersKnown: biddersKnown, + bidderInfo: test.givenBidderInfo, } result := chooser.Choose(test.givenRequest, &test.givenCookie) @@ -392,6 +449,8 @@ func TestChooserEvaluate(t *testing.T) { givenSyncersSeen map[string]struct{} givenPrivacy fakePrivacy givenCookie Cookie + givenGPPSID string + givenBidderInfo map[string]config.BidderInfo givenSyncTypeFilter SyncTypeFilter normalizedBidderNamesLookup func(name string) (openrtb_ext.BidderName, bool) expectedSyncer Syncer @@ -541,13 +600,56 @@ func TestChooserEvaluate(t *testing.T) { expectedSyncer: nil, expectedEvaluation: BidderEvaluation{Bidder: "unconfigured", Status: StatusUnconfiguredBidder}, }, + { + description: "Blocked By Regulation Scope - GDPR", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true, gdprInScope: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GDPR: true, + }, + }, + }, + }, + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, + }, + { + description: "Blocked By Regulation Scope - GPP", + givenBidder: "a", + givenSyncersSeen: map[string]struct{}{}, + givenPrivacy: fakePrivacy{gdprAllowsHostCookie: true, gdprAllowsBidderSync: true, ccpaAllowsBidderSync: true, activityAllowUserSync: true}, + givenCookie: cookieNeedsSync, + givenBidderInfo: map[string]config.BidderInfo{ + "a": { + Syncer: &config.Syncer{ + SkipWhen: &config.SkipWhen{ + GPPSID: []string{"2", "3"}, + }, + }, + }, + }, + givenGPPSID: "2", + givenSyncTypeFilter: syncTypeFilter, + normalizedBidderNamesLookup: normalizedBidderNamesLookup, + normalisedBidderName: "a", + expectedSyncer: nil, + expectedEvaluation: BidderEvaluation{Bidder: "a", SyncerKey: "keyA", Status: StatusBlockedByRegulationScope}, + }, } for _, test := range testCases { t.Run(test.description, func(t *testing.T) { - chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown).(standardChooser) + chooser, _ := NewChooser(bidderSyncerLookup, biddersKnown, test.givenBidderInfo).(standardChooser) chooser.normalizeValidBidderName = test.normalizedBidderNamesLookup - sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie) + sync, evaluation := chooser.evaluate(test.givenBidder, test.givenSyncersSeen, test.givenSyncTypeFilter, &test.givenPrivacy, &test.givenCookie, test.givenGPPSID) assert.Equal(t, test.normalisedBidderName, test.givenPrivacy.inputBidderName) assert.Equal(t, test.expectedSyncer, sync, test.description+":syncer") @@ -600,6 +702,7 @@ type fakePrivacy struct { gdprAllowsBidderSync bool ccpaAllowsBidderSync bool activityAllowUserSync bool + gdprInScope bool inputBidderName string } @@ -620,3 +723,7 @@ func (p *fakePrivacy) CCPAAllowsBidderSync(bidder string) bool { func (p *fakePrivacy) ActivityAllowsUserSync(bidder string) bool { return p.activityAllowUserSync } + +func (p *fakePrivacy) GDPRInScope() bool { + return p.gdprInScope +}