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