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  }