github.com/prebid/prebid-server@v0.275.0/usersync/chooser.go (about) 1 package usersync 2 3 // Chooser determines which syncers are eligible for a given request. 4 type Chooser interface { 5 // Choose considers bidders to sync, filters the bidders, and returns the result of the 6 // user sync selection. 7 Choose(request Request, cookie *Cookie) Result 8 } 9 10 // NewChooser returns a new instance of the standard chooser implementation. 11 func NewChooser(bidderSyncerLookup map[string]Syncer) Chooser { 12 bidders := make([]string, 0, len(bidderSyncerLookup)) 13 for k := range bidderSyncerLookup { 14 bidders = append(bidders, k) 15 } 16 17 return standardChooser{ 18 bidderSyncerLookup: bidderSyncerLookup, 19 biddersAvailable: bidders, 20 bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, 21 } 22 } 23 24 // Request specifies a user sync request. 25 type Request struct { 26 Bidders []string 27 Cooperative Cooperative 28 Limit int 29 Privacy Privacy 30 SyncTypeFilter SyncTypeFilter 31 } 32 33 // Cooperative specifies the settings for cooperative syncing for a given request, where bidders 34 // other than those used by the publisher are considered for syncing. 35 type Cooperative struct { 36 Enabled bool 37 PriorityGroups [][]string 38 } 39 40 // Result specifies which bidders were included in the evaluation and which syncers were chosen. 41 type Result struct { 42 BiddersEvaluated []BidderEvaluation 43 Status Status 44 SyncersChosen []SyncerChoice 45 } 46 47 // BidderEvaluation specifies which bidders were considered to be synced. 48 type BidderEvaluation struct { 49 Bidder string 50 SyncerKey string 51 Status Status 52 } 53 54 // SyncerChoice specifies a syncer chosen. 55 type SyncerChoice struct { 56 Bidder string 57 Syncer Syncer 58 } 59 60 // Status specifies the result of a sync evaluation. 61 type Status int 62 63 const ( 64 // StatusOK specifies user syncing is permitted. 65 StatusOK Status = iota 66 67 // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. 68 StatusBlockedByUserOptOut 69 70 // StatusBlockedByGDPR specifies a user's GDPR TCF consent explicitly forbids host cookies 71 // or specific bidder syncing. 72 StatusBlockedByGDPR 73 74 // StatusBlockedByCCPA specifies a user's CCPA consent explicitly forbids bidder syncing. 75 StatusBlockedByCCPA 76 77 // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. 78 StatusAlreadySynced 79 80 // StatusUnknownBidder specifies a requested bidder is unknown to Prebid Server. 81 StatusUnknownBidder 82 83 // StatusTypeNotSupported specifies a requested sync type is not supported by a specific bidder. 84 StatusTypeNotSupported 85 86 // StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice. 87 StatusDuplicate 88 89 // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities 90 StatusBlockedByPrivacy 91 ) 92 93 // Privacy determines which privacy policies will be enforced for a user sync request. 94 type Privacy interface { 95 GDPRAllowsHostCookie() bool 96 GDPRAllowsBidderSync(bidder string) bool 97 CCPAAllowsBidderSync(bidder string) bool 98 ActivityAllowsUserSync(bidder string) bool 99 } 100 101 // standardChooser implements the user syncer algorithm per official Prebid specification. 102 type standardChooser struct { 103 bidderSyncerLookup map[string]Syncer 104 biddersAvailable []string 105 bidderChooser bidderChooser 106 } 107 108 // Choose randomly selects user syncers which are permitted by the user's privacy settings and 109 // which don't already have a valid user sync. 110 func (c standardChooser) Choose(request Request, cookie *Cookie) Result { 111 if !cookie.AllowSyncs() { 112 return Result{Status: StatusBlockedByUserOptOut} 113 } 114 115 if !request.Privacy.GDPRAllowsHostCookie() { 116 return Result{Status: StatusBlockedByGDPR} 117 } 118 119 syncersSeen := make(map[string]struct{}) 120 limitDisabled := request.Limit <= 0 121 122 biddersEvaluated := make([]BidderEvaluation, 0) 123 syncersChosen := make([]SyncerChoice, 0) 124 125 bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) 126 for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { 127 syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie) 128 129 biddersEvaluated = append(biddersEvaluated, evaluation) 130 if evaluation.Status == StatusOK { 131 syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) 132 } 133 } 134 135 return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} 136 } 137 138 func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie) (Syncer, BidderEvaluation) { 139 syncer, exists := c.bidderSyncerLookup[bidder] 140 if !exists { 141 return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} 142 } 143 144 _, seen := syncersSeen[syncer.Key()] 145 if seen { 146 return nil, BidderEvaluation{Status: StatusDuplicate, Bidder: bidder, SyncerKey: syncer.Key()} 147 } 148 syncersSeen[syncer.Key()] = struct{}{} 149 150 if !syncer.SupportsType(syncTypeFilter.ForBidder(bidder)) { 151 return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()} 152 } 153 154 if cookie.HasLiveSync(syncer.Key()) { 155 return nil, BidderEvaluation{Status: StatusAlreadySynced, Bidder: bidder, SyncerKey: syncer.Key()} 156 } 157 158 userSyncActivityAllowed := privacy.ActivityAllowsUserSync(bidder) 159 if !userSyncActivityAllowed { 160 return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} 161 } 162 163 if !privacy.GDPRAllowsBidderSync(bidder) { 164 return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} 165 } 166 167 if !privacy.CCPAAllowsBidderSync(bidder) { 168 return nil, BidderEvaluation{Status: StatusBlockedByCCPA, Bidder: bidder, SyncerKey: syncer.Key()} 169 } 170 171 return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} 172 }