Skip to content

Commit

Permalink
Privacy Sandbox: support testing label header (#3381)
Browse files Browse the repository at this point in the history
  • Loading branch information
pm-nilesh-chate authored Mar 5, 2024
1 parent 05a1293 commit 4b1ca6b
Show file tree
Hide file tree
Showing 12 changed files with 779 additions and 56 deletions.
10 changes: 10 additions & 0 deletions config/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,16 @@ type AccountPrivacy struct {
AllowActivities *AllowActivities `mapstructure:"allowactivities" json:"allowactivities"`
IPv6Config IPv6 `mapstructure:"ipv6" json:"ipv6"`
IPv4Config IPv4 `mapstructure:"ipv4" json:"ipv4"`
PrivacySandbox PrivacySandbox `mapstructure:"privacysandbox" json:"privacysandbox"`
}

type PrivacySandbox struct {
CookieDeprecation CookieDeprecation `mapstructure:"cookiedeprecation"`
}

type CookieDeprecation struct {
Enabled bool `mapstructure:"enabled"`
TTLSec int `mapstructure:"ttl_sec"`
}

type IPv6 struct {
Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -1107,6 +1107,8 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) {
v.SetDefault("account_defaults.price_floors.fetch.max_age_sec", 86400)
v.SetDefault("account_defaults.price_floors.fetch.period_sec", 3600)
v.SetDefault("account_defaults.price_floors.fetch.max_schema_dims", 0)
v.SetDefault("account_defaults.privacy.privacysandbox.cookiedeprecation.enabled", false)
v.SetDefault("account_defaults.privacy.privacysandbox.cookiedeprecation.ttl_sec", 604800)

v.SetDefault("account_defaults.events_enabled", false)
v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56)
Expand Down
9 changes: 9 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ func TestDefaults(t *testing.T) {
cmpInts(t, "account_defaults.price_floors.fetch.period_sec", 3600, cfg.AccountDefaults.PriceFloors.Fetcher.Period)
cmpInts(t, "account_defaults.price_floors.fetch.max_age_sec", 86400, cfg.AccountDefaults.PriceFloors.Fetcher.MaxAge)
cmpInts(t, "account_defaults.price_floors.fetch.max_schema_dims", 0, cfg.AccountDefaults.PriceFloors.Fetcher.MaxSchemaDims)
cmpBools(t, "account_defaults.privacy.privacysandbox.cookiedeprecation.enabled", false, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.Enabled)
cmpInts(t, "account_defaults.privacy.privacysandbox.cookiedeprecation.ttl_sec", 604800, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.TTLSec)

cmpBools(t, "account_defaults.events.enabled", false, cfg.AccountDefaults.Events.Enabled)

Expand Down Expand Up @@ -503,6 +505,10 @@ account_defaults:
anon_keep_bits: 50
ipv4:
anon_keep_bits: 20
privacysandbox:
cookiedeprecation:
enabled: true
ttl_sec: 86400
tmax_adjustments:
enabled: true
bidder_response_duration_min_ms: 700
Expand Down Expand Up @@ -622,6 +628,9 @@ func TestFullConfig(t *testing.T) {
cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits)
cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits)

cmpBools(t, "account_defaults.privacy.cookiedeprecation.enabled", true, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.Enabled)
cmpInts(t, "account_defaults.privacy.cookiedeprecation.ttl_sec", 86400, cfg.AccountDefaults.Privacy.PrivacySandbox.CookieDeprecation.TTLSec)

// Assert compression related defaults
cmpBools(t, "compression.request.enable_gzip", true, cfg.Compression.Request.GZIP)
cmpBools(t, "compression.response.enable_gzip", false, cfg.Compression.Response.GZIP)
Expand Down
50 changes: 42 additions & 8 deletions endpoints/cookie_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"strconv"
"strings"
"time"

"github.com/golang/glog"
"github.com/julienschmidt/httprouter"
Expand All @@ -29,8 +30,11 @@ import (
"github.com/prebid/prebid-server/v2/usersync"
"github.com/prebid/prebid-server/v2/util/jsonutil"
stringutil "github.com/prebid/prebid-server/v2/util/stringutil"
"github.com/prebid/prebid-server/v2/util/timeutil"
)

const receiveCookieDeprecation = "receive-cookie-deprecation"

var (
errCookieSyncOptOut = errors.New("User has opted out")
errCookieSyncBody = errors.New("Failed to read request body")
Expand Down Expand Up @@ -73,6 +77,7 @@ func NewCookieSyncEndpoint(
metrics: metrics,
pbsAnalytics: analyticsRunner,
accountsFetcher: accountsFetcher,
time: &timeutil.RealTime{},
}
}

Expand All @@ -83,10 +88,12 @@ type cookieSyncEndpoint struct {
metrics metrics.MetricsEngine
pbsAnalytics analytics.Runner
accountsFetcher stored_requests.AccountFetcher
time timeutil.Time
}

func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
request, privacyMacros, err := c.parseRequest(r)
request, privacyMacros, account, err := c.parseRequest(r)
c.setCookieDeprecationHeader(w, r, account)
if err != nil {
c.writeParseRequestErrorMetrics(err)
c.handleError(w, err, http.StatusBadRequest)
Expand All @@ -113,32 +120,32 @@ func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ ht
}
}

func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, error) {
func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, *config.Account, error) {
defer r.Body.Close()
body, err := io.ReadAll(r.Body)
if err != nil {
return usersync.Request{}, macros.UserSyncPrivacy{}, errCookieSyncBody
return usersync.Request{}, macros.UserSyncPrivacy{}, nil, errCookieSyncBody
}

request := cookieSyncRequest{}
if err := jsonutil.UnmarshalValid(body, &request); err != nil {
return usersync.Request{}, macros.UserSyncPrivacy{}, fmt.Errorf("JSON parsing failed: %s", err.Error())
return usersync.Request{}, macros.UserSyncPrivacy{}, nil, fmt.Errorf("JSON parsing failed: %s", err.Error())
}

if request.Account == "" {
request.Account = metrics.PublisherUnknown
}
account, fetchErrs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account, c.metrics)
if len(fetchErrs) > 0 {
return usersync.Request{}, macros.UserSyncPrivacy{}, combineErrors(fetchErrs)
return usersync.Request{}, macros.UserSyncPrivacy{}, nil, combineErrors(fetchErrs)
}

request = c.setLimit(request, account.CookieSync)
request = c.setCooperativeSync(request, account.CookieSync)

privacyMacros, gdprSignal, privacyPolicies, err := extractPrivacyPolicies(request, c.privacyConfig.gdprConfig.DefaultValue)
if err != nil {
return usersync.Request{}, macros.UserSyncPrivacy{}, err
return usersync.Request{}, macros.UserSyncPrivacy{}, account, err
}

ccpaParsedPolicy := ccpa.ParsedPolicy{}
Expand All @@ -156,7 +163,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma

syncTypeFilter, err := parseTypeFilter(request.FilterSettings)
if err != nil {
return usersync.Request{}, macros.UserSyncPrivacy{}, err
return usersync.Request{}, macros.UserSyncPrivacy{}, account, err
}

gdprRequestInfo := gdpr.RequestInfo{
Expand Down Expand Up @@ -185,7 +192,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, ma
SyncTypeFilter: syncTypeFilter,
GPPSID: request.GPPSID,
}
return rx, privacyMacros, nil
return rx, privacyMacros, account, nil
}

func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue string) (macros.UserSyncPrivacy, gdpr.Signal, privacy.Policies, error) {
Expand Down Expand Up @@ -455,11 +462,38 @@ func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.S
})

w.Header().Set("Content-Type", "application/json; charset=utf-8")

enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
enc.Encode(response)
}

func (c *cookieSyncEndpoint) setCookieDeprecationHeader(w http.ResponseWriter, r *http.Request, account *config.Account) {
if rcd, err := r.Cookie(receiveCookieDeprecation); err == nil && rcd != nil {
return
}
if account == nil || !account.Privacy.PrivacySandbox.CookieDeprecation.Enabled {
return
}
cookie := &http.Cookie{
Name: receiveCookieDeprecation,
Value: "1",
Secure: true,
HttpOnly: true,
Path: "/",
SameSite: http.SameSiteNoneMode,
Expires: c.time.Now().Add(time.Second * time.Duration(account.Privacy.PrivacySandbox.CookieDeprecation.TTLSec)),
}
setCookiePartitioned(w, cookie)
}

// setCookiePartitioned temporary substitute for http.SetCookie(w, cookie) until it supports Partitioned cookie type. Refer https://github.com/golang/go/issues/62490
func setCookiePartitioned(w http.ResponseWriter, cookie *http.Cookie) {
if v := cookie.String(); v != "" {
w.Header().Add("Set-Cookie", v+"; Partitioned;")
}
}

func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.CookieSyncBidder {
to := make([]*analytics.CookieSyncBidder, len(from))
for i, b := range from {
Expand Down
Loading

0 comments on commit 4b1ca6b

Please sign in to comment.