Skip to content
Open
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
16 changes: 9 additions & 7 deletions config/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ ccpa:
enforce: true
lmt:
enforce: true
cookie_sync:
disabled_iframe_bidders: ["hostBidder1", "hostBidder2"]
host_cookie:
cookie_name: userid
family: prebid
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions config/usersync.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}
64 changes: 61 additions & 3 deletions endpoints/cookie_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"io"
"math"
"net/http"
"slices"
"strconv"
"strings"
"time"
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -325,22 +329,76 @@ 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
}

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
}
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,
Expand Down
Loading
Loading