github.com/prebid/prebid-server@v0.275.0/firstpartydata/first_party_data.go (about)

     1  package firstpartydata
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  
     7  	"github.com/prebid/openrtb/v19/openrtb2"
     8  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
     9  
    10  	"github.com/prebid/prebid-server/errortypes"
    11  	"github.com/prebid/prebid-server/openrtb_ext"
    12  	"github.com/prebid/prebid-server/ortb"
    13  	"github.com/prebid/prebid-server/util/ptrutil"
    14  )
    15  
    16  const (
    17  	siteKey = "site"
    18  	appKey  = "app"
    19  	userKey = "user"
    20  	dataKey = "data"
    21  
    22  	userDataKey        = "userData"
    23  	appContentDataKey  = "appContentData"
    24  	siteContentDataKey = "siteContentData"
    25  )
    26  
    27  type ResolvedFirstPartyData struct {
    28  	Site *openrtb2.Site
    29  	App  *openrtb2.App
    30  	User *openrtb2.User
    31  }
    32  
    33  // ExtractGlobalFPD extracts request level FPD from the request and removes req.{site,app,user}.ext.data if exists
    34  func ExtractGlobalFPD(req *openrtb_ext.RequestWrapper) (map[string][]byte, error) {
    35  	fpdReqData := make(map[string][]byte, 3)
    36  
    37  	siteExt, err := req.GetSiteExt()
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  	refreshExt := false
    42  
    43  	if len(siteExt.GetExt()[dataKey]) > 0 {
    44  		newSiteExt := siteExt.GetExt()
    45  		fpdReqData[siteKey] = newSiteExt[dataKey]
    46  		delete(newSiteExt, dataKey)
    47  		siteExt.SetExt(newSiteExt)
    48  		refreshExt = true
    49  	}
    50  
    51  	appExt, err := req.GetAppExt()
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	if len(appExt.GetExt()[dataKey]) > 0 {
    56  		newAppExt := appExt.GetExt()
    57  		fpdReqData[appKey] = newAppExt[dataKey]
    58  		delete(newAppExt, dataKey)
    59  		appExt.SetExt(newAppExt)
    60  		refreshExt = true
    61  	}
    62  
    63  	userExt, err := req.GetUserExt()
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  	if len(userExt.GetExt()[dataKey]) > 0 {
    68  		newUserExt := userExt.GetExt()
    69  		fpdReqData[userKey] = newUserExt[dataKey]
    70  		delete(newUserExt, dataKey)
    71  		userExt.SetExt(newUserExt)
    72  		refreshExt = true
    73  	}
    74  	if refreshExt {
    75  		// need to keep site/app/user ext clean in case bidder is not in global fpd bidder list
    76  		// rebuild/resync the request in the request wrapper.
    77  		if err := req.RebuildRequest(); err != nil {
    78  			return nil, err
    79  		}
    80  	}
    81  
    82  	return fpdReqData, nil
    83  }
    84  
    85  // ExtractOpenRtbGlobalFPD extracts and deletes user.data and {app/site}.content.data from request
    86  func ExtractOpenRtbGlobalFPD(bidRequest *openrtb2.BidRequest) map[string][]openrtb2.Data {
    87  	openRtbGlobalFPD := make(map[string][]openrtb2.Data, 3)
    88  	if bidRequest.User != nil && len(bidRequest.User.Data) > 0 {
    89  		openRtbGlobalFPD[userDataKey] = bidRequest.User.Data
    90  		bidRequest.User.Data = nil
    91  	}
    92  
    93  	if bidRequest.Site != nil && bidRequest.Site.Content != nil && len(bidRequest.Site.Content.Data) > 0 {
    94  		openRtbGlobalFPD[siteContentDataKey] = bidRequest.Site.Content.Data
    95  		bidRequest.Site.Content.Data = nil
    96  	}
    97  
    98  	if bidRequest.App != nil && bidRequest.App.Content != nil && len(bidRequest.App.Content.Data) > 0 {
    99  		openRtbGlobalFPD[appContentDataKey] = bidRequest.App.Content.Data
   100  		bidRequest.App.Content.Data = nil
   101  	}
   102  
   103  	return openRtbGlobalFPD
   104  }
   105  
   106  // ResolveFPD consolidates First Party Data from different sources and returns valid FPD that will be applied to bidders later or returns errors
   107  func ResolveFPD(bidRequest *openrtb2.BidRequest, fpdBidderConfigData map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, biddersWithGlobalFPD []string) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) {
   108  	var errL []error
   109  
   110  	resolvedFpd := make(map[openrtb_ext.BidderName]*ResolvedFirstPartyData)
   111  
   112  	allBiddersTable := make(map[string]struct{})
   113  
   114  	if biddersWithGlobalFPD == nil {
   115  		// add all bidders in bidder configs to receive global data and bidder specific data
   116  		for bidderName := range fpdBidderConfigData {
   117  			if _, present := allBiddersTable[string(bidderName)]; !present {
   118  				allBiddersTable[string(bidderName)] = struct{}{}
   119  			}
   120  		}
   121  	} else {
   122  		// only bidders in global bidder list will receive global data and bidder specific data
   123  		for _, bidderName := range biddersWithGlobalFPD {
   124  			if _, present := allBiddersTable[string(bidderName)]; !present {
   125  				allBiddersTable[string(bidderName)] = struct{}{}
   126  			}
   127  		}
   128  	}
   129  
   130  	for bidderName := range allBiddersTable {
   131  		fpdConfig := fpdBidderConfigData[openrtb_ext.BidderName(bidderName)]
   132  
   133  		resolvedFpdConfig := &ResolvedFirstPartyData{}
   134  
   135  		newUser, err := resolveUser(fpdConfig, bidRequest.User, globalFPD, openRtbGlobalFPD, bidderName)
   136  		if err != nil {
   137  			errL = append(errL, err)
   138  		}
   139  		resolvedFpdConfig.User = newUser
   140  
   141  		newApp, err := resolveApp(fpdConfig, bidRequest.App, globalFPD, openRtbGlobalFPD, bidderName)
   142  		if err != nil {
   143  			errL = append(errL, err)
   144  		}
   145  		resolvedFpdConfig.App = newApp
   146  
   147  		newSite, err := resolveSite(fpdConfig, bidRequest.Site, globalFPD, openRtbGlobalFPD, bidderName)
   148  		if err != nil {
   149  			errL = append(errL, err)
   150  		}
   151  		resolvedFpdConfig.Site = newSite
   152  
   153  		if len(errL) == 0 {
   154  			resolvedFpd[openrtb_ext.BidderName(bidderName)] = resolvedFpdConfig
   155  		}
   156  	}
   157  	return resolvedFpd, errL
   158  }
   159  
   160  func resolveUser(fpdConfig *openrtb_ext.ORTB2, bidRequestUser *openrtb2.User, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.User, error) {
   161  	var fpdConfigUser json.RawMessage
   162  
   163  	if fpdConfig != nil && fpdConfig.User != nil {
   164  		fpdConfigUser = fpdConfig.User
   165  	}
   166  
   167  	if bidRequestUser == nil && fpdConfigUser == nil {
   168  		return nil, nil
   169  	}
   170  
   171  	var newUser *openrtb2.User
   172  	if bidRequestUser != nil {
   173  		newUser = ptrutil.Clone(bidRequestUser)
   174  	} else {
   175  		newUser = &openrtb2.User{}
   176  	}
   177  
   178  	//apply global fpd
   179  	if len(globalFPD[userKey]) > 0 {
   180  		extData := buildExtData(globalFPD[userKey])
   181  		if len(newUser.Ext) > 0 {
   182  			var err error
   183  			newUser.Ext, err = jsonpatch.MergePatch(newUser.Ext, extData)
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  		} else {
   188  			newUser.Ext = extData
   189  		}
   190  	}
   191  	if openRtbGlobalFPD != nil && len(openRtbGlobalFPD[userDataKey]) > 0 {
   192  		newUser.Data = openRtbGlobalFPD[userDataKey]
   193  	}
   194  	if fpdConfigUser != nil {
   195  		if err := mergeUser(newUser, fpdConfigUser); err != nil {
   196  			return nil, err
   197  		}
   198  	}
   199  
   200  	return newUser, nil
   201  }
   202  
   203  func mergeUser(v *openrtb2.User, overrideJSON json.RawMessage) error {
   204  	*v = *ortb.CloneUser(v)
   205  
   206  	// Track EXTs
   207  	// It's not necessary to track `ext` fields in array items because the array
   208  	// items will be replaced entirely with the override JSON, so no merge is required.
   209  	var ext, extGeo extMerger
   210  	ext.Track(&v.Ext)
   211  	if v.Geo != nil {
   212  		extGeo.Track(&v.Geo.Ext)
   213  	}
   214  
   215  	// Merge
   216  	if err := json.Unmarshal(overrideJSON, &v); err != nil {
   217  		return err
   218  	}
   219  
   220  	// Merge EXTs
   221  	if err := ext.Merge(); err != nil {
   222  		return err
   223  	}
   224  	if err := extGeo.Merge(); err != nil {
   225  		return err
   226  	}
   227  
   228  	return nil
   229  }
   230  
   231  func resolveSite(fpdConfig *openrtb_ext.ORTB2, bidRequestSite *openrtb2.Site, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.Site, error) {
   232  	var fpdConfigSite json.RawMessage
   233  
   234  	if fpdConfig != nil && fpdConfig.Site != nil {
   235  		fpdConfigSite = fpdConfig.Site
   236  	}
   237  
   238  	if bidRequestSite == nil && fpdConfigSite == nil {
   239  		return nil, nil
   240  	}
   241  	if bidRequestSite == nil && fpdConfigSite != nil {
   242  		return nil, &errortypes.BadInput{
   243  			Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object is not defined in request, but defined in FPD config", bidderName),
   244  		}
   245  	}
   246  
   247  	var newSite *openrtb2.Site
   248  	if bidRequestSite != nil {
   249  		newSite = ptrutil.Clone(bidRequestSite)
   250  	} else {
   251  		newSite = &openrtb2.Site{}
   252  	}
   253  
   254  	//apply global fpd
   255  	if len(globalFPD[siteKey]) > 0 {
   256  		extData := buildExtData(globalFPD[siteKey])
   257  		if len(newSite.Ext) > 0 {
   258  			var err error
   259  			newSite.Ext, err = jsonpatch.MergePatch(newSite.Ext, extData)
   260  			if err != nil {
   261  				return nil, err
   262  			}
   263  		} else {
   264  			newSite.Ext = extData
   265  		}
   266  	}
   267  	// apply global openRTB fpd if exists
   268  	if len(openRtbGlobalFPD) > 0 && len(openRtbGlobalFPD[siteContentDataKey]) > 0 {
   269  		if newSite.Content == nil {
   270  			newSite.Content = &openrtb2.Content{}
   271  		} else {
   272  			contentCopy := *newSite.Content
   273  			newSite.Content = &contentCopy
   274  		}
   275  		newSite.Content.Data = openRtbGlobalFPD[siteContentDataKey]
   276  	}
   277  	if fpdConfigSite != nil {
   278  		if err := mergeSite(newSite, fpdConfigSite, bidderName); err != nil {
   279  			return nil, err
   280  		}
   281  	}
   282  	return newSite, nil
   283  }
   284  
   285  func mergeSite(v *openrtb2.Site, overrideJSON json.RawMessage, bidderName string) error {
   286  	*v = *ortb.CloneSite(v)
   287  
   288  	// Track EXTs
   289  	// It's not necessary to track `ext` fields in array items because the array
   290  	// items will be replaced entirely with the override JSON, so no merge is required.
   291  	var ext, extPublisher, extContent, extContentProducer, extContentNetwork, extContentChannel extMerger
   292  	ext.Track(&v.Ext)
   293  	if v.Publisher != nil {
   294  		extPublisher.Track(&v.Publisher.Ext)
   295  	}
   296  	if v.Content != nil {
   297  		extContent.Track(&v.Content.Ext)
   298  	}
   299  	if v.Content != nil && v.Content.Producer != nil {
   300  		extContentProducer.Track(&v.Content.Producer.Ext)
   301  	}
   302  	if v.Content != nil && v.Content.Network != nil {
   303  		extContentNetwork.Track(&v.Content.Network.Ext)
   304  	}
   305  	if v.Content != nil && v.Content.Channel != nil {
   306  		extContentChannel.Track(&v.Content.Channel.Ext)
   307  	}
   308  
   309  	// Merge
   310  	if err := json.Unmarshal(overrideJSON, &v); err != nil {
   311  		return err
   312  	}
   313  
   314  	// Merge EXTs
   315  	if err := ext.Merge(); err != nil {
   316  		return err
   317  	}
   318  	if err := extPublisher.Merge(); err != nil {
   319  		return err
   320  	}
   321  	if err := extContent.Merge(); err != nil {
   322  		return err
   323  	}
   324  	if err := extContentProducer.Merge(); err != nil {
   325  		return err
   326  	}
   327  	if err := extContentNetwork.Merge(); err != nil {
   328  		return err
   329  	}
   330  	if err := extContentChannel.Merge(); err != nil {
   331  		return err
   332  	}
   333  
   334  	// Re-Validate Site
   335  	if v.ID == "" && v.Page == "" {
   336  		return &errortypes.BadInput{
   337  			Message: fmt.Sprintf("incorrect First Party Data for bidder %s: Site object cannot set empty page if req.site.id is empty", bidderName),
   338  		}
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  func resolveApp(fpdConfig *openrtb_ext.ORTB2, bidRequestApp *openrtb2.App, globalFPD map[string][]byte, openRtbGlobalFPD map[string][]openrtb2.Data, bidderName string) (*openrtb2.App, error) {
   345  	var fpdConfigApp json.RawMessage
   346  
   347  	if fpdConfig != nil {
   348  		fpdConfigApp = fpdConfig.App
   349  	}
   350  
   351  	if bidRequestApp == nil && fpdConfigApp == nil {
   352  		return nil, nil
   353  	}
   354  
   355  	if bidRequestApp == nil && fpdConfigApp != nil {
   356  		return nil, &errortypes.BadInput{
   357  			Message: fmt.Sprintf("incorrect First Party Data for bidder %s: App object is not defined in request, but defined in FPD config", bidderName),
   358  		}
   359  	}
   360  
   361  	var newApp *openrtb2.App
   362  	if bidRequestApp != nil {
   363  		newApp = ptrutil.Clone(bidRequestApp)
   364  	} else {
   365  		newApp = &openrtb2.App{}
   366  	}
   367  
   368  	//apply global fpd if exists
   369  	if len(globalFPD[appKey]) > 0 {
   370  		extData := buildExtData(globalFPD[appKey])
   371  		if len(newApp.Ext) > 0 {
   372  			var err error
   373  			newApp.Ext, err = jsonpatch.MergePatch(newApp.Ext, extData)
   374  			if err != nil {
   375  				return nil, err
   376  			}
   377  		} else {
   378  			newApp.Ext = extData
   379  		}
   380  	}
   381  
   382  	// apply global openRTB fpd if exists
   383  	if len(openRtbGlobalFPD) > 0 && len(openRtbGlobalFPD[appContentDataKey]) > 0 {
   384  		if newApp.Content == nil {
   385  			newApp.Content = &openrtb2.Content{}
   386  		} else {
   387  			contentCopy := *newApp.Content
   388  			newApp.Content = &contentCopy
   389  		}
   390  		newApp.Content.Data = openRtbGlobalFPD[appContentDataKey]
   391  	}
   392  
   393  	if fpdConfigApp != nil {
   394  		if err := mergeApp(newApp, fpdConfigApp); err != nil {
   395  			return nil, err
   396  		}
   397  	}
   398  
   399  	return newApp, nil
   400  }
   401  
   402  func mergeApp(v *openrtb2.App, overrideJSON json.RawMessage) error {
   403  	*v = *ortb.CloneApp(v)
   404  
   405  	// Track EXTs
   406  	// It's not necessary to track `ext` fields in array items because the array
   407  	// items will be replaced entirely with the override JSON, so no merge is required.
   408  	var ext, extPublisher, extContent, extContentProducer, extContentNetwork, extContentChannel extMerger
   409  	ext.Track(&v.Ext)
   410  	if v.Publisher != nil {
   411  		extPublisher.Track(&v.Publisher.Ext)
   412  	}
   413  	if v.Content != nil {
   414  		extContent.Track(&v.Content.Ext)
   415  	}
   416  	if v.Content != nil && v.Content.Producer != nil {
   417  		extContentProducer.Track(&v.Content.Producer.Ext)
   418  	}
   419  	if v.Content != nil && v.Content.Network != nil {
   420  		extContentNetwork.Track(&v.Content.Network.Ext)
   421  	}
   422  	if v.Content != nil && v.Content.Channel != nil {
   423  		extContentChannel.Track(&v.Content.Channel.Ext)
   424  	}
   425  
   426  	// Merge
   427  	if err := json.Unmarshal(overrideJSON, &v); err != nil {
   428  		return err
   429  	}
   430  
   431  	// Merge EXTs
   432  	if err := ext.Merge(); err != nil {
   433  		return err
   434  	}
   435  	if err := extPublisher.Merge(); err != nil {
   436  		return err
   437  	}
   438  	if err := extContent.Merge(); err != nil {
   439  		return err
   440  	}
   441  	if err := extContentProducer.Merge(); err != nil {
   442  		return err
   443  	}
   444  	if err := extContentNetwork.Merge(); err != nil {
   445  		return err
   446  	}
   447  	if err := extContentChannel.Merge(); err != nil {
   448  		return err
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  func buildExtData(data []byte) []byte {
   455  	res := make([]byte, 0, len(data)+len(`"{"data":}"`))
   456  	res = append(res, []byte(`{"data":`)...)
   457  	res = append(res, data...)
   458  	res = append(res, []byte(`}`)...)
   459  	return res
   460  }
   461  
   462  // ExtractBidderConfigFPD extracts bidder specific configs from req.ext.prebid.bidderconfig
   463  func ExtractBidderConfigFPD(reqExt *openrtb_ext.RequestExt) (map[openrtb_ext.BidderName]*openrtb_ext.ORTB2, error) {
   464  	fpd := make(map[openrtb_ext.BidderName]*openrtb_ext.ORTB2)
   465  	reqExtPrebid := reqExt.GetPrebid()
   466  	if reqExtPrebid != nil {
   467  		for _, bidderConfig := range reqExtPrebid.BidderConfigs {
   468  			for _, bidder := range bidderConfig.Bidders {
   469  				if _, present := fpd[openrtb_ext.BidderName(bidder)]; present {
   470  					//if bidder has duplicated config - throw an error
   471  					return nil, &errortypes.BadInput{
   472  						Message: fmt.Sprintf("multiple First Party Data bidder configs provided for bidder: %s", bidder),
   473  					}
   474  				}
   475  
   476  				fpdBidderData := &openrtb_ext.ORTB2{}
   477  
   478  				if bidderConfig.Config != nil && bidderConfig.Config.ORTB2 != nil {
   479  					if bidderConfig.Config.ORTB2.Site != nil {
   480  						fpdBidderData.Site = bidderConfig.Config.ORTB2.Site
   481  					}
   482  					if bidderConfig.Config.ORTB2.App != nil {
   483  						fpdBidderData.App = bidderConfig.Config.ORTB2.App
   484  					}
   485  					if bidderConfig.Config.ORTB2.User != nil {
   486  						fpdBidderData.User = bidderConfig.Config.ORTB2.User
   487  					}
   488  				}
   489  
   490  				fpd[openrtb_ext.BidderName(bidder)] = fpdBidderData
   491  			}
   492  		}
   493  		reqExtPrebid.BidderConfigs = nil
   494  		reqExt.SetPrebid(reqExtPrebid)
   495  	}
   496  	return fpd, nil
   497  }
   498  
   499  // ExtractFPDForBidders extracts FPD data from request if specified
   500  func ExtractFPDForBidders(req *openrtb_ext.RequestWrapper) (map[openrtb_ext.BidderName]*ResolvedFirstPartyData, []error) {
   501  	reqExt, err := req.GetRequestExt()
   502  	if err != nil {
   503  		return nil, []error{err}
   504  	}
   505  	if reqExt == nil || reqExt.GetPrebid() == nil {
   506  		return nil, nil
   507  	}
   508  	var biddersWithGlobalFPD []string
   509  
   510  	extPrebid := reqExt.GetPrebid()
   511  	if extPrebid.Data != nil {
   512  		biddersWithGlobalFPD = extPrebid.Data.Bidders
   513  		extPrebid.Data.Bidders = nil
   514  		reqExt.SetPrebid(extPrebid)
   515  	}
   516  
   517  	fbdBidderConfigData, err := ExtractBidderConfigFPD(reqExt)
   518  	if err != nil {
   519  		return nil, []error{err}
   520  	}
   521  
   522  	var globalFpd map[string][]byte
   523  	var openRtbGlobalFPD map[string][]openrtb2.Data
   524  
   525  	if biddersWithGlobalFPD != nil {
   526  		//global fpd data should not be extracted and removed from request if global bidder list is nil.
   527  		//Bidders that don't have any fpd config should receive request data as is
   528  		globalFpd, err = ExtractGlobalFPD(req)
   529  		if err != nil {
   530  			return nil, []error{err}
   531  		}
   532  		openRtbGlobalFPD = ExtractOpenRtbGlobalFPD(req.BidRequest)
   533  	}
   534  
   535  	return ResolveFPD(req.BidRequest, fbdBidderConfigData, globalFpd, openRtbGlobalFPD, biddersWithGlobalFPD)
   536  }