github.com/prebid/prebid-server/v2@v2.18.0/privacy/ccpa/policy.go (about)

     1  package ccpa
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	gpplib "github.com/prebid/go-gpp"
     8  	gppConstants "github.com/prebid/go-gpp/constants"
     9  	"github.com/prebid/openrtb/v20/openrtb2"
    10  	"github.com/prebid/prebid-server/v2/errortypes"
    11  	"github.com/prebid/prebid-server/v2/openrtb_ext"
    12  	gppPolicy "github.com/prebid/prebid-server/v2/privacy/gpp"
    13  )
    14  
    15  // Policy represents the CCPA regulatory information from an OpenRTB bid request.
    16  type Policy struct {
    17  	Consent       string
    18  	NoSaleBidders []string
    19  }
    20  
    21  // ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request.
    22  func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (Policy, error) {
    23  	var noSaleBidders []string
    24  	var gppSIDs []int8
    25  	var requestUSPrivacy string
    26  	var warn error
    27  
    28  	if req == nil || req.BidRequest == nil {
    29  		return Policy{}, nil
    30  	}
    31  
    32  	if req.BidRequest.Regs != nil {
    33  		requestUSPrivacy = req.BidRequest.Regs.USPrivacy
    34  		gppSIDs = req.BidRequest.Regs.GPPSID
    35  	}
    36  
    37  	consent, err := SelectCCPAConsent(requestUSPrivacy, gpp, gppSIDs)
    38  	if err != nil {
    39  		warn = &errortypes.Warning{
    40  			Message:     "regs.us_privacy consent does not match uspv1 in GPP, using regs.gpp",
    41  			WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
    42  	}
    43  
    44  	if consent == "" {
    45  		// Read consent from request.regs.ext
    46  		regsExt, err := req.GetRegExt()
    47  		if err != nil {
    48  			return Policy{}, fmt.Errorf("error reading request.regs.ext: %s", err)
    49  		}
    50  		if regsExt != nil {
    51  			consent = regsExt.GetUSPrivacy()
    52  		}
    53  	}
    54  	// Read no sale bidders from request.ext.prebid
    55  	reqExt, err := req.GetRequestExt()
    56  	if err != nil {
    57  		return Policy{}, fmt.Errorf("error reading request.ext: %s", err)
    58  	}
    59  	reqPrebid := reqExt.GetPrebid()
    60  	if reqPrebid != nil {
    61  		noSaleBidders = reqPrebid.NoSale
    62  	}
    63  
    64  	return Policy{consent, noSaleBidders}, warn
    65  }
    66  
    67  func ReadFromRequest(req *openrtb2.BidRequest) (Policy, error) {
    68  	var gpp gpplib.GppContainer
    69  	if req != nil && req.Regs != nil && len(req.Regs.GPP) > 0 {
    70  		gpp, _ = gpplib.Parse(req.Regs.GPP)
    71  	}
    72  
    73  	return ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: req}, gpp)
    74  }
    75  
    76  // Write mutates an OpenRTB bid request with the CCPA regulatory information.
    77  func (p Policy) Write(req *openrtb_ext.RequestWrapper) error {
    78  	if req == nil {
    79  		return nil
    80  	}
    81  
    82  	regsExt, err := req.GetRegExt()
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	reqExt, err := req.GetRequestExt()
    88  	if err != nil {
    89  		return err
    90  	}
    91  
    92  	regsExt.SetUSPrivacy(p.Consent)
    93  	setPrebidNoSale(p.NoSaleBidders, reqExt)
    94  	return nil
    95  }
    96  
    97  func SelectCCPAConsent(requestUSPrivacy string, gpp gpplib.GppContainer, gppSIDs []int8) (string, error) {
    98  	var consent string
    99  	var err error
   100  
   101  	if len(gpp.SectionTypes) > 0 {
   102  		if gppPolicy.IsSIDInList(gppSIDs, gppConstants.SectionUSPV1) {
   103  			if i := gppPolicy.IndexOfSID(gpp, gppConstants.SectionUSPV1); i >= 0 {
   104  				consent = gpp.Sections[i].GetValue()
   105  			}
   106  		}
   107  	}
   108  
   109  	if requestUSPrivacy != "" {
   110  		if consent == "" {
   111  			consent = requestUSPrivacy
   112  		} else if consent != requestUSPrivacy {
   113  			err = errors.New("request.us_privacy consent does not match uspv1")
   114  		}
   115  	}
   116  
   117  	return consent, err
   118  }
   119  
   120  func setPrebidNoSale(noSaleBidders []string, ext *openrtb_ext.RequestExt) {
   121  	if len(noSaleBidders) == 0 {
   122  		setPrebidNoSaleClear(ext)
   123  	} else {
   124  		setPrebidNoSaleWrite(noSaleBidders, ext)
   125  	}
   126  }
   127  
   128  func setPrebidNoSaleClear(ext *openrtb_ext.RequestExt) {
   129  	prebid := ext.GetPrebid()
   130  	if prebid == nil {
   131  		return
   132  	}
   133  
   134  	// Remove no sale member
   135  	prebid.NoSale = []string{}
   136  	ext.SetPrebid(prebid)
   137  }
   138  
   139  func setPrebidNoSaleWrite(noSaleBidders []string, ext *openrtb_ext.RequestExt) {
   140  	if ext == nil {
   141  		// This should hopefully not be possible. The only caller insures that this has been initialized
   142  		return
   143  	}
   144  
   145  	prebid := ext.GetPrebid()
   146  	if prebid == nil {
   147  		prebid = &openrtb_ext.ExtRequestPrebid{}
   148  	}
   149  	prebid.NoSale = noSaleBidders
   150  	ext.SetPrebid(prebid)
   151  }