github.com/prebid/prebid-server/v2@v2.18.0/usersync/chooser.go (about) 1 package usersync 2 3 import ( 4 "strings" 5 6 "github.com/prebid/prebid-server/v2/config" 7 "github.com/prebid/prebid-server/v2/openrtb_ext" 8 ) 9 10 // Chooser determines which syncers are eligible for a given request. 11 type Chooser interface { 12 // Choose considers bidders to sync, filters the bidders, and returns the result of the 13 // user sync selection. 14 Choose(request Request, cookie *Cookie) Result 15 } 16 17 // NewChooser returns a new instance of the standard chooser implementation. 18 func NewChooser(bidderSyncerLookup map[string]Syncer, biddersKnown map[string]struct{}, bidderInfo map[string]config.BidderInfo) Chooser { 19 bidders := make([]string, 0, len(bidderSyncerLookup)) 20 21 for k := range bidderSyncerLookup { 22 bidders = append(bidders, k) 23 } 24 25 return standardChooser{ 26 bidderSyncerLookup: bidderSyncerLookup, 27 biddersAvailable: bidders, 28 bidderChooser: standardBidderChooser{shuffler: randomShuffler{}}, 29 normalizeValidBidderName: openrtb_ext.NormalizeBidderName, 30 biddersKnown: biddersKnown, 31 bidderInfo: bidderInfo, 32 } 33 } 34 35 // Request specifies a user sync request. 36 type Request struct { 37 Bidders []string 38 Cooperative Cooperative 39 Limit int 40 Privacy Privacy 41 SyncTypeFilter SyncTypeFilter 42 GPPSID string 43 Debug bool 44 } 45 46 // Cooperative specifies the settings for cooperative syncing for a given request, where bidders 47 // other than those used by the publisher are considered for syncing. 48 type Cooperative struct { 49 Enabled bool 50 PriorityGroups [][]string 51 } 52 53 // Result specifies which bidders were included in the evaluation and which syncers were chosen. 54 type Result struct { 55 BiddersEvaluated []BidderEvaluation 56 Status Status 57 SyncersChosen []SyncerChoice 58 } 59 60 // BidderEvaluation specifies which bidders were considered to be synced. 61 type BidderEvaluation struct { 62 Bidder string 63 SyncerKey string 64 Status Status 65 } 66 67 // SyncerChoice specifies a syncer chosen. 68 type SyncerChoice struct { 69 Bidder string 70 Syncer Syncer 71 } 72 73 // Status specifies the result of a sync evaluation. 74 type Status int 75 76 const ( 77 // StatusOK specifies user syncing is permitted. 78 StatusOK Status = iota 79 80 // StatusBlockedByUserOptOut specifies a user's cookie explicitly signals an opt-out. 81 StatusBlockedByUserOptOut 82 83 // StatusAlreadySynced specifies a user's cookie has an existing non-expired sync for a specific bidder. 84 StatusAlreadySynced 85 86 // StatusUnknownBidder specifies a requested bidder is unknown to Prebid Server. 87 StatusUnknownBidder 88 89 // StatusTypeNotSupported specifies a requested sync type is not supported by a specific bidder. 90 StatusTypeNotSupported 91 92 // StatusDuplicate specifies the bidder is a duplicate or shared a syncer key with another bidder choice. 93 StatusDuplicate 94 95 // StatusBlockedByPrivacy specifies a bidder sync url is not allowed by privacy activities 96 StatusBlockedByPrivacy 97 98 // StatusBlockedByRegulationScope specifies the bidder chose to not sync given GDPR being in scope or because of a GPPSID 99 StatusBlockedByRegulationScope 100 101 // StatusUnconfiguredBidder refers to a bidder who hasn't been configured to have a syncer key, but is known by Prebid Server 102 StatusUnconfiguredBidder 103 104 // StatusBlockedByDisabledUsersync refers to a bidder who won't be synced because it's been disabled in its config by the host 105 StatusBlockedByDisabledUsersync 106 ) 107 108 // Privacy determines which privacy policies will be enforced for a user sync request. 109 type Privacy interface { 110 GDPRAllowsHostCookie() bool 111 GDPRInScope() bool 112 GDPRAllowsBidderSync(bidder string) bool 113 CCPAAllowsBidderSync(bidder string) bool 114 ActivityAllowsUserSync(bidder string) bool 115 } 116 117 // standardChooser implements the user syncer algorithm per official Prebid specification. 118 type standardChooser struct { 119 bidderSyncerLookup map[string]Syncer 120 biddersAvailable []string 121 bidderChooser bidderChooser 122 normalizeValidBidderName func(name string) (openrtb_ext.BidderName, bool) 123 biddersKnown map[string]struct{} 124 bidderInfo map[string]config.BidderInfo 125 } 126 127 // Choose randomly selects user syncers which are permitted by the user's privacy settings and 128 // which don't already have a valid user sync. 129 func (c standardChooser) Choose(request Request, cookie *Cookie) Result { 130 if !cookie.AllowSyncs() { 131 return Result{Status: StatusBlockedByUserOptOut} 132 } 133 134 if !request.Privacy.GDPRAllowsHostCookie() { 135 return Result{Status: StatusBlockedByPrivacy} 136 } 137 138 syncersSeen := make(map[string]struct{}) 139 biddersSeen := make(map[string]struct{}) 140 limitDisabled := request.Limit <= 0 141 142 biddersEvaluated := make([]BidderEvaluation, 0) 143 syncersChosen := make([]SyncerChoice, 0) 144 145 bidders := c.bidderChooser.choose(request.Bidders, c.biddersAvailable, request.Cooperative) 146 for i := 0; i < len(bidders) && (limitDisabled || len(syncersChosen) < request.Limit); i++ { 147 if _, ok := biddersSeen[bidders[i]]; ok { 148 continue 149 } 150 syncer, evaluation := c.evaluate(bidders[i], syncersSeen, request.SyncTypeFilter, request.Privacy, cookie, request.GPPSID) 151 152 biddersEvaluated = append(biddersEvaluated, evaluation) 153 if evaluation.Status == StatusOK { 154 syncersChosen = append(syncersChosen, SyncerChoice{Bidder: bidders[i], Syncer: syncer}) 155 } 156 biddersSeen[bidders[i]] = struct{}{} 157 } 158 159 return Result{Status: StatusOK, BiddersEvaluated: biddersEvaluated, SyncersChosen: syncersChosen} 160 } 161 162 func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{}, syncTypeFilter SyncTypeFilter, privacy Privacy, cookie *Cookie, GPPSID string) (Syncer, BidderEvaluation) { 163 bidderNormalized, exists := c.normalizeValidBidderName(bidder) 164 if !exists { 165 return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} 166 } 167 168 syncer, exists := c.bidderSyncerLookup[bidderNormalized.String()] 169 if !exists { 170 if _, ok := c.biddersKnown[bidder]; !ok { 171 return nil, BidderEvaluation{Status: StatusUnknownBidder, Bidder: bidder} 172 } else { 173 return nil, BidderEvaluation{Status: StatusUnconfiguredBidder, Bidder: bidder} 174 } 175 } 176 177 _, seen := syncersSeen[syncer.Key()] 178 if seen { 179 return nil, BidderEvaluation{Status: StatusDuplicate, Bidder: bidder, SyncerKey: syncer.Key()} 180 } 181 syncersSeen[syncer.Key()] = struct{}{} 182 183 if !syncer.SupportsType(syncTypeFilter.ForBidder(strings.ToLower(bidder))) { 184 return nil, BidderEvaluation{Status: StatusTypeNotSupported, Bidder: bidder, SyncerKey: syncer.Key()} 185 } 186 187 if cookie.HasLiveSync(syncer.Key()) { 188 return nil, BidderEvaluation{Status: StatusAlreadySynced, Bidder: bidder, SyncerKey: syncer.Key()} 189 } 190 191 userSyncActivityAllowed := privacy.ActivityAllowsUserSync(bidder) 192 if !userSyncActivityAllowed { 193 return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} 194 } 195 196 if !privacy.GDPRAllowsBidderSync(bidderNormalized.String()) { 197 return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} 198 } 199 200 if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.Enabled != nil && !*c.bidderInfo[bidder].Syncer.Enabled { 201 return nil, BidderEvaluation{Status: StatusBlockedByDisabledUsersync, Bidder: bidder, SyncerKey: syncer.Key()} 202 } 203 204 if privacy.GDPRInScope() && c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil && c.bidderInfo[bidder].Syncer.SkipWhen.GDPR { 205 return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} 206 } 207 208 if c.bidderInfo[bidder].Syncer != nil && c.bidderInfo[bidder].Syncer.SkipWhen != nil { 209 for _, gppSID := range c.bidderInfo[bidder].Syncer.SkipWhen.GPPSID { 210 if gppSID == GPPSID { 211 return nil, BidderEvaluation{Status: StatusBlockedByRegulationScope, Bidder: bidder, SyncerKey: syncer.Key()} 212 } 213 } 214 } 215 216 return syncer, BidderEvaluation{Status: StatusOK, Bidder: bidder, SyncerKey: syncer.Key()} 217 }