github.com/prebid/prebid-server/v2@v2.18.0/endpoints/cookie_sync.go (about) 1 package endpoints 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "strconv" 11 "strings" 12 "time" 13 14 "github.com/golang/glog" 15 "github.com/julienschmidt/httprouter" 16 gpplib "github.com/prebid/go-gpp" 17 gppConstants "github.com/prebid/go-gpp/constants" 18 accountService "github.com/prebid/prebid-server/v2/account" 19 "github.com/prebid/prebid-server/v2/analytics" 20 "github.com/prebid/prebid-server/v2/config" 21 "github.com/prebid/prebid-server/v2/errortypes" 22 "github.com/prebid/prebid-server/v2/gdpr" 23 "github.com/prebid/prebid-server/v2/macros" 24 "github.com/prebid/prebid-server/v2/metrics" 25 "github.com/prebid/prebid-server/v2/openrtb_ext" 26 "github.com/prebid/prebid-server/v2/privacy" 27 "github.com/prebid/prebid-server/v2/privacy/ccpa" 28 gppPrivacy "github.com/prebid/prebid-server/v2/privacy/gpp" 29 "github.com/prebid/prebid-server/v2/stored_requests" 30 "github.com/prebid/prebid-server/v2/usersync" 31 "github.com/prebid/prebid-server/v2/util/jsonutil" 32 stringutil "github.com/prebid/prebid-server/v2/util/stringutil" 33 "github.com/prebid/prebid-server/v2/util/timeutil" 34 ) 35 36 const receiveCookieDeprecation = "receive-cookie-deprecation" 37 38 var ( 39 errCookieSyncOptOut = errors.New("User has opted out") 40 errCookieSyncBody = errors.New("Failed to read request body") 41 errCookieSyncGDPRConsentMissing = errors.New("gdpr_consent is required if gdpr=1") 42 errCookieSyncGDPRConsentMissingSignalAmbiguous = errors.New("gdpr_consent is required. gdpr is not specified and is assumed to be 1 by the server. set gdpr=0 to exempt this request") 43 errCookieSyncInvalidBiddersType = errors.New("invalid bidders type. must either be a string '*' or a string array of bidders") 44 errCookieSyncAccountBlocked = errors.New("account is disabled, please reach out to the prebid server host") 45 errCookieSyncAccountConfigMalformed = errors.New("account config is malformed and could not be read") 46 errCookieSyncAccountInvalid = errors.New("account must be valid if provided, please reach out to the prebid server host") 47 errSyncerIsNotPriority = errors.New("syncer key is not a priority, and there are only priority elements left") 48 ) 49 50 var cookieSyncBidderFilterAllowAll = usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude) 51 52 func NewCookieSyncEndpoint( 53 syncersByBidder map[string]usersync.Syncer, 54 config *config.Configuration, 55 gdprPermsBuilder gdpr.PermissionsBuilder, 56 tcf2CfgBuilder gdpr.TCF2ConfigBuilder, 57 metrics metrics.MetricsEngine, 58 analyticsRunner analytics.Runner, 59 accountsFetcher stored_requests.AccountFetcher, 60 bidders map[string]openrtb_ext.BidderName) HTTPRouterHandler { 61 62 bidderHashSet := make(map[string]struct{}, len(bidders)) 63 for _, bidder := range bidders { 64 bidderHashSet[string(bidder)] = struct{}{} 65 } 66 67 return &cookieSyncEndpoint{ 68 chooser: usersync.NewChooser(syncersByBidder, bidderHashSet, config.BidderInfos), 69 config: config, 70 privacyConfig: usersyncPrivacyConfig{ 71 gdprConfig: config.GDPR, 72 gdprPermissionsBuilder: gdprPermsBuilder, 73 tcf2ConfigBuilder: tcf2CfgBuilder, 74 ccpaEnforce: config.CCPA.Enforce, 75 bidderHashSet: bidderHashSet, 76 }, 77 metrics: metrics, 78 pbsAnalytics: analyticsRunner, 79 accountsFetcher: accountsFetcher, 80 time: &timeutil.RealTime{}, 81 } 82 } 83 84 type cookieSyncEndpoint struct { 85 chooser usersync.Chooser 86 config *config.Configuration 87 privacyConfig usersyncPrivacyConfig 88 metrics metrics.MetricsEngine 89 pbsAnalytics analytics.Runner 90 accountsFetcher stored_requests.AccountFetcher 91 time timeutil.Time 92 } 93 94 func (c *cookieSyncEndpoint) Handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { 95 request, privacyMacros, account, err := c.parseRequest(r) 96 c.setCookieDeprecationHeader(w, r, account) 97 if err != nil { 98 c.writeParseRequestErrorMetrics(err) 99 c.handleError(w, err, http.StatusBadRequest) 100 return 101 } 102 decoder := usersync.Base64Decoder{} 103 104 cookie := usersync.ReadCookie(r, decoder, &c.config.HostCookie) 105 usersync.SyncHostCookie(r, cookie, &c.config.HostCookie) 106 107 result := c.chooser.Choose(request, cookie) 108 109 switch result.Status { 110 case usersync.StatusBlockedByUserOptOut: 111 c.metrics.RecordCookieSync(metrics.CookieSyncOptOut) 112 c.handleError(w, errCookieSyncOptOut, http.StatusUnauthorized) 113 case usersync.StatusBlockedByPrivacy: 114 c.metrics.RecordCookieSync(metrics.CookieSyncGDPRHostCookieBlocked) 115 c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, nil, result.BiddersEvaluated, request.Debug) 116 case usersync.StatusOK: 117 c.metrics.RecordCookieSync(metrics.CookieSyncOK) 118 c.writeSyncerMetrics(result.BiddersEvaluated) 119 c.handleResponse(w, request.SyncTypeFilter, cookie, privacyMacros, result.SyncersChosen, result.BiddersEvaluated, request.Debug) 120 } 121 } 122 123 func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, macros.UserSyncPrivacy, *config.Account, error) { 124 defer r.Body.Close() 125 body, err := io.ReadAll(r.Body) 126 if err != nil { 127 return usersync.Request{}, macros.UserSyncPrivacy{}, nil, errCookieSyncBody 128 } 129 130 request := cookieSyncRequest{} 131 if err := jsonutil.UnmarshalValid(body, &request); err != nil { 132 return usersync.Request{}, macros.UserSyncPrivacy{}, nil, fmt.Errorf("JSON parsing failed: %s", err.Error()) 133 } 134 135 if request.Account == "" { 136 request.Account = metrics.PublisherUnknown 137 } 138 account, fetchErrs := accountService.GetAccount(context.Background(), c.config, c.accountsFetcher, request.Account, c.metrics) 139 if len(fetchErrs) > 0 { 140 return usersync.Request{}, macros.UserSyncPrivacy{}, nil, combineErrors(fetchErrs) 141 } 142 143 request = c.setLimit(request, account.CookieSync) 144 request = c.setCooperativeSync(request, account.CookieSync) 145 146 privacyMacros, gdprSignal, privacyPolicies, err := extractPrivacyPolicies(request, c.privacyConfig.gdprConfig.DefaultValue) 147 if err != nil { 148 return usersync.Request{}, macros.UserSyncPrivacy{}, account, err 149 } 150 151 ccpaParsedPolicy := ccpa.ParsedPolicy{} 152 if request.USPrivacy != "" { 153 parsedPolicy, err := ccpa.Policy{Consent: request.USPrivacy}.Parse(c.privacyConfig.bidderHashSet) 154 if err != nil { 155 privacyMacros.USPrivacy = "" 156 } 157 if c.privacyConfig.ccpaEnforce { 158 ccpaParsedPolicy = parsedPolicy 159 } 160 } 161 162 activityControl := privacy.NewActivityControl(&account.Privacy) 163 164 syncTypeFilter, err := parseTypeFilter(request.FilterSettings) 165 if err != nil { 166 return usersync.Request{}, macros.UserSyncPrivacy{}, account, err 167 } 168 169 gdprRequestInfo := gdpr.RequestInfo{ 170 Consent: privacyMacros.GDPRConsent, 171 GDPRSignal: gdprSignal, 172 } 173 174 tcf2Cfg := c.privacyConfig.tcf2ConfigBuilder(c.privacyConfig.gdprConfig.TCF2, account.GDPR) 175 gdprPerms := c.privacyConfig.gdprPermissionsBuilder(tcf2Cfg, gdprRequestInfo) 176 177 rx := usersync.Request{ 178 Bidders: request.Bidders, 179 Cooperative: usersync.Cooperative{ 180 Enabled: (request.CooperativeSync != nil && *request.CooperativeSync) || (request.CooperativeSync == nil && c.config.UserSync.Cooperative.EnabledByDefault), 181 PriorityGroups: c.config.UserSync.PriorityGroups, 182 }, 183 Debug: request.Debug, 184 Limit: request.Limit, 185 Privacy: usersyncPrivacy{ 186 gdprPermissions: gdprPerms, 187 ccpaParsedPolicy: ccpaParsedPolicy, 188 activityControl: activityControl, 189 activityRequest: privacy.NewRequestFromPolicies(privacyPolicies), 190 gdprSignal: gdprSignal, 191 }, 192 SyncTypeFilter: syncTypeFilter, 193 GPPSID: request.GPPSID, 194 } 195 return rx, privacyMacros, account, nil 196 } 197 198 func extractPrivacyPolicies(request cookieSyncRequest, usersyncDefaultGDPRValue string) (macros.UserSyncPrivacy, gdpr.Signal, privacy.Policies, error) { 199 // GDPR 200 gppSID, err := stringutil.StrToInt8Slice(request.GPPSID) 201 if err != nil { 202 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err 203 } 204 205 gdprSignal, gdprString, err := extractGDPRSignal(request.GDPR, gppSID) 206 if err != nil { 207 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err 208 } 209 210 var gpp gpplib.GppContainer 211 if len(request.GPP) > 0 { 212 var errs []error 213 gpp, errs = gpplib.Parse(request.GPP) 214 if len(errs) > 0 { 215 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errs[0] 216 } 217 } 218 219 gdprConsent := request.GDPRConsent 220 if i := gppPrivacy.IndexOfSID(gpp, gppConstants.SectionTCFEU2); i >= 0 { 221 gdprConsent = gpp.Sections[i].GetValue() 222 } 223 224 if gdprConsent == "" { 225 if gdprSignal == gdpr.SignalYes { 226 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errCookieSyncGDPRConsentMissing 227 } 228 229 if gdprSignal == gdpr.SignalAmbiguous && gdpr.SignalNormalize(gdprSignal, usersyncDefaultGDPRValue) == gdpr.SignalYes { 230 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, errCookieSyncGDPRConsentMissingSignalAmbiguous 231 } 232 } 233 234 // CCPA 235 ccpaString, err := ccpa.SelectCCPAConsent(request.USPrivacy, gpp, gppSID) 236 if err != nil { 237 return macros.UserSyncPrivacy{}, gdpr.SignalNo, privacy.Policies{}, err 238 } 239 240 privacyMacros := macros.UserSyncPrivacy{ 241 GDPR: gdprString, 242 GDPRConsent: gdprConsent, 243 USPrivacy: ccpaString, 244 GPP: request.GPP, 245 GPPSID: request.GPPSID, 246 } 247 248 privacyPolicies := privacy.Policies{ 249 GPPSID: gppSID, 250 } 251 252 return privacyMacros, gdprSignal, privacyPolicies, nil 253 } 254 255 func extractGDPRSignal(requestGDPR *int, gppSID []int8) (gdpr.Signal, string, error) { 256 if len(gppSID) > 0 { 257 if gppPrivacy.IsSIDInList(gppSID, gppConstants.SectionTCFEU2) { 258 return gdpr.SignalYes, strconv.Itoa(int(gdpr.SignalYes)), nil 259 } 260 return gdpr.SignalNo, strconv.Itoa(int(gdpr.SignalNo)), nil 261 } 262 263 if requestGDPR == nil { 264 return gdpr.SignalAmbiguous, "", nil 265 } 266 267 gdprSignal, err := gdpr.IntSignalParse(*requestGDPR) 268 if err != nil { 269 return gdpr.SignalAmbiguous, strconv.Itoa(*requestGDPR), err 270 } 271 return gdprSignal, strconv.Itoa(*requestGDPR), nil 272 } 273 274 func (c *cookieSyncEndpoint) writeParseRequestErrorMetrics(err error) { 275 switch err { 276 case errCookieSyncAccountBlocked: 277 c.metrics.RecordCookieSync(metrics.CookieSyncAccountBlocked) 278 case errCookieSyncAccountConfigMalformed: 279 c.metrics.RecordCookieSync(metrics.CookieSyncAccountConfigMalformed) 280 case errCookieSyncAccountInvalid: 281 c.metrics.RecordCookieSync(metrics.CookieSyncAccountInvalid) 282 default: 283 c.metrics.RecordCookieSync(metrics.CookieSyncBadRequest) 284 } 285 } 286 287 func (c *cookieSyncEndpoint) setLimit(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { 288 if request.Limit <= 0 && cookieSyncConfig.DefaultLimit != nil { 289 request.Limit = *cookieSyncConfig.DefaultLimit 290 } 291 if cookieSyncConfig.MaxLimit != nil && (request.Limit <= 0 || request.Limit > *cookieSyncConfig.MaxLimit) { 292 request.Limit = *cookieSyncConfig.MaxLimit 293 } 294 if request.Limit < 0 { 295 request.Limit = 0 296 } 297 298 return request 299 } 300 301 func (c *cookieSyncEndpoint) setCooperativeSync(request cookieSyncRequest, cookieSyncConfig config.CookieSync) cookieSyncRequest { 302 if request.CooperativeSync == nil && cookieSyncConfig.DefaultCoopSync != nil { 303 request.CooperativeSync = cookieSyncConfig.DefaultCoopSync 304 } 305 306 return request 307 } 308 309 func parseTypeFilter(request *cookieSyncRequestFilterSettings) (usersync.SyncTypeFilter, error) { 310 syncTypeFilter := usersync.SyncTypeFilter{ 311 IFrame: cookieSyncBidderFilterAllowAll, 312 Redirect: cookieSyncBidderFilterAllowAll, 313 } 314 315 if request != nil { 316 if filter, err := parseBidderFilter(request.IFrame); err == nil { 317 syncTypeFilter.IFrame = filter 318 } else { 319 return usersync.SyncTypeFilter{}, fmt.Errorf("error parsing filtersettings.iframe: %v", err) 320 } 321 322 if filter, err := parseBidderFilter(request.Redirect); err == nil { 323 syncTypeFilter.Redirect = filter 324 } else { 325 return usersync.SyncTypeFilter{}, fmt.Errorf("error parsing filtersettings.image: %v", err) 326 } 327 } 328 329 return syncTypeFilter, nil 330 } 331 332 func parseBidderFilter(filter *cookieSyncRequestFilter) (usersync.BidderFilter, error) { 333 if filter == nil { 334 return cookieSyncBidderFilterAllowAll, nil 335 } 336 337 var mode usersync.BidderFilterMode 338 switch filter.Mode { 339 case "include": 340 mode = usersync.BidderFilterModeInclude 341 case "exclude": 342 mode = usersync.BidderFilterModeExclude 343 default: 344 return nil, fmt.Errorf("invalid filter value '%s'. must be either 'include' or 'exclude'", filter.Mode) 345 } 346 347 switch v := filter.Bidders.(type) { 348 case string: 349 if v == "*" { 350 return usersync.NewUniformBidderFilter(mode), nil 351 } 352 return nil, fmt.Errorf("invalid bidders value `%s`. must either be '*' or a string array", v) 353 case []interface{}: 354 bidders := make([]string, len(v)) 355 for i, x := range v { 356 if bidder, ok := x.(string); ok { 357 bidders[i] = bidder 358 } else { 359 return nil, errCookieSyncInvalidBiddersType 360 } 361 } 362 return usersync.NewSpecificBidderFilter(bidders, mode), nil 363 default: 364 return nil, errCookieSyncInvalidBiddersType 365 } 366 } 367 368 func (c *cookieSyncEndpoint) handleError(w http.ResponseWriter, err error, httpStatus int) { 369 http.Error(w, err.Error(), httpStatus) 370 c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ 371 Status: httpStatus, 372 Errors: []error{err}, 373 BidderStatus: []*analytics.CookieSyncBidder{}, 374 }) 375 } 376 377 func combineErrors(errs []error) error { 378 var errorStrings []string 379 for _, err := range errs { 380 // preserve knowledge of special account errors 381 switch errortypes.ReadCode(err) { 382 case errortypes.AccountDisabledErrorCode: 383 return errCookieSyncAccountBlocked 384 case errortypes.AcctRequiredErrorCode: 385 return errCookieSyncAccountInvalid 386 case errortypes.MalformedAcctErrorCode: 387 return errCookieSyncAccountConfigMalformed 388 } 389 390 errorStrings = append(errorStrings, err.Error()) 391 } 392 combinedErrors := strings.Join(errorStrings, " ") 393 return errors.New(combinedErrors) 394 } 395 396 func (c *cookieSyncEndpoint) writeSyncerMetrics(biddersEvaluated []usersync.BidderEvaluation) { 397 for _, bidder := range biddersEvaluated { 398 switch bidder.Status { 399 case usersync.StatusOK: 400 c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncOK) 401 case usersync.StatusBlockedByPrivacy: 402 c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncPrivacyBlocked) 403 case usersync.StatusAlreadySynced: 404 c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncAlreadySynced) 405 case usersync.StatusTypeNotSupported: 406 c.metrics.RecordSyncerRequest(bidder.SyncerKey, metrics.SyncerCookieSyncTypeNotSupported) 407 } 408 } 409 } 410 411 func (c *cookieSyncEndpoint) handleResponse(w http.ResponseWriter, tf usersync.SyncTypeFilter, co *usersync.Cookie, m macros.UserSyncPrivacy, s []usersync.SyncerChoice, biddersEvaluated []usersync.BidderEvaluation, debug bool) { 412 status := "no_cookie" 413 if co.HasAnyLiveSyncs() { 414 status = "ok" 415 } 416 417 response := cookieSyncResponse{ 418 Status: status, 419 BidderStatus: make([]cookieSyncResponseBidder, 0, len(s)), 420 } 421 422 for _, syncerChoice := range s { 423 syncTypes := tf.ForBidder(syncerChoice.Bidder) 424 sync, err := syncerChoice.Syncer.GetSync(syncTypes, m) 425 if err != nil { 426 glog.Errorf("Failed to get usersync info for %s: %v", syncerChoice.Bidder, err) 427 continue 428 } 429 430 response.BidderStatus = append(response.BidderStatus, cookieSyncResponseBidder{ 431 BidderCode: syncerChoice.Bidder, 432 NoCookie: true, 433 UsersyncInfo: cookieSyncResponseSync{ 434 URL: sync.URL, 435 Type: string(sync.Type), 436 SupportCORS: sync.SupportCORS, 437 }, 438 }) 439 } 440 441 if debug { 442 biddersSeen := make(map[string]struct{}) 443 var debugInfo []cookieSyncResponseDebug 444 for _, bidderEval := range biddersEvaluated { 445 var debugResponse cookieSyncResponseDebug 446 debugResponse.Bidder = bidderEval.Bidder 447 if bidderEval.Status == usersync.StatusDuplicate && biddersSeen[bidderEval.Bidder] == struct{}{} { 448 debugResponse.Error = getDebugMessage(bidderEval.Status) + " synced as " + bidderEval.SyncerKey 449 debugInfo = append(debugInfo, debugResponse) 450 } else if bidderEval.Status != usersync.StatusOK { 451 debugResponse.Error = getDebugMessage(bidderEval.Status) 452 debugInfo = append(debugInfo, debugResponse) 453 } 454 biddersSeen[bidderEval.Bidder] = struct{}{} 455 } 456 response.Debug = debugInfo 457 } 458 459 c.pbsAnalytics.LogCookieSyncObject(&analytics.CookieSyncObject{ 460 Status: http.StatusOK, 461 BidderStatus: mapBidderStatusToAnalytics(response.BidderStatus), 462 }) 463 464 w.Header().Set("Content-Type", "application/json; charset=utf-8") 465 466 enc := json.NewEncoder(w) 467 enc.SetEscapeHTML(false) 468 enc.Encode(response) 469 } 470 471 func (c *cookieSyncEndpoint) setCookieDeprecationHeader(w http.ResponseWriter, r *http.Request, account *config.Account) { 472 if rcd, err := r.Cookie(receiveCookieDeprecation); err == nil && rcd != nil { 473 return 474 } 475 if account == nil || !account.Privacy.PrivacySandbox.CookieDeprecation.Enabled { 476 return 477 } 478 cookie := &http.Cookie{ 479 Name: receiveCookieDeprecation, 480 Value: "1", 481 Secure: true, 482 HttpOnly: true, 483 Path: "/", 484 SameSite: http.SameSiteNoneMode, 485 Expires: c.time.Now().Add(time.Second * time.Duration(account.Privacy.PrivacySandbox.CookieDeprecation.TTLSec)), 486 } 487 setCookiePartitioned(w, cookie) 488 } 489 490 // setCookiePartitioned temporary substitute for http.SetCookie(w, cookie) until it supports Partitioned cookie type. Refer https://github.com/golang/go/issues/62490 491 func setCookiePartitioned(w http.ResponseWriter, cookie *http.Cookie) { 492 if v := cookie.String(); v != "" { 493 w.Header().Add("Set-Cookie", v+"; Partitioned;") 494 } 495 } 496 497 func mapBidderStatusToAnalytics(from []cookieSyncResponseBidder) []*analytics.CookieSyncBidder { 498 to := make([]*analytics.CookieSyncBidder, len(from)) 499 for i, b := range from { 500 to[i] = &analytics.CookieSyncBidder{ 501 BidderCode: b.BidderCode, 502 NoCookie: b.NoCookie, 503 UsersyncInfo: &analytics.UsersyncInfo{ 504 URL: b.UsersyncInfo.URL, 505 Type: b.UsersyncInfo.Type, 506 SupportCORS: b.UsersyncInfo.SupportCORS, 507 }, 508 } 509 } 510 return to 511 } 512 513 func getDebugMessage(status usersync.Status) string { 514 switch status { 515 case usersync.StatusAlreadySynced: 516 return "Already in sync" 517 case usersync.StatusBlockedByPrivacy: 518 return "Rejected by privacy" 519 case usersync.StatusBlockedByUserOptOut: 520 return "Status blocked by user opt out" 521 case usersync.StatusDuplicate: 522 return "Duplicate bidder" 523 case usersync.StatusUnknownBidder: 524 return "Unsupported bidder" 525 case usersync.StatusUnconfiguredBidder: 526 return "No sync config" 527 case usersync.StatusTypeNotSupported: 528 return "Type not supported" 529 case usersync.StatusBlockedByDisabledUsersync: 530 return "Sync disabled by config" 531 } 532 return "" 533 } 534 535 type cookieSyncRequest struct { 536 Bidders []string `json:"bidders"` 537 GDPR *int `json:"gdpr"` 538 GDPRConsent string `json:"gdpr_consent"` 539 USPrivacy string `json:"us_privacy"` 540 Limit int `json:"limit"` 541 GPP string `json:"gpp"` 542 GPPSID string `json:"gpp_sid"` 543 CooperativeSync *bool `json:"coopSync"` 544 FilterSettings *cookieSyncRequestFilterSettings `json:"filterSettings"` 545 Account string `json:"account"` 546 Debug bool `json:"debug"` 547 } 548 549 type cookieSyncRequestFilterSettings struct { 550 IFrame *cookieSyncRequestFilter `json:"iframe"` 551 Redirect *cookieSyncRequestFilter `json:"image"` 552 } 553 554 type cookieSyncRequestFilter struct { 555 Bidders interface{} `json:"bidders"` 556 Mode string `json:"filter"` 557 } 558 559 type cookieSyncResponse struct { 560 Status string `json:"status"` 561 BidderStatus []cookieSyncResponseBidder `json:"bidder_status"` 562 Debug []cookieSyncResponseDebug `json:"debug,omitempty"` 563 } 564 565 type cookieSyncResponseBidder struct { 566 BidderCode string `json:"bidder"` 567 NoCookie bool `json:"no_cookie,omitempty"` 568 UsersyncInfo cookieSyncResponseSync `json:"usersync,omitempty"` 569 } 570 571 type cookieSyncResponseSync struct { 572 URL string `json:"url,omitempty"` 573 Type string `json:"type,omitempty"` 574 SupportCORS bool `json:"supportCORS,omitempty"` 575 } 576 577 type cookieSyncResponseDebug struct { 578 Bidder string `json:"bidder"` 579 Error string `json:"error,omitempty"` 580 } 581 582 type usersyncPrivacyConfig struct { 583 gdprConfig config.GDPR 584 gdprPermissionsBuilder gdpr.PermissionsBuilder 585 tcf2ConfigBuilder gdpr.TCF2ConfigBuilder 586 ccpaEnforce bool 587 bidderHashSet map[string]struct{} 588 } 589 590 type usersyncPrivacy struct { 591 gdprPermissions gdpr.Permissions 592 ccpaParsedPolicy ccpa.ParsedPolicy 593 activityControl privacy.ActivityControl 594 activityRequest privacy.ActivityRequest 595 gdprSignal gdpr.Signal 596 } 597 598 func (p usersyncPrivacy) GDPRAllowsHostCookie() bool { 599 allowCookie, err := p.gdprPermissions.HostCookiesAllowed(context.Background()) 600 return err == nil && allowCookie 601 } 602 603 func (p usersyncPrivacy) GDPRAllowsBidderSync(bidder string) bool { 604 allowSync, err := p.gdprPermissions.BidderSyncAllowed(context.Background(), openrtb_ext.BidderName(bidder)) 605 return err == nil && allowSync 606 } 607 608 func (p usersyncPrivacy) CCPAAllowsBidderSync(bidder string) bool { 609 enforce := p.ccpaParsedPolicy.CanEnforce() && p.ccpaParsedPolicy.ShouldEnforce(bidder) 610 return !enforce 611 } 612 613 func (p usersyncPrivacy) ActivityAllowsUserSync(bidder string) bool { 614 return p.activityControl.Allow( 615 privacy.ActivitySyncUser, 616 privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidder}, 617 p.activityRequest) 618 } 619 620 func (p usersyncPrivacy) GDPRInScope() bool { 621 return p.gdprSignal == gdpr.SignalYes 622 }