github.com/prebid/prebid-server/v2@v2.18.0/exchange/utils.go (about) 1 package exchange 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "math/rand" 10 "strings" 11 12 "github.com/prebid/prebid-server/v2/ortb" 13 14 "github.com/prebid/go-gdpr/vendorconsent" 15 gpplib "github.com/prebid/go-gpp" 16 gppConstants "github.com/prebid/go-gpp/constants" 17 "github.com/prebid/openrtb/v20/openrtb2" 18 19 "github.com/prebid/prebid-server/v2/config" 20 "github.com/prebid/prebid-server/v2/errortypes" 21 "github.com/prebid/prebid-server/v2/firstpartydata" 22 "github.com/prebid/prebid-server/v2/gdpr" 23 "github.com/prebid/prebid-server/v2/metrics" 24 "github.com/prebid/prebid-server/v2/openrtb_ext" 25 "github.com/prebid/prebid-server/v2/privacy" 26 "github.com/prebid/prebid-server/v2/privacy/ccpa" 27 "github.com/prebid/prebid-server/v2/privacy/lmt" 28 "github.com/prebid/prebid-server/v2/schain" 29 "github.com/prebid/prebid-server/v2/stored_responses" 30 "github.com/prebid/prebid-server/v2/util/jsonutil" 31 "github.com/prebid/prebid-server/v2/util/ptrutil" 32 ) 33 34 var errInvalidRequestExt = errors.New("request.ext is invalid") 35 36 var channelTypeMap = map[metrics.RequestType]config.ChannelType{ 37 metrics.ReqTypeAMP: config.ChannelAMP, 38 metrics.ReqTypeORTB2App: config.ChannelApp, 39 metrics.ReqTypeVideo: config.ChannelVideo, 40 metrics.ReqTypeORTB2Web: config.ChannelWeb, 41 metrics.ReqTypeORTB2DOOH: config.ChannelDOOH, 42 } 43 44 const unknownBidder string = "" 45 46 type requestSplitter struct { 47 bidderToSyncerKey map[string]string 48 me metrics.MetricsEngine 49 privacyConfig config.Privacy 50 gdprPermsBuilder gdpr.PermissionsBuilder 51 hostSChainNode *openrtb2.SupplyChainNode 52 bidderInfo config.BidderInfos 53 } 54 55 // cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is: 56 // 57 // 1. BidRequest.Imp[].Ext will only contain the "prebid" field and a "bidder" field which has the params for the intended Bidder. 58 // 2. Every BidRequest.Imp[] requested Bids from the Bidder who keys it. 59 // 3. BidRequest.User.BuyerUID will be set to that Bidder's ID. 60 func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, 61 auctionReq AuctionRequest, 62 requestExt *openrtb_ext.ExtRequest, 63 gdprSignal gdpr.Signal, 64 gdprEnforced bool, 65 bidAdjustmentFactors map[string]float64, 66 ) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) { 67 req := auctionReq.BidRequestWrapper 68 69 requestAliases, requestAliasesGVLIDs, errs := getRequestAliases(req) 70 if len(errs) > 0 { 71 return 72 } 73 74 allowedBidderRequests = make([]BidderRequest, 0) 75 76 bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses) 77 78 impsByBidder, err := splitImps(req.BidRequest.Imp) 79 if err != nil { 80 errs = []error{err} 81 return 82 } 83 84 var allBidderRequests []BidderRequest 85 var allBidderRequestErrs []error 86 allBidderRequests, allBidderRequestErrs = getAuctionBidderRequests(auctionReq, requestExt, rs.bidderToSyncerKey, impsByBidder, requestAliases, rs.hostSChainNode) 87 if allBidderRequestErrs != nil { 88 errs = append(errs, allBidderRequestErrs...) 89 } 90 91 bidderNameToBidderReq := buildBidResponseRequest(req.BidRequest, bidderImpWithBidResp, requestAliases, auctionReq.BidderImpReplaceImpID) 92 //this function should be executed after getAuctionBidderRequests 93 allBidderRequests = mergeBidderRequests(allBidderRequests, bidderNameToBidderReq) 94 95 var gpp gpplib.GppContainer 96 if req.BidRequest.Regs != nil && len(req.BidRequest.Regs.GPP) > 0 { 97 var gppErrs []error 98 gpp, gppErrs = gpplib.Parse(req.BidRequest.Regs.GPP) 99 if len(gppErrs) > 0 { 100 errs = append(errs, gppErrs[0]) 101 } 102 } 103 104 if auctionReq.Account.PriceFloors.IsAdjustForBidAdjustmentEnabled() { 105 //Apply BidAdjustmentFactor to imp.BidFloor 106 applyBidAdjustmentToFloor(allBidderRequests, bidAdjustmentFactors) 107 } 108 109 consent, err := getConsent(req, gpp) 110 if err != nil { 111 errs = append(errs, err) 112 } 113 114 ccpaEnforcer, err := extractCCPA(req.BidRequest, rs.privacyConfig, &auctionReq.Account, requestAliases, channelTypeMap[auctionReq.LegacyLabels.RType], gpp) 115 if err != nil { 116 errs = append(errs, err) 117 } 118 119 lmtEnforcer := extractLMT(req.BidRequest, rs.privacyConfig) 120 121 // request level privacy policies 122 coppa := req.BidRequest.Regs != nil && req.BidRequest.Regs.COPPA == 1 123 lmt := lmtEnforcer.ShouldEnforce(unknownBidder) 124 125 privacyLabels.CCPAProvided = ccpaEnforcer.CanEnforce() 126 privacyLabels.CCPAEnforced = ccpaEnforcer.ShouldEnforce(unknownBidder) 127 privacyLabels.COPPAEnforced = coppa 128 privacyLabels.LMTEnforced = lmt 129 130 var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{} 131 132 if gdprEnforced { 133 privacyLabels.GDPREnforced = true 134 parsedConsent, err := vendorconsent.ParseString(consent) 135 if err == nil { 136 version := int(parsedConsent.Version()) 137 privacyLabels.GDPRTCFVersion = metrics.TCFVersionToValue(version) 138 } 139 140 gdprRequestInfo := gdpr.RequestInfo{ 141 AliasGVLIDs: requestAliasesGVLIDs, 142 Consent: consent, 143 GDPRSignal: gdprSignal, 144 PublisherID: auctionReq.LegacyLabels.PubID, 145 } 146 gdprPerms = rs.gdprPermsBuilder(auctionReq.TCF2Config, gdprRequestInfo) 147 } 148 149 // bidder level privacy policies 150 for _, bidderRequest := range allBidderRequests { 151 // fetchBids activity 152 scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()} 153 fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req)) 154 if !fetchBidsActivityAllowed { 155 // skip the call to a bidder if fetchBids activity is not allowed 156 // do not add this bidder to allowedBidderRequests 157 continue 158 } 159 160 var auctionPermissions gdpr.AuctionPermissions 161 var gdprErr error 162 163 if gdprEnforced { 164 auctionPermissions, gdprErr = gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) 165 if !auctionPermissions.AllowBidRequest { 166 // auction request is not permitted by GDPR 167 // do not add this bidder to allowedBidderRequests 168 rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName) 169 continue 170 } 171 } 172 173 ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config} 174 175 // FPD should be applied before policies, otherwise it overrides policies and activities restricted data 176 applyFPD(auctionReq.FirstPartyData, bidderRequest) 177 178 reqWrapper := &openrtb_ext.RequestWrapper{ 179 BidRequest: ortb.CloneBidRequestPartial(bidderRequest.BidRequest), 180 } 181 182 passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req)) 183 buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != "" 184 buyerUIDRemoved := false 185 if !passIDActivityAllowed { 186 privacy.ScrubUserFPD(reqWrapper) 187 buyerUIDRemoved = true 188 } else { 189 // run existing policies (GDPR, CCPA, COPPA, LMT) 190 // potentially block passing IDs based on GDPR 191 if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) { 192 privacy.ScrubGdprID(reqWrapper) 193 buyerUIDRemoved = true 194 } 195 // potentially block passing IDs based on CCPA 196 if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { 197 privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) 198 buyerUIDRemoved = true 199 } 200 } 201 if buyerUIDSet && buyerUIDRemoved { 202 rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName) 203 } 204 205 passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req)) 206 if !passGeoActivityAllowed { 207 privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) 208 } else { 209 // run existing policies (GDPR, CCPA, COPPA, LMT) 210 // potentially block passing geo based on GDPR 211 if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassGeo) { 212 privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf) 213 } 214 // potentially block passing geo based on CCPA 215 if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) { 216 privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false) 217 } 218 } 219 220 if lmt || coppa { 221 privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa) 222 } 223 224 passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req)) 225 if !passTIDAllowed { 226 privacy.ScrubTID(reqWrapper) 227 } 228 229 err := reqWrapper.RebuildRequest() 230 if err != nil { 231 errs = append(errs, err) 232 } 233 bidderRequest.BidRequest = reqWrapper.BidRequest 234 235 allowedBidderRequests = append(allowedBidderRequests, bidderRequest) 236 237 // GPP downgrade: always downgrade unless we can confirm GPP is supported 238 if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) { 239 setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) 240 setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) 241 } 242 } 243 244 return 245 } 246 247 func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool { 248 binfo, defined := bidderInfo[bidder] 249 250 if !defined || binfo.OpenRTB == nil { 251 return true 252 } 253 254 return !binfo.OpenRTB.GPPSupported 255 } 256 257 func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.ChannelType) bool { 258 if accountEnabled := account.CCPA.EnabledForChannelType(requestType); accountEnabled != nil { 259 return *accountEnabled 260 } 261 return privacyConfig.CCPA.Enforce 262 } 263 264 func extractCCPA(orig *openrtb2.BidRequest, privacyConfig config.Privacy, account *config.Account, requestAliases map[string]string, requestType config.ChannelType, gpp gpplib.GppContainer) (privacy.PolicyEnforcer, error) { 265 // Quick extra wrapper until RequestWrapper makes its way into CleanRequests 266 ccpaPolicy, err := ccpa.ReadFromRequestWrapper(&openrtb_ext.RequestWrapper{BidRequest: orig}, gpp) 267 if err != nil { 268 return privacy.NilPolicyEnforcer{}, err 269 } 270 271 validBidders := GetValidBidders(requestAliases) 272 ccpaParsedPolicy, err := ccpaPolicy.Parse(validBidders) 273 if err != nil { 274 return privacy.NilPolicyEnforcer{}, err 275 } 276 277 ccpaEnforcer := privacy.EnabledPolicyEnforcer{ 278 Enabled: ccpaEnabled(account, privacyConfig, requestType), 279 PolicyEnforcer: ccpaParsedPolicy, 280 } 281 return ccpaEnforcer, nil 282 } 283 284 func extractLMT(orig *openrtb2.BidRequest, privacyConfig config.Privacy) privacy.PolicyEnforcer { 285 return privacy.EnabledPolicyEnforcer{ 286 Enabled: privacyConfig.LMT.Enforce, 287 PolicyEnforcer: lmt.ReadFromRequest(orig), 288 } 289 } 290 291 func ExtractReqExtBidderParamsMap(bidRequest *openrtb2.BidRequest) (map[string]json.RawMessage, error) { 292 if bidRequest == nil { 293 return nil, errors.New("error bidRequest should not be nil") 294 } 295 296 reqExt := &openrtb_ext.ExtRequest{} 297 if len(bidRequest.Ext) > 0 { 298 err := jsonutil.Unmarshal(bidRequest.Ext, &reqExt) 299 if err != nil { 300 return nil, fmt.Errorf("error decoding Request.ext : %s", err.Error()) 301 } 302 } 303 304 if reqExt.Prebid.BidderParams == nil { 305 return nil, nil 306 } 307 308 var bidderParams map[string]json.RawMessage 309 err := jsonutil.Unmarshal(reqExt.Prebid.BidderParams, &bidderParams) 310 if err != nil { 311 return nil, err 312 } 313 314 return bidderParams, nil 315 } 316 317 func getAuctionBidderRequests(auctionRequest AuctionRequest, 318 requestExt *openrtb_ext.ExtRequest, 319 bidderToSyncerKey map[string]string, 320 impsByBidder map[string][]openrtb2.Imp, 321 requestAliases map[string]string, 322 hostSChainNode *openrtb2.SupplyChainNode) ([]BidderRequest, []error) { 323 324 bidderRequests := make([]BidderRequest, 0, len(impsByBidder)) 325 req := auctionRequest.BidRequestWrapper 326 explicitBuyerUIDs, err := extractBuyerUIDs(req.BidRequest.User) 327 if err != nil { 328 return nil, []error{err} 329 } 330 331 bidderParamsInReqExt, err := ExtractReqExtBidderParamsMap(req.BidRequest) 332 if err != nil { 333 return nil, []error{err} 334 } 335 336 sChainWriter, err := schain.NewSChainWriter(requestExt, hostSChainNode) 337 if err != nil { 338 return nil, []error{err} 339 } 340 341 lowerCaseExplicitBuyerUIDs := make(map[string]string) 342 for bidder, uid := range explicitBuyerUIDs { 343 lowerKey := strings.ToLower(bidder) 344 lowerCaseExplicitBuyerUIDs[lowerKey] = uid 345 } 346 347 var errs []error 348 for bidder, imps := range impsByBidder { 349 coreBidder, isRequestAlias := resolveBidder(bidder, requestAliases) 350 351 reqCopy := *req.BidRequest 352 reqCopy.Imp = imps 353 354 sChainWriter.Write(&reqCopy, bidder) 355 356 reqCopy.Ext, err = buildRequestExtForBidder(bidder, req.BidRequest.Ext, requestExt, bidderParamsInReqExt, auctionRequest.Account.AlternateBidderCodes) 357 if err != nil { 358 return nil, []error{err} 359 } 360 361 if err := removeUnpermissionedEids(&reqCopy, bidder, requestExt); err != nil { 362 errs = append(errs, fmt.Errorf("unable to enforce request.ext.prebid.data.eidpermissions because %v", err)) 363 continue 364 } 365 366 bidderRequest := BidderRequest{ 367 BidderName: openrtb_ext.BidderName(bidder), 368 BidderCoreName: coreBidder, 369 IsRequestAlias: isRequestAlias, 370 BidRequest: &reqCopy, 371 BidderLabels: metrics.AdapterLabels{ 372 Source: auctionRequest.LegacyLabels.Source, 373 RType: auctionRequest.LegacyLabels.RType, 374 Adapter: coreBidder, 375 PubID: auctionRequest.LegacyLabels.PubID, 376 CookieFlag: auctionRequest.LegacyLabels.CookieFlag, 377 AdapterBids: metrics.AdapterBidPresent, 378 }, 379 } 380 381 syncerKey := bidderToSyncerKey[string(coreBidder)] 382 if hadSync := prepareUser(&reqCopy, bidder, syncerKey, lowerCaseExplicitBuyerUIDs, auctionRequest.UserSyncs); !hadSync && req.BidRequest.App == nil { 383 bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagNo 384 } else { 385 bidderRequest.BidderLabels.CookieFlag = metrics.CookieFlagYes 386 } 387 388 bidderRequests = append(bidderRequests, bidderRequest) 389 } 390 return bidderRequests, errs 391 } 392 393 func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, requestExtParsed *openrtb_ext.ExtRequest, bidderParamsInReqExt map[string]json.RawMessage, cfgABC *openrtb_ext.ExtAlternateBidderCodes) (json.RawMessage, error) { 394 // Resolve alternatebiddercode for current bidder 395 var reqABC *openrtb_ext.ExtAlternateBidderCodes 396 if len(requestExt) != 0 && requestExtParsed != nil && requestExtParsed.Prebid.AlternateBidderCodes != nil { 397 reqABC = requestExtParsed.Prebid.AlternateBidderCodes 398 } 399 alternateBidderCodes := buildRequestExtAlternateBidderCodes(bidder, cfgABC, reqABC) 400 401 if (len(requestExt) == 0 || requestExtParsed == nil) && alternateBidderCodes == nil { 402 return nil, nil 403 } 404 405 // Resolve Bidder Params 406 var bidderParams json.RawMessage 407 if bidderParamsInReqExt != nil { 408 bidderParams = bidderParamsInReqExt[bidder] 409 } 410 411 // Copy Allowed Fields 412 // Per: https://docs.prebid.org/prebid-server/endpoints/openrtb2/pbs-endpoint-auction.html#prebid-server-ortb2-extension-summary 413 prebid := openrtb_ext.ExtRequestPrebid{ 414 BidderParams: bidderParams, 415 AlternateBidderCodes: alternateBidderCodes, 416 } 417 418 if requestExtParsed != nil { 419 prebid.Channel = requestExtParsed.Prebid.Channel 420 prebid.CurrencyConversions = requestExtParsed.Prebid.CurrencyConversions 421 prebid.Debug = requestExtParsed.Prebid.Debug 422 prebid.Integration = requestExtParsed.Prebid.Integration 423 prebid.MultiBid = buildRequestExtMultiBid(bidder, requestExtParsed.Prebid.MultiBid, alternateBidderCodes) 424 prebid.Sdk = requestExtParsed.Prebid.Sdk 425 prebid.Server = requestExtParsed.Prebid.Server 426 } 427 428 // Marshal New Prebid Object 429 prebidJson, err := jsonutil.Marshal(prebid) 430 if err != nil { 431 return nil, err 432 } 433 434 // Parse Existing Ext 435 extMap := make(map[string]json.RawMessage) 436 if len(requestExt) != 0 { 437 if err := jsonutil.Unmarshal(requestExt, &extMap); err != nil { 438 return nil, err 439 } 440 } 441 442 // Update Ext With Prebid Json 443 if bytes.Equal(prebidJson, []byte(`{}`)) { 444 delete(extMap, "prebid") 445 } else { 446 extMap["prebid"] = prebidJson 447 } 448 449 if len(extMap) > 0 { 450 return jsonutil.Marshal(extMap) 451 } else { 452 return nil, nil 453 } 454 } 455 456 func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { 457 458 if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil { 459 return altBidderCodes 460 } 461 462 if altBidderCodes := copyExtAlternateBidderCodes(bidder, accABC); altBidderCodes != nil { 463 return altBidderCodes 464 } 465 466 return nil 467 } 468 469 func copyExtAlternateBidderCodes(bidder string, altBidderCodes *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes { 470 if altBidderCodes != nil { 471 alternateBidderCodes := &openrtb_ext.ExtAlternateBidderCodes{ 472 Enabled: altBidderCodes.Enabled, 473 } 474 475 if bidderCodes, ok := altBidderCodes.IsBidderInAlternateBidderCodes(bidder); ok { 476 alternateBidderCodes.Bidders = map[string]openrtb_ext.ExtAdapterAlternateBidderCodes{ 477 bidder: bidderCodes, 478 } 479 } 480 481 return alternateBidderCodes 482 } 483 return nil 484 } 485 486 func buildRequestExtMultiBid(adapter string, reqMultiBid []*openrtb_ext.ExtMultiBid, adapterABC *openrtb_ext.ExtAlternateBidderCodes) []*openrtb_ext.ExtMultiBid { 487 adapterMultiBid := make([]*openrtb_ext.ExtMultiBid, 0) 488 for _, multiBid := range reqMultiBid { 489 if multiBid.Bidder != "" { 490 if strings.ToLower(multiBid.Bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(multiBid.Bidder), adapterABC) { 491 adapterMultiBid = append(adapterMultiBid, multiBid) 492 } 493 } else { 494 for _, bidder := range multiBid.Bidders { 495 if strings.ToLower(bidder) == adapter || isBidderInExtAlternateBidderCodes(adapter, strings.ToLower(bidder), adapterABC) { 496 adapterMultiBid = append(adapterMultiBid, &openrtb_ext.ExtMultiBid{ 497 Bidders: []string{bidder}, 498 MaxBids: multiBid.MaxBids, 499 }) 500 } 501 } 502 } 503 } 504 505 if len(adapterMultiBid) > 0 { 506 return adapterMultiBid 507 } 508 509 return nil 510 } 511 512 func isBidderInExtAlternateBidderCodes(adapter, currentMultiBidBidder string, adapterABC *openrtb_ext.ExtAlternateBidderCodes) bool { 513 if adapterABC != nil { 514 if abc, ok := adapterABC.Bidders[adapter]; ok { 515 for _, bidder := range abc.AllowedBidderCodes { 516 if bidder == "*" || bidder == currentMultiBidBidder { 517 return true 518 } 519 } 520 } 521 } 522 return false 523 } 524 525 // extractBuyerUIDs parses the values from user.ext.prebid.buyeruids, and then deletes those values from the ext. 526 // This prevents a Bidder from using these values to figure out who else is involved in the Auction. 527 func extractBuyerUIDs(user *openrtb2.User) (map[string]string, error) { 528 if user == nil { 529 return nil, nil 530 } 531 if len(user.Ext) == 0 { 532 return nil, nil 533 } 534 535 var userExt openrtb_ext.ExtUser 536 if err := jsonutil.Unmarshal(user.Ext, &userExt); err != nil { 537 return nil, err 538 } 539 if userExt.Prebid == nil { 540 return nil, nil 541 } 542 543 // The API guarantees that user.ext.prebid.buyeruids exists and has at least one ID defined, 544 // as long as user.ext.prebid exists. 545 buyerUIDs := userExt.Prebid.BuyerUIDs 546 userExt.Prebid = nil 547 548 // Remarshal (instead of removing) if the ext has other known fields 549 if userExt.Consent != "" || len(userExt.Eids) > 0 { 550 if newUserExtBytes, err := jsonutil.Marshal(userExt); err != nil { 551 return nil, err 552 } else { 553 user.Ext = newUserExtBytes 554 } 555 } else { 556 user.Ext = nil 557 } 558 return buyerUIDs, nil 559 } 560 561 // splitImps takes a list of Imps and returns a map of imps which have been sanitized for each bidder. 562 // 563 // For example, suppose imps has two elements. One goes to rubicon, while the other goes to appnexus and index. 564 // The returned map will have three keys: rubicon, appnexus, and index--each with one Imp. 565 // The "imp.ext" value of the appnexus Imp will only contain the "prebid" values, and "appnexus" value at the "bidder" key. 566 // The "imp.ext" value of the rubicon Imp will only contain the "prebid" values, and "rubicon" value at the "bidder" key. 567 // 568 // The goal here is so that Bidders only get Imps and Imp.Ext values which are intended for them. 569 func splitImps(imps []openrtb2.Imp) (map[string][]openrtb2.Imp, error) { 570 bidderImps := make(map[string][]openrtb2.Imp) 571 572 for i, imp := range imps { 573 var impExt map[string]json.RawMessage 574 if err := jsonutil.UnmarshalValid(imp.Ext, &impExt); err != nil { 575 return nil, fmt.Errorf("invalid json for imp[%d]: %v", i, err) 576 } 577 578 var impExtPrebid map[string]json.RawMessage 579 if impExtPrebidJSON, exists := impExt[openrtb_ext.PrebidExtKey]; exists { 580 // validation already performed by impExt unmarshal. no error is possible here, proven by tests. 581 jsonutil.Unmarshal(impExtPrebidJSON, &impExtPrebid) 582 } 583 584 var impExtPrebidBidder map[string]json.RawMessage 585 if impExtPrebidBidderJSON, exists := impExtPrebid[openrtb_ext.PrebidExtBidderKey]; exists { 586 // validation already performed by impExt unmarshal. no error is possible here, proven by tests. 587 jsonutil.Unmarshal(impExtPrebidBidderJSON, &impExtPrebidBidder) 588 } 589 590 sanitizedImpExt, err := createSanitizedImpExt(impExt, impExtPrebid) 591 if err != nil { 592 return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: %v", i, err) 593 } 594 595 for bidder, bidderExt := range impExtPrebidBidder { 596 impCopy := imp 597 598 sanitizedImpExt[openrtb_ext.PrebidExtBidderKey] = bidderExt 599 600 impExtJSON, err := jsonutil.Marshal(sanitizedImpExt) 601 if err != nil { 602 return nil, fmt.Errorf("unable to remove other bidder fields for imp[%d]: cannot marshal ext: %v", i, err) 603 } 604 impCopy.Ext = impExtJSON 605 606 bidderImps[bidder] = append(bidderImps[bidder], impCopy) 607 } 608 } 609 610 return bidderImps, nil 611 } 612 613 var allowedImpExtFields = map[string]interface{}{ 614 openrtb_ext.AuctionEnvironmentKey: struct{}{}, 615 openrtb_ext.FirstPartyDataExtKey: struct{}{}, 616 openrtb_ext.FirstPartyDataContextExtKey: struct{}{}, 617 openrtb_ext.GPIDKey: struct{}{}, 618 openrtb_ext.SKAdNExtKey: struct{}{}, 619 openrtb_ext.TIDKey: struct{}{}, 620 } 621 622 var allowedImpExtPrebidFields = map[string]interface{}{ 623 openrtb_ext.IsRewardedInventoryKey: struct{}{}, 624 openrtb_ext.OptionsKey: struct{}{}, 625 } 626 627 func createSanitizedImpExt(impExt, impExtPrebid map[string]json.RawMessage) (map[string]json.RawMessage, error) { 628 sanitizedImpExt := make(map[string]json.RawMessage, 6) 629 sanitizedImpPrebidExt := make(map[string]json.RawMessage, 2) 630 631 // copy allowed imp[].ext.prebid fields 632 for k := range allowedImpExtPrebidFields { 633 if v, exists := impExtPrebid[k]; exists { 634 sanitizedImpPrebidExt[k] = v 635 } 636 } 637 638 // marshal sanitized imp[].ext.prebid 639 if len(sanitizedImpPrebidExt) > 0 { 640 if impExtPrebidJSON, err := jsonutil.Marshal(sanitizedImpPrebidExt); err == nil { 641 sanitizedImpExt[openrtb_ext.PrebidExtKey] = impExtPrebidJSON 642 } else { 643 return nil, fmt.Errorf("cannot marshal ext.prebid: %v", err) 644 } 645 } 646 647 // copy reserved imp[].ext fields known to not be bidder names 648 for k := range allowedImpExtFields { 649 if v, exists := impExt[k]; exists { 650 sanitizedImpExt[k] = v 651 } 652 } 653 654 return sanitizedImpExt, nil 655 } 656 657 // prepareUser changes req.User so that it's ready for the given bidder. 658 // This *will* mutate the request, but will *not* mutate any objects nested inside it. 659 // 660 // In this function, "givenBidder" may or may not be an alias. "coreBidder" must *not* be an alias. 661 // It returns true if a Cookie User Sync existed, and false otherwise. 662 func prepareUser(req *openrtb2.BidRequest, givenBidder, syncerKey string, explicitBuyerUIDs map[string]string, usersyncs IdFetcher) bool { 663 cookieId, hadCookie, _ := usersyncs.GetUID(syncerKey) 664 665 if id, ok := explicitBuyerUIDs[strings.ToLower(givenBidder)]; ok { 666 req.User = copyWithBuyerUID(req.User, id) 667 } else if hadCookie { 668 req.User = copyWithBuyerUID(req.User, cookieId) 669 } 670 671 return hadCookie 672 } 673 674 // copyWithBuyerUID either overwrites the BuyerUID property on user with the argument, or returns 675 // a new (empty) User with the BuyerUID already set. 676 func copyWithBuyerUID(user *openrtb2.User, buyerUID string) *openrtb2.User { 677 if user == nil { 678 return &openrtb2.User{ 679 BuyerUID: buyerUID, 680 } 681 } 682 if user.BuyerUID == "" { 683 clone := *user 684 clone.BuyerUID = buyerUID 685 return &clone 686 } 687 return user 688 } 689 690 // removeUnpermissionedEids modifies the request to remove any request.user.ext.eids not permissions for the specific bidder 691 func removeUnpermissionedEids(request *openrtb2.BidRequest, bidder string, requestExt *openrtb_ext.ExtRequest) error { 692 // ensure request might have eids (as much as we can check before unmarshalling) 693 if request.User == nil || len(request.User.Ext) == 0 { 694 return nil 695 } 696 697 // ensure request has eid permissions to enforce 698 if requestExt == nil || requestExt.Prebid.Data == nil || len(requestExt.Prebid.Data.EidPermissions) == 0 { 699 return nil 700 } 701 702 // low level unmarshal to preserve other request.user.ext values. prebid server is non-destructive. 703 var userExt map[string]json.RawMessage 704 if err := jsonutil.Unmarshal(request.User.Ext, &userExt); err != nil { 705 return err 706 } 707 708 eidsJSON, eidsSpecified := userExt["eids"] 709 if !eidsSpecified { 710 return nil 711 } 712 713 var eids []openrtb2.EID 714 if err := jsonutil.Unmarshal(eidsJSON, &eids); err != nil { 715 return err 716 } 717 718 // exit early if there are no eids (empty array) 719 if len(eids) == 0 { 720 return nil 721 } 722 723 // translate eid permissions to a map for quick lookup 724 eidRules := make(map[string][]string) 725 for _, p := range requestExt.Prebid.Data.EidPermissions { 726 eidRules[p.Source] = p.Bidders 727 } 728 729 eidsAllowed := make([]openrtb2.EID, 0, len(eids)) 730 for _, eid := range eids { 731 allowed := false 732 if rule, hasRule := eidRules[eid.Source]; hasRule { 733 for _, ruleBidder := range rule { 734 if ruleBidder == "*" || strings.EqualFold(ruleBidder, bidder) { 735 allowed = true 736 break 737 } 738 } 739 } else { 740 allowed = true 741 } 742 743 if allowed { 744 eidsAllowed = append(eidsAllowed, eid) 745 } 746 } 747 748 // exit early if all eids are allowed and nothing needs to be removed 749 if len(eids) == len(eidsAllowed) { 750 return nil 751 } 752 753 // marshal eidsAllowed back to userExt 754 if len(eidsAllowed) == 0 { 755 delete(userExt, "eids") 756 } else { 757 eidsRaw, err := jsonutil.Marshal(eidsAllowed) 758 if err != nil { 759 return err 760 } 761 userExt["eids"] = eidsRaw 762 } 763 764 // exit early if userExt is empty 765 if len(userExt) == 0 { 766 setUserExtWithCopy(request, nil) 767 return nil 768 } 769 770 userExtJSON, err := jsonutil.Marshal(userExt) 771 if err != nil { 772 return err 773 } 774 setUserExtWithCopy(request, userExtJSON) 775 return nil 776 } 777 778 func setUserExtWithCopy(request *openrtb2.BidRequest, userExtJSON json.RawMessage) { 779 userCopy := *request.User 780 userCopy.Ext = userExtJSON 781 request.User = &userCopy 782 } 783 784 // resolveBidder returns the known BidderName associated with bidder, if bidder is an alias. If it's not an alias, the bidder is returned. 785 func resolveBidder(bidder string, requestAliases map[string]string) (openrtb_ext.BidderName, bool) { 786 normalisedBidderName, _ := openrtb_ext.NormalizeBidderName(bidder) 787 788 if coreBidder, ok := requestAliases[bidder]; ok { 789 return openrtb_ext.BidderName(coreBidder), true 790 } 791 792 return normalisedBidderName, false 793 } 794 795 func getRequestAliases(req *openrtb_ext.RequestWrapper) (map[string]string, map[string]uint16, []error) { 796 reqExt, err := req.GetRequestExt() 797 if err != nil { 798 return nil, nil, []error{errInvalidRequestExt} 799 } 800 801 if prebid := reqExt.GetPrebid(); prebid != nil { 802 return prebid.Aliases, prebid.AliasGVLIDs, nil 803 } 804 805 return nil, nil, nil 806 } 807 808 func GetValidBidders(requestAliases map[string]string) map[string]struct{} { 809 validBidders := openrtb_ext.BuildBidderNameHashSet() 810 811 for k := range requestAliases { 812 validBidders[k] = struct{}{} 813 } 814 815 return validBidders 816 } 817 818 // Quick little randomizer for a list of strings. Stuffing it in utils to keep other files clean 819 func randomizeList(list []openrtb_ext.BidderName) { 820 l := len(list) 821 perm := rand.Perm(l) 822 var j int 823 for i := 0; i < l; i++ { 824 j = perm[i] 825 list[i], list[j] = list[j], list[i] 826 } 827 } 828 829 func getExtCacheInstructions(requestExtPrebid *openrtb_ext.ExtRequestPrebid) extCacheInstructions { 830 //returnCreative defaults to true 831 cacheInstructions := extCacheInstructions{returnCreative: true} 832 foundBidsRC := false 833 foundVastRC := false 834 835 if requestExtPrebid != nil && requestExtPrebid.Cache != nil { 836 if requestExtPrebid.Cache.Bids != nil { 837 cacheInstructions.cacheBids = true 838 if requestExtPrebid.Cache.Bids.ReturnCreative != nil { 839 cacheInstructions.returnCreative = *requestExtPrebid.Cache.Bids.ReturnCreative 840 foundBidsRC = true 841 } 842 } 843 844 if requestExtPrebid.Cache.VastXML != nil { 845 cacheInstructions.cacheVAST = true 846 if requestExtPrebid.Cache.VastXML.ReturnCreative != nil { 847 cacheInstructions.returnCreative = *requestExtPrebid.Cache.VastXML.ReturnCreative 848 foundVastRC = true 849 } 850 } 851 } 852 853 if foundBidsRC && foundVastRC { 854 cacheInstructions.returnCreative = *requestExtPrebid.Cache.Bids.ReturnCreative || *requestExtPrebid.Cache.VastXML.ReturnCreative 855 } 856 857 return cacheInstructions 858 } 859 860 func getExtTargetData(requestExtPrebid *openrtb_ext.ExtRequestPrebid, cacheInstructions extCacheInstructions) *targetData { 861 if requestExtPrebid != nil && requestExtPrebid.Targeting != nil { 862 return &targetData{ 863 includeWinners: *requestExtPrebid.Targeting.IncludeWinners, 864 includeBidderKeys: *requestExtPrebid.Targeting.IncludeBidderKeys, 865 includeCacheBids: cacheInstructions.cacheBids, 866 includeCacheVast: cacheInstructions.cacheVAST, 867 includeFormat: requestExtPrebid.Targeting.IncludeFormat, 868 priceGranularity: *requestExtPrebid.Targeting.PriceGranularity, 869 mediaTypePriceGranularity: requestExtPrebid.Targeting.MediaTypePriceGranularity, 870 preferDeals: requestExtPrebid.Targeting.PreferDeals, 871 alwaysIncludeDeals: requestExtPrebid.Targeting.AlwaysIncludeDeals, 872 } 873 } 874 875 return nil 876 } 877 878 // getDebugInfo returns the boolean flags that allow for debug information in bidResponse.Ext, the SeatBid.httpcalls slice, and 879 // also sets the debugLog information 880 func getDebugInfo(test int8, requestExtPrebid *openrtb_ext.ExtRequestPrebid, accountDebugFlag bool, debugLog *DebugLog) (bool, bool, *DebugLog) { 881 requestDebugAllow := parseRequestDebugValues(test, requestExtPrebid) 882 debugLog = setDebugLogValues(accountDebugFlag, debugLog) 883 884 responseDebugAllow := (requestDebugAllow && accountDebugFlag) || debugLog.DebugEnabledOrOverridden 885 accountDebugAllow := (requestDebugAllow && accountDebugFlag) || (debugLog.DebugEnabledOrOverridden && accountDebugFlag) 886 887 return responseDebugAllow, accountDebugAllow, debugLog 888 } 889 890 // setDebugLogValues initializes the DebugLog if nil. It also sets the value of the debugInfo flag 891 // used in HoldAuction 892 func setDebugLogValues(accountDebugFlag bool, debugLog *DebugLog) *DebugLog { 893 if debugLog == nil { 894 debugLog = &DebugLog{} 895 } 896 897 debugLog.Enabled = debugLog.DebugEnabledOrOverridden || accountDebugFlag 898 return debugLog 899 } 900 901 func parseRequestDebugValues(test int8, requestExtPrebid *openrtb_ext.ExtRequestPrebid) bool { 902 return test == 1 || (requestExtPrebid != nil && requestExtPrebid.Debug) 903 } 904 905 func getExtBidAdjustmentFactors(requestExtPrebid *openrtb_ext.ExtRequestPrebid) map[string]float64 { 906 if requestExtPrebid != nil && requestExtPrebid.BidAdjustmentFactors != nil { 907 caseInsensitiveMap := make(map[string]float64, len(requestExtPrebid.BidAdjustmentFactors)) 908 for bidder, bidAdjFactor := range requestExtPrebid.BidAdjustmentFactors { 909 caseInsensitiveMap[strings.ToLower(bidder)] = bidAdjFactor 910 } 911 return caseInsensitiveMap 912 } 913 return nil 914 } 915 916 func applyFPD(fpd map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData, r BidderRequest) { 917 if fpd == nil { 918 return 919 } 920 921 bidder := r.BidderCoreName 922 if r.IsRequestAlias { 923 bidder = r.BidderName 924 } 925 926 fpdToApply, exists := fpd[bidder] 927 if !exists || fpdToApply == nil { 928 return 929 } 930 931 if fpdToApply.Site != nil { 932 r.BidRequest.Site = fpdToApply.Site 933 } 934 935 if fpdToApply.App != nil { 936 r.BidRequest.App = fpdToApply.App 937 } 938 939 if fpdToApply.User != nil { 940 //BuyerUID is a value obtained between fpd extraction and fpd application. 941 //BuyerUID needs to be set back to fpd before applying this fpd to final bidder request 942 if r.BidRequest.User != nil && len(r.BidRequest.User.BuyerUID) > 0 { 943 fpdToApply.User.BuyerUID = r.BidRequest.User.BuyerUID 944 } 945 r.BidRequest.User = fpdToApply.User 946 } 947 } 948 949 func buildBidResponseRequest(req *openrtb2.BidRequest, 950 bidderImpResponses stored_responses.BidderImpsWithBidResponses, 951 requestAliases map[string]string, 952 bidderImpReplaceImpID stored_responses.BidderImpReplaceImpID) map[openrtb_ext.BidderName]BidderRequest { 953 954 bidderToBidderResponse := make(map[openrtb_ext.BidderName]BidderRequest) 955 956 for bidderName, impResps := range bidderImpResponses { 957 resolvedBidder, isRequestAlias := resolveBidder(string(bidderName), requestAliases) 958 bidderToBidderResponse[bidderName] = BidderRequest{ 959 BidRequest: req, 960 BidderCoreName: resolvedBidder, 961 BidderName: bidderName, 962 BidderStoredResponses: impResps, 963 ImpReplaceImpId: bidderImpReplaceImpID[string(bidderName)], 964 IsRequestAlias: isRequestAlias, 965 BidderLabels: metrics.AdapterLabels{Adapter: resolvedBidder}, 966 } 967 } 968 return bidderToBidderResponse 969 } 970 971 func mergeBidderRequests(allBidderRequests []BidderRequest, bidderNameToBidderReq map[openrtb_ext.BidderName]BidderRequest) []BidderRequest { 972 if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) == 0 { 973 return allBidderRequests 974 } 975 if len(allBidderRequests) == 0 && len(bidderNameToBidderReq) > 0 { 976 for _, v := range bidderNameToBidderReq { 977 allBidderRequests = append(allBidderRequests, v) 978 } 979 return allBidderRequests 980 } else if len(allBidderRequests) > 0 && len(bidderNameToBidderReq) > 0 { 981 //merge bidder requests with real imps and imps with stored resp 982 for bn, br := range bidderNameToBidderReq { 983 found := false 984 for i, ar := range allBidderRequests { 985 if ar.BidderName == bn { 986 //bidder req with real imps and imps with stored resp 987 allBidderRequests[i].BidderStoredResponses = br.BidderStoredResponses 988 found = true 989 break 990 } 991 } 992 if !found { 993 //bidder req with stored bid responses only 994 br.BidRequest.Imp = nil // to indicate this bidder request has bidder responses only 995 allBidderRequests = append(allBidderRequests, br) 996 } 997 } 998 } 999 return allBidderRequests 1000 } 1001 1002 func setLegacyGDPRFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { 1003 if r.Regs != nil && r.Regs.GDPR == nil { 1004 if r.Regs.GPPSID != nil { 1005 // Set to 0 unless SID exists 1006 regs := *r.Regs 1007 regs.GDPR = ptrutil.ToPtr[int8](0) 1008 for _, id := range r.Regs.GPPSID { 1009 if id == int8(gppConstants.SectionTCFEU2) { 1010 regs.GDPR = ptrutil.ToPtr[int8](1) 1011 } 1012 } 1013 r.Regs = ®s 1014 } 1015 } 1016 1017 if r.User == nil || len(r.User.Consent) == 0 { 1018 for _, sec := range gpp.Sections { 1019 if sec.GetID() == gppConstants.SectionTCFEU2 { 1020 var user openrtb2.User 1021 if r.User == nil { 1022 user = openrtb2.User{} 1023 } else { 1024 user = *r.User 1025 } 1026 user.Consent = sec.GetValue() 1027 r.User = &user 1028 } 1029 } 1030 } 1031 1032 } 1033 func setLegacyUSPFromGPP(r *openrtb2.BidRequest, gpp gpplib.GppContainer) { 1034 if r.Regs == nil { 1035 return 1036 } 1037 1038 if len(r.Regs.USPrivacy) > 0 || r.Regs.GPPSID == nil { 1039 return 1040 } 1041 for _, sid := range r.Regs.GPPSID { 1042 if sid == int8(gppConstants.SectionUSPV1) { 1043 for _, sec := range gpp.Sections { 1044 if sec.GetID() == gppConstants.SectionUSPV1 { 1045 regs := *r.Regs 1046 regs.USPrivacy = sec.GetValue() 1047 r.Regs = ®s 1048 } 1049 } 1050 } 1051 } 1052 1053 } 1054 1055 func WrapJSONInData(data []byte) []byte { 1056 res := make([]byte, 0, len(data)) 1057 res = append(res, []byte(`{"data":`)...) 1058 res = append(res, data...) 1059 res = append(res, []byte(`}`)...) 1060 return res 1061 } 1062 1063 func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { 1064 mType := bid.MType 1065 var bidType openrtb_ext.BidType 1066 if mType > 0 { 1067 switch mType { 1068 case openrtb2.MarkupBanner: 1069 bidType = openrtb_ext.BidTypeBanner 1070 case openrtb2.MarkupVideo: 1071 bidType = openrtb_ext.BidTypeVideo 1072 case openrtb2.MarkupAudio: 1073 bidType = openrtb_ext.BidTypeAudio 1074 case openrtb2.MarkupNative: 1075 bidType = openrtb_ext.BidTypeNative 1076 default: 1077 return bidType, fmt.Errorf("Failed to parse bid mType for impression \"%s\"", bid.ImpID) 1078 } 1079 } else { 1080 var err error 1081 bidType, err = getPrebidMediaTypeForBid(bid) 1082 if err != nil { 1083 return bidType, err 1084 } 1085 } 1086 return bidType, nil 1087 } 1088 1089 func getPrebidMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) { 1090 var err error 1091 var bidType openrtb_ext.BidType 1092 1093 if bid.Ext != nil { 1094 var bidExt openrtb_ext.ExtBid 1095 err = jsonutil.Unmarshal(bid.Ext, &bidExt) 1096 if err == nil && bidExt.Prebid != nil { 1097 if bidType, err = openrtb_ext.ParseBidType(string(bidExt.Prebid.Type)); err == nil { 1098 return bidType, nil 1099 } 1100 } 1101 } 1102 1103 errMsg := fmt.Sprintf("Failed to parse bid mediatype for impression \"%s\"", bid.ImpID) 1104 if err != nil { 1105 errMsg = fmt.Sprintf("%s, %s", errMsg, err.Error()) 1106 } 1107 1108 return bidType, &errortypes.BadServerResponse{ 1109 Message: errMsg, 1110 } 1111 } 1112 1113 func applyBidAdjustmentToFloor(allBidderRequests []BidderRequest, bidAdjustmentFactors map[string]float64) { 1114 1115 if len(bidAdjustmentFactors) == 0 { 1116 return 1117 } 1118 1119 for _, bidderRequest := range allBidderRequests { 1120 bidAdjustment := 1.0 1121 1122 if bidAdjustemntValue, ok := bidAdjustmentFactors[string(bidderRequest.BidderName)]; ok { 1123 bidAdjustment = bidAdjustemntValue 1124 } 1125 1126 if bidAdjustment != 1.0 { 1127 for index, imp := range bidderRequest.BidRequest.Imp { 1128 imp.BidFloor = imp.BidFloor / bidAdjustment 1129 bidderRequest.BidRequest.Imp[index] = imp 1130 } 1131 } 1132 } 1133 }