diff --git a/config/account.go b/config/account.go index 2d2bfb5c..0e529869 100644 --- a/config/account.go +++ b/config/account.go @@ -41,7 +41,7 @@ type Account struct { GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"` DebugAllow bool `mapstructure:"debug_allow" json:"debug_allow"` DefaultIntegration string `mapstructure:"default_integration" json:"default_integration"` - CookieSync CookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` + CookieSync AccountCookieSync `mapstructure:"cookie_sync" json:"cookie_sync"` Events Events `mapstructure:"events" json:"events"` // Don't enable this feature. It is still under developmment - https://github.com/prebid/prebid-server/issues/1725 TruncateTargetAttribute *int `mapstructure:"truncate_target_attr" json:"truncate_target_attr"` AlternateBidderCodes *openrtb_ext.ExtAlternateBidderCodes `mapstructure:"alternatebiddercodes" json:"alternatebiddercodes"` @@ -56,12 +56,14 @@ type Account struct { TargetingPrefix string `mapstructure:"targeting_prefix" json:"targeting_prefix"` } -// CookieSync represents the account-level defaults for the cookie sync endpoint. -type CookieSync struct { - DefaultLimit *int `mapstructure:"default_limit" json:"default_limit"` - MaxLimit *int `mapstructure:"max_limit" json:"max_limit"` - DefaultCoopSync *bool `mapstructure:"default_coop_sync" json:"default_coop_sync"` - PriorityGroups [][]string `mapstructure:"priority_groups" json:"priority_groups"` +// AccountCookieSync represents the account-level defaults for the cookie sync endpoint. +type AccountCookieSync struct { + DefaultLimit *int `mapstructure:"default_limit" json:"default_limit"` + MaxLimit *int `mapstructure:"max_limit" json:"max_limit"` + DefaultCoopSync *bool `mapstructure:"default_coop_sync" json:"default_coop_sync"` + PriorityGroups [][]string `mapstructure:"priority_groups" json:"priority_groups"` + PriorityGroupsOnly *bool `mapstructure:"priority_groups_only" json:"priority_groups_only"` + DisabledIFrameBidders []string `mapstructure:"disabled_iframe_bidders" json:"disabled_iframe_bidders"` } // AccountCCPA represents account-specific CCPA configuration diff --git a/config/config.go b/config/config.go index 321201be..de862992 100644 --- a/config/config.go +++ b/config/config.go @@ -52,6 +52,9 @@ type Configuration struct { Event Event `mapstructure:"event"` Accounts StoredRequests `mapstructure:"accounts"` UserSync UserSync `mapstructure:"user_sync"` + // CookieSync holds host-level cookie sync settings that are always enforced and + // cannot be overridden by account configuration. + CookieSync CookieSync `mapstructure:"cookie_sync"` // Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives. StoredVideo StoredRequests `mapstructure:"stored_video_req"` StoredResponses StoredRequests `mapstructure:"stored_responses"` diff --git a/config/config_test.go b/config/config_test.go index d4e98a18..cec717d3 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -426,6 +426,8 @@ ccpa: enforce: true lmt: enforce: true +cookie_sync: + disabled_iframe_bidders: ["hostBidder1", "hostBidder2"] host_cookie: cookie_name: userid family: prebid @@ -650,6 +652,7 @@ func TestFullConfig(t *testing.T) { cmpStrings(t, "cookie family", "prebid", cfg.HostCookie.Family) cmpStrings(t, "opt out", "http://prebid.org/optout", cfg.HostCookie.OptOutURL) cmpStrings(t, "opt in", "http://prebid.org/optin", cfg.HostCookie.OptInURL) + assert.Equal(t, []string{"hostBidder1", "hostBidder2"}, cfg.CookieSync.DisabledIFrameBidders, "cookie_sync.disabled_iframe_bidders") cmpStrings(t, "external url", "http://prebid-server.prebid.org/", cfg.ExternalURL) cmpStrings(t, "host", "prebid-server.prebid.org", cfg.Host) cmpInts(t, "port", 1234, cfg.Port) diff --git a/config/usersync.go b/config/usersync.go index d490853d..7fed692c 100644 --- a/config/usersync.go +++ b/config/usersync.go @@ -12,3 +12,12 @@ type UserSync struct { type UserSyncCooperative struct { EnabledByDefault bool `mapstructure:"default"` } + +// CookieSync specifies host-level cookie sync settings that are always enforced by the +// host operator. These settings cannot be overridden by account configuration and are +// unioned with any account-level restrictions. +type CookieSync struct { + // DisabledIFrameBidders lists bidders for which iframe cookie syncs are disabled for + // every account. Use "*" to disable iframe syncs for all bidders. + DisabledIFrameBidders []string `mapstructure:"disabled_iframe_bidders" json:"disabled_iframe_bidders"` +} diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index a90bc611..a8705d3a 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -8,6 +8,7 @@ import ( "io" "math" "net/http" + "slices" "strconv" "strings" "time" @@ -167,6 +168,9 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma return usersync.Request{}, macros.UserSyncPrivacy{}, account, err } + disabledIFrameBidders := mergeDisabledIFrameBidders(c.config.CookieSync.DisabledIFrameBidders, account.CookieSync.DisabledIFrameBidders) + syncTypeFilter = applyDisabledIFrameBidders(syncTypeFilter, disabledIFrameBidders) + gdprRequestInfo := gdpr.RequestInfo{ Consent: privacyMacros.GDPRConsent, GDPRSignal: gdprSignal, @@ -290,7 +294,7 @@ func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) { } } -func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { +func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.AccountCookieSync) cookieSyncRequest { limit := getEffectiveLimit(request.Limit, cookieSyncConfig.DefaultLimit) maxLimit := getEffectiveMaxLimit(cookieSyncConfig.MaxLimit) if maxLimit < limit { @@ -325,7 +329,7 @@ func getEffectiveMaxLimit(maxLimit *int) int { return math.MaxInt } -func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { +func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.AccountCookieSync) cookieSyncRequest { if request.CooperativeSync == nil && cookieSyncConfig.DefaultCoopSync != nil { request.CooperativeSync = cookieSyncConfig.DefaultCoopSync } @@ -333,7 +337,7 @@ func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cooki return request } -func (c *cookieSyncEndpoint) findPriorityGroups(accountCookieSyncConfig config.CookieSync) [][]string { +func (c *cookieSyncEndpoint) findPriorityGroups(accountCookieSyncConfig config.AccountCookieSync) [][]string { // Account-level config takes precedence over global config, which will be deprecated in the future if accountCookieSyncConfig.DefaultCoopSync != nil { return accountCookieSyncConfig.PriorityGroups @@ -341,6 +345,60 @@ func (c *cookieSyncEndpoint) findPriorityGroups(accountCookieSyncConfig config.C return c.config.UserSync.PriorityGroups } +// mergeDisabledIFrameBidders returns the union of the host-level and account-level +// disabled iframe bidder lists. Host-level entries are always enforced and cannot be +// overridden by account configuration. Duplicates are removed while preserving order, +// with host entries taking precedence. +func mergeDisabledIFrameBidders(host, account []string) []string { + if len(host) == 0 { + return account + } + if len(account) == 0 { + return host + } + + merged := make([]string, 0, len(host)+len(account)) + seen := make(map[string]struct{}, len(host)+len(account)) + for _, bidder := range host { + if _, ok := seen[bidder]; !ok { + seen[bidder] = struct{}{} + merged = append(merged, bidder) + } + } + for _, bidder := range account { + if _, ok := seen[bidder]; !ok { + seen[bidder] = struct{}{} + merged = append(merged, bidder) + } + } + return merged +} + +// applyDisabledIFrameBidders enforces iframe cookie sync restrictions from the union of +// host-level and account-level configuration. +// The disabledIFrameBidders slice supports two formats, matching the filterSettings convention: +// - "*" (string): disables iframe syncs for all bidders +// - ["bidderA", "bidderB"] (array): disables iframe syncs for specific bidders only +// When specific bidders are disabled, a CompositeFilter ANDs the request-level filter with the +// configured filter, ensuring configuration can only further restrict — never broaden — +// what the request's filterSettings allows. Redirect syncs are never affected. +func applyDisabledIFrameBidders(syncTypeFilter usersync.SyncTypeFilter, disabledIFrameBidders []string) usersync.SyncTypeFilter { + if len(disabledIFrameBidders) == 0 { + return syncTypeFilter + } + + if slices.Contains(disabledIFrameBidders, "*") { + syncTypeFilter.IFrame = usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude) + } else { + syncTypeFilter.IFrame = usersync.CompositeFilter{ + RequestFilter: syncTypeFilter.IFrame, + AccountFilter: usersync.NewSpecificBidderFilter(disabledIFrameBidders, usersync.BidderFilterModeExclude), + } + } + + return syncTypeFilter +} + func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) { syncTypeFilter := usersync.SyncTypeFilter{ IFrame: cookieSyncBidderFilterAllowAll, diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 6fc56f79..a1f27416 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -588,6 +588,7 @@ func TestCookieSyncParseRequest(t *testing.T) { testCases := []struct { description string givenConfig config.UserSync + givenCookieSyncConfig config.CookieSync givenBody io.Reader givenGDPRConfig config.GDPR givenCCPAEnabled bool @@ -1108,6 +1109,78 @@ func TestCookieSyncParseRequest(t *testing.T) { expectedError: errCookieSyncAccountBlocked.Error(), givenAccountRequired: true, }, + { + description: "IFrame Disabled For All Bidders Via Account Config Wildcard", + givenBody: strings.NewReader(`{` + + `"bidders":["a", "b"],` + + `"account":"IFrameDisabledAllAccount"` + + `}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"a", "b", "c"}}, + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + }, + }, + expectedPrivacy: macros.UserSyncPrivacy{}, + expectedRequest: usersync.Request{ + Bidders: []string{"a", "b"}, + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: nil, + }, + Limit: 20, + Privacy: usersyncPrivacy{ + gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, + { + description: "IFrame Disabled For Specific Bidders", + givenBody: strings.NewReader(`{` + + `"bidders":["biddera", "bidderb"],` + + `"account":"IFrameDisabledAccount"` + + `}`), + givenGDPRConfig: config.GDPR{Enabled: true, DefaultValue: "0"}, + givenCCPAEnabled: true, + givenConfig: config.UserSync{ + PriorityGroups: [][]string{{"biddera", "bidderb", "bidderc"}}, + Cooperative: config.UserSyncCooperative{ + EnabledByDefault: false, + }, + }, + givenCookieSyncConfig: config.CookieSync{ + DisabledIFrameBidders: []string{"bidderb"}, + }, + expectedPrivacy: macros.UserSyncPrivacy{}, + expectedRequest: usersync.Request{ + Bidders: []string{"biddera", "bidderb"}, + Cooperative: usersync.Cooperative{ + Enabled: true, + PriorityGroups: nil, + }, + Limit: 20, + Privacy: usersyncPrivacy{ + gdprPermissions: &fakePermissions{}, + activityRequest: emptyActivityPoliciesRequest, + gdprSignal: -1, + }, + SyncTypeFilter: usersync.SyncTypeFilter{ + IFrame: usersync.CompositeFilter{ + RequestFilter: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + AccountFilter: usersync.NewSpecificBidderFilter([]string{"bidderb", "biddera"}, usersync.BidderFilterModeExclude), + }, + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + }, } for _, test := range testCases { @@ -1128,6 +1201,7 @@ func TestCookieSyncParseRequest(t *testing.T) { endpoint := cookieSyncEndpoint{ config: &config.Configuration{ UserSync: test.givenConfig, + CookieSync: test.givenCookieSyncConfig, AccountRequired: test.givenAccountRequired, }, privacyConfig: usersyncPrivacyConfig{ @@ -1139,6 +1213,8 @@ func TestCookieSyncParseRequest(t *testing.T) { accountsFetcher: FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ "TestAccount": testAccountData, "DisabledAccount": json.RawMessage(`{"disabled":true}`), + "IFrameDisabledAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true, "disabled_iframe_bidders": ["biddera"]}}`), + "IFrameDisabledAllAccount": json.RawMessage(`{"cookie_sync": {"default_limit": 20, "max_limit": 30, "default_coop_sync": true, "disabled_iframe_bidders": ["*"]}}`), "ValidAccountInvalidActivities": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`), }}, } @@ -1306,7 +1382,7 @@ func TestSetLimit(t *testing.T) { Limit: nil, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultLimit: nil, MaxLimit: nil, }, @@ -1321,7 +1397,7 @@ func TestSetLimit(t *testing.T) { Limit: intNegative, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultLimit: int20, }, }, @@ -1335,7 +1411,7 @@ func TestSetLimit(t *testing.T) { Limit: int0, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultLimit: int20, }, }, @@ -1349,7 +1425,7 @@ func TestSetLimit(t *testing.T) { Limit: int10, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultLimit: int20, MaxLimit: int30, }, @@ -1364,7 +1440,7 @@ func TestSetLimit(t *testing.T) { Limit: int30, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultLimit: int20, MaxLimit: int10, }, @@ -1379,7 +1455,7 @@ func TestSetLimit(t *testing.T) { Limit: intMax, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{}, + CookieSync: config.AccountCookieSync{}, }, expectedRequest: cookieSyncRequest{ Limit: intMax, @@ -1412,7 +1488,7 @@ func TestSetCooperativeSync(t *testing.T) { CooperativeSync: nil, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultCoopSync: nil, }, }, @@ -1426,7 +1502,7 @@ func TestSetCooperativeSync(t *testing.T) { CooperativeSync: nil, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultCoopSync: &coopSyncTrue, }, }, @@ -1440,7 +1516,7 @@ func TestSetCooperativeSync(t *testing.T) { CooperativeSync: &coopSyncTrue, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultCoopSync: nil, }, }, @@ -1454,7 +1530,7 @@ func TestSetCooperativeSync(t *testing.T) { CooperativeSync: &coopSyncFalse, }, givenAccount: &config.Account{ - CookieSync: config.CookieSync{ + CookieSync: config.AccountCookieSync{ DefaultCoopSync: &coopSyncTrue, }, }, @@ -1537,6 +1613,188 @@ func TestCookieSyncWriteParseRequestErrorMetrics(t *testing.T) { } } +func TestMergeDisabledIFrameBidders(t *testing.T) { + testCases := map[string]struct { + givenHost []string + givenAccount []string + expected []string + }{ + "both_nil": { + givenHost: nil, + givenAccount: nil, + expected: nil, + }, + "host_only": { + givenHost: []string{"bidderA", "bidderB"}, + givenAccount: nil, + expected: []string{"bidderA", "bidderB"}, + }, + "account_only": { + givenHost: nil, + givenAccount: []string{"bidderA", "bidderB"}, + expected: []string{"bidderA", "bidderB"}, + }, + "union": { + givenHost: []string{"bidderA"}, + givenAccount: []string{"bidderB"}, + expected: []string{"bidderA", "bidderB"}, + }, + "dedupe_host_precedence": { + givenHost: []string{"bidderA", "bidderB"}, + givenAccount: []string{"bidderB", "bidderC"}, + expected: []string{"bidderA", "bidderB", "bidderC"}, + }, + "wildcard_from_host": { + givenHost: []string{"*"}, + givenAccount: []string{"bidderA"}, + expected: []string{"*", "bidderA"}, + }, + "wildcard_from_account": { + givenHost: []string{"bidderA"}, + givenAccount: []string{"*"}, + expected: []string{"bidderA", "*"}, + }, + "wildcard_not_first_in_host": { + givenHost: []string{"bidderA", "*", "bidderB"}, + givenAccount: []string{"bidderC"}, + expected: []string{"bidderA", "*", "bidderB", "bidderC"}, + }, + "wildcard_not_first_in_account": { + givenHost: []string{"bidderA"}, + givenAccount: []string{"bidderB", "*"}, + expected: []string{"bidderA", "bidderB", "*"}, + }, + "wildcard_in_both_deduped": { + givenHost: []string{"bidderA", "*"}, + givenAccount: []string{"*", "bidderB"}, + expected: []string{"bidderA", "*", "bidderB"}, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + result := mergeDisabledIFrameBidders(test.givenHost, test.givenAccount) + assert.Equal(t, test.expected, result) + }) + } +} + +func TestApplyDisabledIFrameBidders(t *testing.T) { + allowAll := usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + } + + testCases := map[string]struct { + givenFilter usersync.SyncTypeFilter + givenBidders []string + expectedIFrame map[string]bool + expectedRedirect map[string]bool + expectedExactSync *usersync.SyncTypeFilter + }{ + "nil": { + givenFilter: allowAll, + givenBidders: nil, + expectedExactSync: &usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + "empty": { + givenFilter: allowAll, + givenBidders: []string{}, + expectedExactSync: &usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + "wildcard": { + givenFilter: allowAll, + givenBidders: []string{"*"}, + expectedExactSync: &usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + "specific_bidders": { + givenFilter: allowAll, + givenBidders: []string{"bidderA", "bidderB"}, + expectedIFrame: map[string]bool{ + "biddera": false, + "bidderb": false, + "bidderc": true, + }, + expectedRedirect: map[string]bool{ + "biddera": true, + }, + }, + // wildcard in givenBidders overrides the request-level redirect filter + "wildcard_overrides_request_filter": { + givenFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + Redirect: usersync.NewSpecificBidderFilter([]string{"biddera"}, usersync.BidderFilterModeExclude), + }, + givenBidders: []string{"*"}, + expectedExactSync: &usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewSpecificBidderFilter([]string{"biddera"}, usersync.BidderFilterModeExclude), + }, + }, + // request already excludes all iframe; account restriction still results in all blocked + "request_excludes_all": { + givenFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + givenBidders: []string{"bidderA"}, + expectedIFrame: map[string]bool{ + "biddera": false, + "bidderb": false, + }, + }, + // request includes specific bidders; account disables one of them + "request_includes_specific": { + givenFilter: usersync.SyncTypeFilter{ + IFrame: usersync.NewSpecificBidderFilter([]string{"bidderA", "bidderB"}, usersync.BidderFilterModeInclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + givenBidders: []string{"bidderA"}, + expectedIFrame: map[string]bool{ + "biddera": false, + "bidderb": true, + "bidderc": false, + }, + }, + // wildcard takes precedence when mixed with specific bidders + "wildcard_with_specific": { + givenFilter: allowAll, + givenBidders: []string{"*", "biddera"}, + expectedExactSync: &usersync.SyncTypeFilter{ + IFrame: usersync.NewUniformBidderFilter(usersync.BidderFilterModeExclude), + Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude), + }, + }, + } + + for name, test := range testCases { + t.Run(name, func(t *testing.T) { + result := applyDisabledIFrameBidders(test.givenFilter, test.givenBidders) + + if test.expectedExactSync != nil { + assert.Equal(t, *test.expectedExactSync, result) + } + + for bidder, expected := range test.expectedIFrame { + assert.Equal(t, expected, result.IFrame.Allowed(bidder), "IFrame.Allowed(%s)", bidder) + } + + for bidder, expected := range test.expectedRedirect { + assert.Equal(t, expected, result.Redirect.Allowed(bidder), "Redirect.Allowed(%s)", bidder) + } + }) + } +} + func TestParseTypeFilter(t *testing.T) { testCases := []struct { description string @@ -2517,7 +2775,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { testCases := []struct { description string givenGlobalConfig config.UserSync - givenAccountCookieSync config.CookieSync + givenAccountCookieSync config.AccountCookieSync expectedPriorityGroups [][]string }{ { @@ -2525,7 +2783,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: [][]string{{"global1", "global2"}, {"global3"}}, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: ptrutil.ToPtr(true), PriorityGroups: [][]string{{"account1", "account2"}, {"account3"}}, }, @@ -2536,7 +2794,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: [][]string{{"global1", "global2"}, {"global3"}}, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: ptrutil.ToPtr(false), PriorityGroups: [][]string{{"account1", "account2"}, {"account3"}}, }, @@ -2547,7 +2805,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: [][]string{{"global1", "global2"}, {"global3"}}, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: nil, PriorityGroups: [][]string{{"account1", "account2"}, {"account3"}}, }, @@ -2558,7 +2816,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: [][]string{{"global1", "global2"}, {"global3"}}, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: ptrutil.ToPtr(true), PriorityGroups: [][]string{}, }, @@ -2569,7 +2827,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: [][]string{{"global1", "global2"}, {"global3"}}, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: ptrutil.ToPtr(true), PriorityGroups: nil, }, @@ -2580,7 +2838,7 @@ func TestCookieSyncFindPriorityGroups(t *testing.T) { givenGlobalConfig: config.UserSync{ PriorityGroups: nil, }, - givenAccountCookieSync: config.CookieSync{ + givenAccountCookieSync: config.AccountCookieSync{ DefaultCoopSync: nil, PriorityGroups: [][]string{{"account1", "account2"}}, }, diff --git a/usersync/bidderfilter.go b/usersync/bidderfilter.go index 2e4df476..47a7fca6 100644 --- a/usersync/bidderfilter.go +++ b/usersync/bidderfilter.go @@ -64,3 +64,21 @@ func (f UniformBidderFilter) Allowed(bidder string) bool { func NewUniformBidderFilter(mode BidderFilterMode) BidderFilter { return UniformBidderFilter{mode: mode} } + +type CompositeFilter struct { + RequestFilter BidderFilter + AccountFilter BidderFilter +} + +func (f CompositeFilter) Allowed(bidder string) bool { + if f.RequestFilter == nil && f.AccountFilter == nil { + return true + } + if f.RequestFilter == nil { + return f.AccountFilter.Allowed(bidder) + } + if f.AccountFilter == nil { + return f.RequestFilter.Allowed(bidder) + } + return f.RequestFilter.Allowed(bidder) && f.AccountFilter.Allowed(bidder) +}