github.com/prebid/prebid-server@v0.275.0/adapters/rubicon/rubicon.go (about) 1 package rubicon 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "strconv" 9 "strings" 10 11 "github.com/prebid/prebid-server/adapters" 12 "github.com/prebid/prebid-server/config" 13 "github.com/prebid/prebid-server/errortypes" 14 "github.com/prebid/prebid-server/openrtb_ext" 15 "github.com/prebid/prebid-server/util/maputil" 16 17 "github.com/buger/jsonparser" 18 "github.com/prebid/openrtb/v19/adcom1" 19 "github.com/prebid/openrtb/v19/openrtb2" 20 ) 21 22 const badvLimitSize = 50 23 24 var bannerExtContent = []byte(`{"rp":{"mime":"text/html"}}`) 25 26 type RubiconAdapter struct { 27 URI string 28 XAPIUsername string 29 XAPIPassword string 30 } 31 32 type rubiconContext struct { 33 Data json.RawMessage `json:"data"` 34 } 35 36 type rubiconData struct { 37 AdServer rubiconAdServer `json:"adserver"` 38 PbAdSlot string `json:"pbadslot"` 39 } 40 41 type rubiconAdServer struct { 42 Name string `json:"name"` 43 AdSlot string `json:"adslot"` 44 } 45 46 type rubiconExtImpBidder struct { 47 Prebid *openrtb_ext.ExtImpPrebid `json:"prebid"` 48 Bidder openrtb_ext.ExtImpRubicon `json:"bidder"` 49 Gpid string `json:"gpid"` 50 Skadn json.RawMessage `json:"skadn,omitempty"` 51 Data json.RawMessage `json:"data"` 52 Context rubiconContext `json:"context"` 53 } 54 55 type bidRequestExt struct { 56 Prebid bidRequestExtPrebid `json:"prebid"` 57 } 58 59 type bidRequestExtPrebid struct { 60 Bidders bidRequestExtPrebidBidders `json:"bidders"` 61 } 62 63 type bidRequestExtPrebidBidders struct { 64 Rubicon prebidBiddersRubicon `json:"rubicon,omitempty"` 65 } 66 67 type prebidBiddersRubicon struct { 68 Debug prebidBiddersRubiconDebug `json:"debug,omitempty"` 69 } 70 71 type prebidBiddersRubiconDebug struct { 72 CpmOverride float64 `json:"cpmoverride,omitempty"` 73 } 74 75 type rubiconImpExtRPTrack struct { 76 Mint string `json:"mint"` 77 MintVersion string `json:"mint_version"` 78 } 79 80 type rubiconImpExt struct { 81 RP rubiconImpExtRP `json:"rp,omitempty"` 82 GPID string `json:"gpid,omitempty"` 83 Skadn json.RawMessage `json:"skadn,omitempty"` 84 } 85 86 type rubiconImpExtRP struct { 87 ZoneID int `json:"zone_id"` 88 Target json.RawMessage `json:"target,omitempty"` 89 Track rubiconImpExtRPTrack `json:"track"` 90 } 91 92 type rubiconUserExtRP struct { 93 Target json.RawMessage `json:"target,omitempty"` 94 } 95 96 type rubiconDataExt struct { 97 SegTax int `json:"segtax"` 98 } 99 100 type rubiconUserExt struct { 101 Eids []openrtb2.EID `json:"eids,omitempty"` 102 RP rubiconUserExtRP `json:"rp"` 103 LiverampIdl string `json:"liveramp_idl,omitempty"` 104 Data json.RawMessage `json:"data,omitempty"` 105 Consent string `json:"consent,omitempty"` 106 } 107 108 type rubiconSiteExtRP struct { 109 SiteID int `json:"site_id"` 110 Target json.RawMessage `json:"target,omitempty"` 111 } 112 113 type rubiconSiteExt struct { 114 RP rubiconSiteExtRP `json:"rp"` 115 } 116 117 type rubiconPubExtRP struct { 118 AccountID int `json:"account_id"` 119 } 120 121 type rubiconPubExt struct { 122 RP rubiconPubExtRP `json:"rp"` 123 } 124 125 type rubiconBannerExtRP struct { 126 MIME string `json:"mime"` 127 } 128 129 type rubiconBannerExt struct { 130 RP rubiconBannerExtRP `json:"rp"` 131 } 132 133 // ***** Video Extension ***** 134 type rubiconVideoExt struct { 135 Skip int `json:"skip,omitempty"` 136 SkipDelay int `json:"skipdelay,omitempty"` 137 VideoType string `json:"videotype,omitempty"` 138 RP rubiconVideoExtRP `json:"rp"` 139 } 140 141 type rubiconVideoExtRP struct { 142 SizeID int `json:"size_id,omitempty"` 143 } 144 145 type rubiconDeviceExtRP struct { 146 PixelRatio float64 `json:"pixelratio"` 147 } 148 149 type rubiconDeviceExt struct { 150 RP rubiconDeviceExtRP `json:"rp"` 151 } 152 153 type rubiconBidResponse struct { 154 openrtb2.BidResponse 155 SeatBid []rubiconSeatBid `json:"seatbid,omitempty"` 156 } 157 158 type rubiconSeatBid struct { 159 openrtb2.SeatBid 160 Buyer string `json:"buyer,omitempty"` 161 Bid []rubiconBid `json:"bid"` 162 } 163 164 type rubiconBid struct { 165 openrtb2.Bid 166 AdmNative json.RawMessage `json:"adm_native,omitempty"` 167 } 168 169 type extPrebid struct { 170 Prebid *openrtb_ext.ExtBidPrebid `json:"prebid,omitempty"` 171 Bidder json.RawMessage `json:"bidder,omitempty"` 172 } 173 174 // defines the contract for bidrequest.user.ext.eids[i].ext 175 type rubiconUserExtEidExt struct { 176 Segments []string `json:"segments,omitempty"` 177 } 178 179 type mappedRubiconUidsParam struct { 180 segments []string 181 liverampIdl string 182 } 183 184 func resolveVideoSizeId(placement adcom1.VideoPlacementSubtype, instl int8, impId string) (sizeID int, err error) { 185 if placement != 0 { 186 if placement == 1 { 187 return 201, nil 188 } 189 if placement == 3 { 190 return 203, nil 191 } 192 } 193 194 if instl == 1 { 195 return 202, nil 196 } 197 return 0, &errortypes.BadInput{ 198 Message: fmt.Sprintf("video.size_id can not be resolved in impression with id : %s", impId), 199 } 200 } 201 202 func appendTrackerToUrl(uri string, tracker string) (res string) { 203 // Append integration method. Adapter init happens once 204 urlObject, err := url.Parse(uri) 205 // No other exception throwing mechanism in this stack, so ignoring parse errors. 206 if err == nil { 207 values := urlObject.Query() 208 values.Add("tk_xint", tracker) 209 urlObject.RawQuery = values.Encode() 210 res = urlObject.String() 211 } else { 212 res = uri 213 } 214 return 215 } 216 217 // Builder builds a new instance of the Rubicon adapter for the given bidder with the given config. 218 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) { 219 uri := appendTrackerToUrl(config.Endpoint, config.XAPI.Tracker) 220 221 bidder := &RubiconAdapter{ 222 URI: uri, 223 XAPIUsername: config.XAPI.Username, 224 XAPIPassword: config.XAPI.Password, 225 } 226 return bidder, nil 227 } 228 229 func updateRequestTo26(r *openrtb2.BidRequest) error { 230 if r.Regs != nil { 231 regsCopy := *r.Regs 232 r.Regs = ®sCopy 233 } 234 235 if r.Source != nil { 236 sourceCopy := *r.Source 237 r.Source = &sourceCopy 238 } 239 240 if r.User != nil { 241 userCopy := *r.User 242 r.User = &userCopy 243 } 244 245 requestWrapper := &openrtb_ext.RequestWrapper{BidRequest: r} 246 247 if err := openrtb_ext.ConvertUpTo26(requestWrapper); err != nil { 248 return err 249 } 250 251 return requestWrapper.RebuildRequest() 252 } 253 254 func (a *RubiconAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) { 255 256 err := updateRequestTo26(request) 257 258 if err != nil { 259 return nil, []error{err} 260 } 261 262 numRequests := len(request.Imp) 263 requestData := make([]*adapters.RequestData, 0, numRequests) 264 headers := http.Header{} 265 headers.Add("Content-Type", "application/json;charset=utf-8") 266 headers.Add("Accept", "application/json") 267 headers.Add("User-Agent", "prebid-server/1.0") 268 269 impsToExtNotGrouped, errs := createImpsToExtMap(request.Imp) 270 impsToExtMap := prepareImpsToExtMap(impsToExtNotGrouped) 271 272 rubiconRequest := *request 273 for imp, bidderExt := range impsToExtMap { 274 rubiconExt := bidderExt.Bidder 275 target, err := updateImpRpTargetWithFpdAttributes(bidderExt, rubiconExt, *imp, request.Site, request.App) 276 if err != nil { 277 errs = append(errs, err) 278 continue 279 } 280 281 siteId, err := rubiconExt.SiteId.Int64() 282 if err != nil { 283 errs = append(errs, err) 284 continue 285 } 286 287 zoneId, err := rubiconExt.ZoneId.Int64() 288 if err != nil { 289 errs = append(errs, err) 290 continue 291 } 292 293 impExt := rubiconImpExt{ 294 RP: rubiconImpExtRP{ 295 ZoneID: int(zoneId), 296 Target: target, 297 Track: rubiconImpExtRPTrack{Mint: "", MintVersion: ""}, 298 }, 299 GPID: bidderExt.Gpid, 300 Skadn: bidderExt.Skadn, 301 } 302 303 imp.Ext, err = json.Marshal(&impExt) 304 if err != nil { 305 errs = append(errs, err) 306 continue 307 } 308 309 secure := int8(1) 310 imp.Secure = &secure 311 312 resolvedBidFloor, err := resolveBidFloor(imp.BidFloor, imp.BidFloorCur, reqInfo) 313 if err != nil { 314 errs = append(errs, &errortypes.BadInput{ 315 Message: fmt.Sprintf("Unable to convert provided bid floor currency from %s to USD", 316 imp.BidFloorCur), 317 }) 318 continue 319 } 320 321 if resolvedBidFloor > 0 { 322 imp.BidFloorCur = "USD" 323 imp.BidFloor = resolvedBidFloor 324 } 325 326 if request.User != nil { 327 userCopy := *request.User 328 target, err := updateUserRpTargetWithFpdAttributes(rubiconExt.Visitor, userCopy) 329 if err != nil { 330 errs = append(errs, err) 331 continue 332 } 333 334 userExtRP := rubiconUserExt{RP: rubiconUserExtRP{Target: target}} 335 userBuyerUID := userCopy.BuyerUID 336 337 if len(userCopy.EIDs) > 0 { 338 userExtRP.Eids = userCopy.EIDs 339 340 if userBuyerUID == "" { 341 userBuyerUID = extractUserBuyerUID(userExtRP.Eids) 342 } 343 344 mappedRubiconUidsParam, errors := getSegments(userExtRP.Eids) 345 if len(errors) > 0 { 346 errs = append(errs, errors...) 347 continue 348 } 349 350 if err := updateUserExtWithSegments(&userExtRP, mappedRubiconUidsParam); err != nil { 351 errs = append(errs, err) 352 continue 353 } 354 355 userExtRP.LiverampIdl = mappedRubiconUidsParam.liverampIdl 356 } 357 358 if userCopy.Consent != "" { 359 userExtRP.Consent = userCopy.Consent 360 userCopy.Consent = "" 361 } 362 363 userCopy.Ext, err = json.Marshal(&userExtRP) 364 if err != nil { 365 errs = append(errs, err) 366 continue 367 } 368 userCopy.Geo = nil 369 userCopy.Yob = 0 370 userCopy.Gender = "" 371 userCopy.BuyerUID = userBuyerUID 372 userCopy.EIDs = nil 373 374 rubiconRequest.User = &userCopy 375 } 376 377 if request.Device != nil { 378 deviceCopy := *request.Device 379 deviceExt := rubiconDeviceExt{RP: rubiconDeviceExtRP{PixelRatio: request.Device.PxRatio}} 380 deviceCopy.Ext, err = json.Marshal(&deviceExt) 381 rubiconRequest.Device = &deviceCopy 382 } 383 384 isVideo := isVideo(*imp) 385 impType := openrtb_ext.BidTypeVideo 386 requestNative := make(map[string]interface{}) 387 if isVideo { 388 videoCopy := *imp.Video 389 390 videoSizeId := rubiconExt.Video.VideoSizeID 391 if videoSizeId == 0 { 392 resolvedSizeId, err := resolveVideoSizeId(imp.Video.Placement, imp.Instl, imp.ID) 393 if err != nil { 394 errs = append(errs, err) 395 continue 396 } 397 videoSizeId = resolvedSizeId 398 } 399 400 // if imp.rwdd = 1, set imp.video.ext.videotype = "rewarded" 401 var videoType = "" 402 if imp.Rwdd == 1 { 403 videoType = "rewarded" 404 imp.Rwdd = 0 405 } 406 videoExt := rubiconVideoExt{Skip: rubiconExt.Video.Skip, SkipDelay: rubiconExt.Video.SkipDelay, VideoType: videoType, RP: rubiconVideoExtRP{SizeID: videoSizeId}} 407 videoCopy.Ext, err = json.Marshal(&videoExt) 408 imp.Video = &videoCopy 409 imp.Banner = nil 410 imp.Native = nil 411 } else if imp.Banner != nil { 412 bannerCopy := *imp.Banner 413 if len(bannerCopy.Format) < 1 && (bannerCopy.W == nil || *bannerCopy.W == 0 && bannerCopy.H == nil || *bannerCopy.H == 0) { 414 errs = append(errs, &errortypes.BadInput{ 415 Message: "rubicon imps must have at least one imp.format element", 416 }) 417 continue 418 } 419 bannerCopy.Ext = bannerExtContent 420 if err != nil { 421 errs = append(errs, err) 422 continue 423 } 424 imp.Banner = &bannerCopy 425 imp.Video = nil 426 imp.Native = nil 427 impType = openrtb_ext.BidTypeBanner 428 } else { 429 native, err := resolveNativeObject(imp.Native, requestNative) 430 if err != nil { 431 errs = append(errs, err) 432 continue 433 } 434 imp.Native = native 435 imp.Video = nil 436 impType = openrtb_ext.BidTypeNative 437 } 438 439 accountId, err := rubiconExt.AccountId.Int64() 440 if err != nil { 441 errs = append(errs, err) 442 continue 443 } 444 445 pubExt := rubiconPubExt{RP: rubiconPubExtRP{AccountID: int(accountId)}} 446 447 if request.Site != nil { 448 siteCopy := *request.Site 449 siteExtRP := rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}} 450 if siteCopy.Content != nil { 451 siteTarget := make(map[string]interface{}) 452 updateExtWithIabAttribute(siteTarget, siteCopy.Content.Data, []int{1, 2, 5, 6}) 453 if len(siteTarget) > 0 { 454 updatedSiteTarget, err := json.Marshal(siteTarget) 455 if err != nil { 456 errs = append(errs, err) 457 continue 458 } 459 siteExtRP.RP.Target = updatedSiteTarget 460 } 461 } 462 463 siteCopy.Ext, err = json.Marshal(&siteExtRP) 464 if err != nil { 465 errs = append(errs, err) 466 continue 467 } 468 469 siteCopy.Publisher = &openrtb2.Publisher{} 470 siteCopy.Publisher.Ext, err = json.Marshal(&pubExt) 471 rubiconRequest.Site = &siteCopy 472 } else { 473 appCopy := *request.App 474 appCopy.Ext, err = json.Marshal(rubiconSiteExt{RP: rubiconSiteExtRP{SiteID: int(siteId)}}) 475 appCopy.Publisher = &openrtb2.Publisher{} 476 appCopy.Publisher.Ext, err = json.Marshal(&pubExt) 477 rubiconRequest.App = &appCopy 478 } 479 480 if request.Source != nil || rubiconExt.PChain != "" { 481 var sourceCopy openrtb2.Source 482 if request.Source != nil { 483 sourceCopy = *request.Source 484 } else { 485 sourceCopy = openrtb2.Source{} 486 } 487 488 if sourceCopy.SChain != nil { 489 var sourceCopyExt openrtb_ext.ExtSource 490 if sourceCopy.Ext != nil { 491 if err = json.Unmarshal(sourceCopy.Ext, &sourceCopyExt); err != nil { 492 errs = append(errs, &errortypes.BadInput{Message: err.Error()}) 493 continue 494 } 495 } else { 496 sourceCopyExt = openrtb_ext.ExtSource{} 497 } 498 499 sourceCopyExt.SChain = sourceCopy.SChain 500 sourceCopy.SChain = nil 501 502 sourceCopy.Ext, err = json.Marshal(&sourceCopyExt) 503 if err != nil { 504 errs = append(errs, err) 505 continue 506 } 507 } 508 509 if rubiconExt.PChain != "" { 510 sourceCopy.PChain = rubiconExt.PChain 511 } 512 513 rubiconRequest.Source = &sourceCopy 514 } 515 516 if request.Regs != nil && (request.Regs.GDPR != nil || request.Regs.USPrivacy != "") { 517 regsCopy := *request.Regs 518 519 var regsCopyExt openrtb_ext.ExtRegs 520 if regsCopy.Ext != nil { 521 if err = json.Unmarshal(regsCopy.Ext, ®sCopyExt); err != nil { 522 errs = append(errs, &errortypes.BadInput{Message: err.Error()}) 523 continue 524 } 525 } else { 526 regsCopyExt = openrtb_ext.ExtRegs{} 527 } 528 529 if regsCopy.GDPR != nil { 530 regsCopyExt.GDPR = regsCopy.GDPR 531 } 532 if regsCopy.USPrivacy != "" { 533 regsCopyExt.USPrivacy = regsCopy.USPrivacy 534 } 535 536 regsCopy.Ext, err = json.Marshal(®sCopyExt) 537 if err != nil { 538 errs = append(errs, err) 539 continue 540 } 541 regsCopy.GDPR = nil 542 regsCopy.USPrivacy = "" 543 544 rubiconRequest.Regs = ®sCopy 545 } 546 547 reqBadv := request.BAdv 548 if reqBadv != nil { 549 if len(reqBadv) > badvLimitSize { 550 rubiconRequest.BAdv = reqBadv[:badvLimitSize] 551 } 552 } 553 554 rubiconRequest.Imp = []openrtb2.Imp{*imp} 555 rubiconRequest.Cur = nil 556 rubiconRequest.Ext = nil 557 558 reqJSON, err := json.Marshal(rubiconRequest) 559 if impType == openrtb_ext.BidTypeNative && len(requestNative) > 0 { 560 reqJSON, err = setImpNative(reqJSON, requestNative) 561 } 562 563 if err != nil { 564 errs = append(errs, err) 565 continue 566 } 567 568 reqData := &adapters.RequestData{ 569 Method: "POST", 570 Uri: a.URI, 571 Body: reqJSON, 572 Headers: headers, 573 } 574 reqData.SetBasicAuth(a.XAPIUsername, a.XAPIPassword) 575 requestData = append(requestData, reqData) 576 } 577 578 return requestData, errs 579 } 580 581 func createImpsToExtMap(imps []openrtb2.Imp) (map[*openrtb2.Imp]rubiconExtImpBidder, []error) { 582 impsToExtMap := make(map[*openrtb2.Imp]rubiconExtImpBidder) 583 errs := make([]error, 0) 584 var err error 585 for _, imp := range imps { 586 impCopy := imp 587 var bidderExt rubiconExtImpBidder 588 if err = json.Unmarshal(imp.Ext, &bidderExt); err != nil { 589 errs = append(errs, &errortypes.BadInput{ 590 Message: err.Error(), 591 }) 592 continue 593 } 594 impsToExtMap[&impCopy] = bidderExt 595 } 596 597 return impsToExtMap, errs 598 } 599 600 func prepareImpsToExtMap(impsToExtMap map[*openrtb2.Imp]rubiconExtImpBidder) map[*openrtb2.Imp]rubiconExtImpBidder { 601 preparedImpsToExtMap := make(map[*openrtb2.Imp]rubiconExtImpBidder) 602 for imp, bidderExt := range impsToExtMap { 603 if bidderExt.Bidder.BidOnMultiformat == false { 604 impCopy := imp 605 preparedImpsToExtMap[impCopy] = bidderExt 606 continue 607 } 608 609 splitImps := splitMultiFormatImp(imp) 610 for _, imp := range splitImps { 611 impCopy := imp 612 preparedImpsToExtMap[impCopy] = bidderExt 613 } 614 } 615 616 return preparedImpsToExtMap 617 } 618 619 func splitMultiFormatImp(imp *openrtb2.Imp) []*openrtb2.Imp { 620 splitImps := make([]*openrtb2.Imp, 0) 621 if imp.Banner != nil { 622 impCopy := *imp 623 impCopy.Video = nil 624 impCopy.Native = nil 625 impCopy.Audio = nil 626 splitImps = append(splitImps, &impCopy) 627 } 628 629 if imp.Video != nil { 630 impCopy := *imp 631 impCopy.Banner = nil 632 impCopy.Native = nil 633 impCopy.Audio = nil 634 splitImps = append(splitImps, &impCopy) 635 } 636 637 if imp.Native != nil { 638 impCopy := *imp 639 impCopy.Banner = nil 640 impCopy.Video = nil 641 impCopy.Audio = nil 642 splitImps = append(splitImps, &impCopy) 643 } 644 645 if imp.Audio != nil { 646 impCopy := *imp 647 impCopy.Banner = nil 648 impCopy.Video = nil 649 impCopy.Native = nil 650 splitImps = append(splitImps, &impCopy) 651 } 652 653 return splitImps 654 } 655 656 func resolveBidFloor(bidFloor float64, bidFloorCur string, reqInfo *adapters.ExtraRequestInfo) (float64, error) { 657 if bidFloor > 0 && bidFloorCur != "" && strings.ToUpper(bidFloorCur) != "USD" { 658 return reqInfo.ConvertCurrency(bidFloor, bidFloorCur, "USD") 659 } 660 661 return bidFloor, nil 662 } 663 664 func updateImpRpTargetWithFpdAttributes(extImp rubiconExtImpBidder, extImpRubicon openrtb_ext.ExtImpRubicon, 665 imp openrtb2.Imp, site *openrtb2.Site, app *openrtb2.App) (json.RawMessage, error) { 666 667 existingTarget, _, _, err := jsonparser.Get(imp.Ext, "rp", "target") 668 if isNotKeyPathError(err) { 669 return nil, err 670 } 671 target, err := rawJSONToMap(existingTarget) 672 if err != nil { 673 return nil, err 674 } 675 err = populateFirstPartyDataAttributes(extImpRubicon.Inventory, target) 676 if err != nil { 677 return nil, err 678 } 679 680 if site != nil { 681 siteExtData, _, _, err := jsonparser.Get(site.Ext, "data") 682 if isNotKeyPathError(err) { 683 return nil, err 684 } 685 err = populateFirstPartyDataAttributes(siteExtData, target) 686 if err != nil { 687 return nil, err 688 } 689 if len(site.SectionCat) > 0 { 690 addStringArrayAttribute(site.SectionCat, target, "sectioncat") 691 } 692 if len(site.PageCat) > 0 { 693 addStringArrayAttribute(site.PageCat, target, "pagecat") 694 } 695 if site.Page != "" { 696 addStringAttribute(site.Page, target, "page") 697 } 698 if site.Ref != "" { 699 addStringAttribute(site.Ref, target, "ref") 700 } 701 if site.Search != "" { 702 addStringAttribute(site.Search, target, "search") 703 } 704 } else { 705 appExtData, _, _, err := jsonparser.Get(app.Ext, "data") 706 if isNotKeyPathError(err) { 707 return nil, err 708 } 709 err = populateFirstPartyDataAttributes(appExtData, target) 710 if err != nil { 711 return nil, err 712 } 713 if len(app.SectionCat) > 0 { 714 addStringArrayAttribute(app.SectionCat, target, "sectioncat") 715 } 716 if len(app.PageCat) > 0 { 717 addStringArrayAttribute(app.PageCat, target, "pagecat") 718 } 719 } 720 721 if len(extImp.Context.Data) > 0 { 722 err = populateFirstPartyDataAttributes(extImp.Context.Data, target) 723 } else if len(extImp.Data) > 0 { 724 err = populateFirstPartyDataAttributes(extImp.Data, target) 725 } 726 if isNotKeyPathError(err) { 727 return nil, err 728 } 729 730 var data rubiconData 731 if len(extImp.Data) > 0 { 732 err := json.Unmarshal(extImp.Data, &data) 733 if err != nil { 734 return nil, err 735 } 736 } 737 var contextData rubiconData 738 if len(extImp.Context.Data) > 0 { 739 err := json.Unmarshal(extImp.Context.Data, &contextData) 740 if err != nil { 741 return nil, err 742 } 743 } 744 745 if data.PbAdSlot != "" { 746 target["pbadslot"] = data.PbAdSlot 747 } else { 748 dfpAdUnitCode := extractDfpAdUnitCode(data, contextData) 749 if dfpAdUnitCode != "" { 750 target["dfp_ad_unit_code"] = dfpAdUnitCode 751 } 752 } 753 754 if len(extImpRubicon.Keywords) > 0 { 755 addStringArrayAttribute(extImpRubicon.Keywords, target, "keywords") 756 } 757 updatedTarget, err := json.Marshal(target) 758 if err != nil { 759 return nil, err 760 } 761 return updatedTarget, nil 762 } 763 764 func extractDfpAdUnitCode(data rubiconData, contextData rubiconData) string { 765 if contextData.AdServer.Name == "gam" && contextData.AdServer.AdSlot != "" { 766 return contextData.AdServer.AdSlot 767 } else if data.AdServer.Name == "gam" && data.AdServer.AdSlot != "" { 768 return data.AdServer.AdSlot 769 } 770 771 return "" 772 } 773 774 func isNotKeyPathError(err error) bool { 775 return err != nil && err != jsonparser.KeyPathNotFoundError 776 } 777 778 func addStringAttribute(attribute string, target map[string]interface{}, attributeName string) { 779 target[attributeName] = [1]string{attribute} 780 } 781 782 func addStringArrayAttribute(attribute []string, target map[string]interface{}, attributeName string) { 783 target[attributeName] = attribute 784 } 785 786 func updateUserRpTargetWithFpdAttributes(visitor json.RawMessage, user openrtb2.User) (json.RawMessage, error) { 787 existingTarget, _, _, err := jsonparser.Get(user.Ext, "rp", "target") 788 if isNotKeyPathError(err) { 789 return nil, err 790 } 791 target, err := rawJSONToMap(existingTarget) 792 if err != nil { 793 return nil, err 794 } 795 err = populateFirstPartyDataAttributes(visitor, target) 796 if err != nil { 797 return nil, err 798 } 799 userExtData, _, _, err := jsonparser.Get(user.Ext, "data") 800 if isNotKeyPathError(err) { 801 return nil, err 802 } 803 err = populateFirstPartyDataAttributes(userExtData, target) 804 if err != nil { 805 return nil, err 806 } 807 updateExtWithIabAttribute(target, user.Data, []int{4}) 808 809 updatedTarget, err := json.Marshal(target) 810 if err != nil { 811 return nil, err 812 } 813 return updatedTarget, nil 814 } 815 816 func updateExtWithIabAttribute(target map[string]interface{}, data []openrtb2.Data, segTaxes []int) { 817 var segmentIdsToCopy = getSegmentIdsToCopy(data, segTaxes) 818 if len(segmentIdsToCopy) == 0 { 819 return 820 } 821 822 target["iab"] = segmentIdsToCopy 823 } 824 825 func populateFirstPartyDataAttributes(source json.RawMessage, target map[string]interface{}) error { 826 sourceAsMap, err := rawJSONToMap(source) 827 if err != nil { 828 return err 829 } 830 831 for key, val := range sourceAsMap { 832 switch typedValue := val.(type) { 833 case string: 834 target[key] = [1]string{typedValue} 835 case float64: 836 if typedValue == float64(int(typedValue)) { 837 target[key] = [1]string{strconv.Itoa(int(typedValue))} 838 } 839 case bool: 840 target[key] = [1]string{strconv.FormatBool(typedValue)} 841 case []interface{}: 842 if isStringArray(typedValue) { 843 target[key] = typedValue 844 } 845 if isBoolArray(typedValue) { 846 target[key] = convertToStringArray(typedValue) 847 } 848 } 849 } 850 return nil 851 } 852 853 func isStringArray(array []interface{}) bool { 854 for _, val := range array { 855 if _, ok := val.(string); !ok { 856 return false 857 } 858 } 859 860 return true 861 } 862 863 func isBoolArray(array []interface{}) bool { 864 for _, val := range array { 865 if _, ok := val.(bool); !ok { 866 return false 867 } 868 } 869 870 return true 871 } 872 873 func convertToStringArray(arr []interface{}) []string { 874 var stringArray []string 875 for _, val := range arr { 876 if boolVal, ok := val.(bool); ok { 877 stringArray = append(stringArray, strconv.FormatBool(boolVal)) 878 } 879 } 880 881 return stringArray 882 } 883 884 func rawJSONToMap(message json.RawMessage) (map[string]interface{}, error) { 885 if message == nil { 886 return make(map[string]interface{}), nil 887 } 888 889 return mapFromRawJSON(message) 890 } 891 892 func mapFromRawJSON(message json.RawMessage) (map[string]interface{}, error) { 893 targetAsMap := make(map[string]interface{}) 894 err := json.Unmarshal(message, &targetAsMap) 895 if err != nil { 896 return nil, err 897 } 898 return targetAsMap, nil 899 } 900 901 func getSegmentIdsToCopy(data []openrtb2.Data, segTaxValues []int) []string { 902 var segmentIdsToCopy = make([]string, 0, len(data)) 903 904 for _, dataRecord := range data { 905 if dataRecord.Ext != nil { 906 var dataExtObject rubiconDataExt 907 err := json.Unmarshal(dataRecord.Ext, &dataExtObject) 908 if err != nil { 909 continue 910 } 911 if contains(segTaxValues, dataExtObject.SegTax) { 912 for _, segment := range dataRecord.Segment { 913 segmentIdsToCopy = append(segmentIdsToCopy, segment.ID) 914 } 915 } 916 } 917 } 918 return segmentIdsToCopy 919 } 920 921 func contains(s []int, e int) bool { 922 for _, a := range s { 923 if a == e { 924 return true 925 } 926 } 927 return false 928 } 929 930 func extractUserBuyerUID(eids []openrtb2.EID) string { 931 for _, eid := range eids { 932 if eid.Source != "rubiconproject.com" { 933 continue 934 } 935 936 for _, uid := range eid.UIDs { 937 return uid.ID 938 } 939 } 940 941 return "" 942 } 943 944 func getSegments(eids []openrtb2.EID) (mappedRubiconUidsParam, []error) { 945 rubiconUidsParam := mappedRubiconUidsParam{ 946 segments: make([]string, 0), 947 } 948 errs := make([]error, 0) 949 950 for _, eid := range eids { 951 switch eid.Source { 952 case "liveintent.com": 953 uids := eid.UIDs 954 if len(uids) > 0 { 955 if eid.Ext != nil { 956 var eidExt rubiconUserExtEidExt 957 if err := json.Unmarshal(eid.Ext, &eidExt); err != nil { 958 errs = append(errs, &errortypes.BadInput{ 959 Message: err.Error(), 960 }) 961 continue 962 } 963 rubiconUidsParam.segments = eidExt.Segments 964 } 965 } 966 case "liveramp.com": 967 uids := eid.UIDs 968 if len(uids) > 0 { 969 uidId := uids[0].ID 970 if uidId != "" && rubiconUidsParam.liverampIdl == "" { 971 rubiconUidsParam.liverampIdl = uidId 972 } 973 } 974 } 975 } 976 977 return rubiconUidsParam, errs 978 } 979 980 func updateUserExtWithSegments(userExtRP *rubiconUserExt, rubiconUidsParam mappedRubiconUidsParam) error { 981 if len(rubiconUidsParam.segments) > 0 { 982 983 if rubiconUidsParam.segments != nil { 984 userExtRPTarget := make(map[string]interface{}) 985 986 if userExtRP.RP.Target != nil { 987 if err := json.Unmarshal(userExtRP.RP.Target, &userExtRPTarget); err != nil { 988 return &errortypes.BadInput{Message: err.Error()} 989 } 990 } 991 992 userExtRPTarget["LIseg"] = rubiconUidsParam.segments 993 994 if target, err := json.Marshal(&userExtRPTarget); err != nil { 995 return &errortypes.BadInput{Message: err.Error()} 996 } else { 997 userExtRP.RP.Target = target 998 } 999 } 1000 } 1001 return nil 1002 } 1003 1004 func isVideo(imp openrtb2.Imp) bool { 1005 video := imp.Video 1006 if video != nil { 1007 // Do any other media types exist? Or check required video fields. 1008 return imp.Banner == nil || isFullyPopulatedVideo(video) 1009 } 1010 return false 1011 } 1012 1013 func isFullyPopulatedVideo(video *openrtb2.Video) bool { 1014 // These are just recommended video fields for XAPI 1015 return video.MIMEs != nil && video.Protocols != nil && video.MaxDuration != 0 && video.Linearity != 0 && video.API != nil 1016 } 1017 1018 func resolveNativeObject(native *openrtb2.Native, target map[string]interface{}) (*openrtb2.Native, error) { 1019 if native == nil { 1020 return nil, fmt.Errorf("Native object is not present for request") 1021 } 1022 ver := native.Ver 1023 if ver == "1.0" || ver == "1.1" { 1024 return native, nil 1025 } 1026 1027 err := json.Unmarshal([]byte(native.Request), &target) 1028 if err != nil { 1029 return nil, err 1030 } 1031 1032 if _, ok := target["eventtrackers"].([]interface{}); !ok { 1033 return nil, fmt.Errorf("Eventtrackers are not present or not of array type") 1034 } 1035 1036 context := target["context"] 1037 if context != nil { 1038 if _, ok := context.(float64); !ok { 1039 return nil, fmt.Errorf("Context is not of int type") 1040 } 1041 } 1042 1043 if _, ok := target["plcmttype"].(float64); !ok { 1044 return nil, fmt.Errorf("Plcmttype is not present or not of int type") 1045 } 1046 1047 return native, nil 1048 } 1049 1050 func setImpNative(jsonData []byte, requestNative map[string]interface{}) ([]byte, error) { 1051 var jsonMap map[string]interface{} 1052 if err := json.Unmarshal(jsonData, &jsonMap); err != nil { 1053 return jsonData, err 1054 } 1055 1056 var impMap map[string]interface{} 1057 if impSlice, ok := maputil.ReadEmbeddedSlice(jsonMap, "imp"); !ok { 1058 return jsonData, fmt.Errorf("unable to find imp in json data") 1059 } else if len(impSlice) == 0 { 1060 return jsonData, fmt.Errorf("unable to find imp[0] in json data") 1061 } else if impMap, ok = impSlice[0].(map[string]interface{}); !ok { 1062 return jsonData, fmt.Errorf("unexpected type for imp[0] found in json data") 1063 } 1064 1065 nativeMap, ok := maputil.ReadEmbeddedMap(impMap, "native") 1066 if !ok { 1067 return jsonData, fmt.Errorf("unable to find imp[0].native in json data") 1068 } 1069 1070 nativeMap["request_native"] = requestNative 1071 1072 if jsonReEncoded, err := json.Marshal(jsonMap); err == nil { 1073 return jsonReEncoded, nil 1074 } else { 1075 return nil, fmt.Errorf("unable to encode json data (%v)", err) 1076 } 1077 } 1078 1079 func (a *RubiconAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { 1080 if response.StatusCode == http.StatusNoContent { 1081 return nil, nil 1082 } 1083 1084 if response.StatusCode == http.StatusBadRequest { 1085 return nil, []error{&errortypes.BadInput{ 1086 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 1087 }} 1088 } 1089 1090 if response.StatusCode != http.StatusOK { 1091 return nil, []error{&errortypes.BadServerResponse{ 1092 Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode), 1093 }} 1094 } 1095 1096 var bidResp rubiconBidResponse 1097 if err := json.Unmarshal(response.Body, &bidResp); err != nil { 1098 return nil, []error{&errortypes.BadServerResponse{ 1099 Message: err.Error(), 1100 }} 1101 } 1102 1103 var bidReq openrtb2.BidRequest 1104 if err := json.Unmarshal(externalRequest.Body, &bidReq); err != nil { 1105 return nil, []error{err} 1106 } 1107 1108 bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) 1109 1110 bidType := openrtb_ext.BidTypeNative 1111 1112 isVideo := isVideo(bidReq.Imp[0]) 1113 if isVideo { 1114 bidType = openrtb_ext.BidTypeVideo 1115 } else if bidReq.Imp[0].Banner != nil { 1116 bidType = openrtb_ext.BidTypeBanner 1117 } 1118 1119 impToCpmOverride := mapImpIdToCpmOverride(internalRequest.Imp) 1120 cmpOverride := cmpOverrideFromBidRequest(internalRequest) 1121 1122 for _, sb := range bidResp.SeatBid { 1123 buyer, err := strconv.Atoi(sb.Buyer) 1124 if err != nil { 1125 buyer = 0 1126 } 1127 for i := 0; i < len(sb.Bid); i++ { 1128 bid := sb.Bid[i] 1129 1130 updatedBidExt := updateBidExtWithMetaNetworkId(bid, buyer) 1131 if updatedBidExt != nil { 1132 bid.Ext = updatedBidExt 1133 } 1134 bidCmpOverride, ok := impToCpmOverride[bid.ImpID] 1135 if !ok || bidCmpOverride == 0 { 1136 bidCmpOverride = cmpOverride 1137 } 1138 1139 if bidCmpOverride > 0 { 1140 bid.Price = bidCmpOverride 1141 } 1142 1143 if bid.Price != 0 { 1144 // Since Rubicon XAPI returns only one bid per response 1145 // copy response.bidid to openrtb_response.seatbid.bid.bidid 1146 if bid.ID == "0" { 1147 bid.ID = bidResp.BidID 1148 } 1149 1150 resolvedAdm := resolveAdm(bid) 1151 if len(resolvedAdm) > 0 { 1152 bid.AdM = resolvedAdm 1153 } 1154 1155 var ortbBid openrtb2.Bid // `targetStruct` can be anything of your choice 1156 1157 rubiconBidAsBytes, _ := json.Marshal(bid) 1158 if len(rubiconBidAsBytes) > 0 { 1159 err = json.Unmarshal(rubiconBidAsBytes, &ortbBid) 1160 if err != nil { 1161 return nil, []error{err} 1162 } 1163 } 1164 1165 bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ 1166 Bid: &ortbBid, 1167 BidType: bidType, 1168 }) 1169 } 1170 } 1171 } 1172 if bidResp.Cur != "" { 1173 bidResponse.Currency = bidResp.Cur 1174 } 1175 1176 return bidResponse, nil 1177 } 1178 1179 func mapImpIdToCpmOverride(imps []openrtb2.Imp) map[string]float64 { 1180 impIdToCmpOverride := make(map[string]float64) 1181 for _, imp := range imps { 1182 var bidderExt adapters.ExtImpBidder 1183 if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { 1184 continue 1185 } 1186 1187 var rubiconExt openrtb_ext.ExtImpRubicon 1188 if err := json.Unmarshal(bidderExt.Bidder, &rubiconExt); err != nil { 1189 continue 1190 } 1191 1192 impIdToCmpOverride[imp.ID] = rubiconExt.Debug.CpmOverride 1193 } 1194 return impIdToCmpOverride 1195 } 1196 1197 func resolveAdm(bid rubiconBid) string { 1198 var bidAdm = bid.AdM 1199 if len(bidAdm) > 0 { 1200 return bidAdm 1201 } 1202 1203 admObject := bid.AdmNative 1204 admObjectAsBytes, err := json.Marshal(&admObject) 1205 if err != nil { 1206 return "" 1207 } 1208 1209 return string(admObjectAsBytes) 1210 } 1211 1212 func cmpOverrideFromBidRequest(bidRequest *openrtb2.BidRequest) float64 { 1213 var bidRequestExt bidRequestExt 1214 if err := json.Unmarshal(bidRequest.Ext, &bidRequestExt); err != nil { 1215 return 0 1216 } 1217 1218 return bidRequestExt.Prebid.Bidders.Rubicon.Debug.CpmOverride 1219 } 1220 1221 func updateBidExtWithMetaNetworkId(bid rubiconBid, buyer int) json.RawMessage { 1222 if buyer <= 0 { 1223 return nil 1224 } 1225 var bidExt *extPrebid 1226 if bid.Ext != nil { 1227 if err := json.Unmarshal(bid.Ext, &bidExt); err != nil { 1228 return nil 1229 } 1230 } 1231 1232 if bidExt != nil { 1233 if bidExt.Prebid != nil { 1234 if bidExt.Prebid.Meta != nil { 1235 bidExt.Prebid.Meta.NetworkID = buyer 1236 } else { 1237 bidExt.Prebid.Meta = &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer} 1238 } 1239 } else { 1240 bidExt.Prebid = &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}} 1241 } 1242 } else { 1243 bidExt = &extPrebid{Prebid: &openrtb_ext.ExtBidPrebid{Meta: &openrtb_ext.ExtBidPrebidMeta{NetworkID: buyer}}} 1244 } 1245 1246 marshalledExt, err := json.Marshal(&bidExt) 1247 if err == nil { 1248 return marshalledExt 1249 } 1250 return nil 1251 }