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  }