From dd5f6ed753a5d9e26cc1ef8843358d318eddf7fb Mon Sep 17 00:00:00 2001 From: cchang320 Date: Thu, 6 Dec 2018 10:26:14 -0800 Subject: [PATCH] Adding GumGum server adapter (#748) * gumgum adapter implementation * removing device info params since newest prebid client started sending over device info * secure endpoints for gumgum * removing app from gumgum bidder info --- adapters/gumgum/gumgum.go | 135 ++++++++++++++++++ adapters/gumgum/gumgum_test.go | 10 ++ .../gumgum/gumgumtest/exemplary/banner.json | 99 +++++++++++++ .../gumgum/gumgumtest/params/race/banner.json | 3 + adapters/gumgum/params_test.go | 52 +++++++ adapters/gumgum/usersync.go | 18 +++ adapters/gumgum/usersync_test.go | 22 +++ config/config.go | 2 + exchange/adapter_map.go | 2 + openrtb_ext/bidders.go | 2 + openrtb_ext/imp_gumgum.go | 5 + static/bidder-info/gumgum.yaml | 6 + static/bidder-params/gumgum.json | 14 ++ usersync/usersyncers/syncer.go | 2 + usersync/usersyncers/syncer_test.go | 1 + 15 files changed, 373 insertions(+) create mode 100644 adapters/gumgum/gumgum.go create mode 100644 adapters/gumgum/gumgum_test.go create mode 100644 adapters/gumgum/gumgumtest/exemplary/banner.json create mode 100644 adapters/gumgum/gumgumtest/params/race/banner.json create mode 100644 adapters/gumgum/params_test.go create mode 100644 adapters/gumgum/usersync.go create mode 100644 adapters/gumgum/usersync_test.go create mode 100644 openrtb_ext/imp_gumgum.go create mode 100644 static/bidder-info/gumgum.yaml create mode 100644 static/bidder-params/gumgum.json diff --git a/adapters/gumgum/gumgum.go b/adapters/gumgum/gumgum.go new file mode 100644 index 00000000000..97496e0e716 --- /dev/null +++ b/adapters/gumgum/gumgum.go @@ -0,0 +1,135 @@ +package gumgum + +import ( + "encoding/json" + "fmt" + "github.com/mxmCherry/openrtb" + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/errortypes" + "github.com/prebid/prebid-server/openrtb_ext" + "net/http" +) + +type GumGumAdapter struct { + URI string +} + +func (g *GumGumAdapter) MakeRequests(request *openrtb.BidRequest) ([]*adapters.RequestData, []error) { + var validImps []openrtb.Imp + var trackingId string + + numRequests := len(request.Imp) + errs := make([]error, 0, numRequests) + + for i := 0; i < numRequests; i++ { + imp := request.Imp[i] + zone, err := preprocess(&imp) + if err != nil { + errs = append(errs, err) + } else { + if request.Imp[i].Banner != nil { + bannerCopy := *request.Imp[i].Banner + if bannerCopy.W == nil && bannerCopy.H == nil && len(bannerCopy.Format) > 0 { + format := bannerCopy.Format[0] + bannerCopy.W = &(format.W) + bannerCopy.H = &(format.H) + } + request.Imp[i].Banner = &bannerCopy + validImps = append(validImps, request.Imp[i]) + trackingId = zone + } + } + } + + if len(validImps) == 0 { + return nil, errs + } + + request.Imp = validImps + + if request.Site != nil { + siteCopy := *request.Site + siteCopy.ID = trackingId + request.Site = &siteCopy + } + + reqJSON, err := json.Marshal(request) + if err != nil { + errs = append(errs, err) + return nil, errs + } + + headers := http.Header{} + headers.Add("Content-Type", "application/json;charset=utf-8") + headers.Add("Accept", "application/json") + return []*adapters.RequestData{{ + Method: "POST", + Uri: g.URI, + Body: reqJSON, + Headers: headers, + }}, errs +} + +func (g *GumGumAdapter) MakeBids(internalRequest *openrtb.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) { + if response.StatusCode == http.StatusNoContent { + return nil, nil + } + + if response.StatusCode == http.StatusBadRequest { + return nil, []error{&errortypes.BadInput{ + Message: fmt.Sprintf("Bad user input: HTTP status %d", response.StatusCode), + }} + } + + if response.StatusCode != http.StatusOK { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: HTTP status %d", response.StatusCode), + }} + } + var bidResp openrtb.BidResponse + if err := json.Unmarshal(response.Body, &bidResp); err != nil { + return nil, []error{&errortypes.BadServerResponse{ + Message: fmt.Sprintf("Bad server response: %d. ", err), + }} + } + + var errs []error + bidResponse := adapters.NewBidderResponseWithBidsCapacity(5) + + for _, sb := range bidResp.SeatBid { + for i := range sb.Bid { + bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{ + Bid: &sb.Bid[i], + BidType: openrtb_ext.BidTypeBanner, + }) + } + } + return bidResponse, errs +} + +func preprocess(imp *openrtb.Imp) (string, error) { + var bidderExt adapters.ExtImpBidder + if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + var gumgumExt openrtb_ext.ExtImpGumGum + if err := json.Unmarshal(bidderExt.Bidder, &gumgumExt); err != nil { + err = &errortypes.BadInput{ + Message: err.Error(), + } + return "", err + } + + zone := gumgumExt.Zone + return zone, nil +} + +func NewGumGumBidder(endpoint string) *GumGumAdapter { + return &GumGumAdapter{ + URI: endpoint, + } +} diff --git a/adapters/gumgum/gumgum_test.go b/adapters/gumgum/gumgum_test.go new file mode 100644 index 00000000000..f87c8cf6216 --- /dev/null +++ b/adapters/gumgum/gumgum_test.go @@ -0,0 +1,10 @@ +package gumgum + +import ( + "github.com/prebid/prebid-server/adapters/adapterstest" + "testing" +) + +func TestJsonSamples(t *testing.T) { + adapterstest.RunJSONBidderTest(t, "gumgumtest", NewGumGumBidder("https://g2.gumgum.com/providers/prbds2s/bid")) +} diff --git a/adapters/gumgum/gumgumtest/exemplary/banner.json b/adapters/gumgum/gumgumtest/exemplary/banner.json new file mode 100644 index 00000000000..2fbd3da22da --- /dev/null +++ b/adapters/gumgum/gumgumtest/exemplary/banner.json @@ -0,0 +1,99 @@ +{ + "mockBidRequest": { + "id": "test-request-id", + "imp": [ + { + "id": "test-imp-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 300 + } + ], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + } + ] + }, + + "httpCalls": [ + { + "expectedRequest": { + "uri": "https://g2.gumgum.com/providers/prbds2s/bid", + "body":{ + "id": "test-request-id", + "imp": [{ + "id": "test-imp-id", + "banner": { + "format": [{ + "w": 300, + "h": 250 + }, { + "w": 300, + "h": 300 + }], + "w": 300, + "h": 250 + }, + "ext": { + "bidder": { + "zone": "dc9d6be1" + } + } + }] + } + }, + "mockResponse": { + "status": 200, + "body": { + "seatbid": [ + { + "bid": [ + { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + } + ] + } + ] + } + } + } + ], + + "expectedBidResponses": [ + { + "currency": "USD", + "bids": [ + { + "bid": { + "crid": "2068416", + "adm": "some-test-ad", + "adid": "2068416", + "price": 5, + "id": "5736a50b-6b05-42a8-aa6d-b0a4649dcd05", + "impid": "test-imp-id", + "cid": "4747" + }, + "type": "banner" + } + ] + } + ] +} diff --git a/adapters/gumgum/gumgumtest/params/race/banner.json b/adapters/gumgum/gumgumtest/params/race/banner.json new file mode 100644 index 00000000000..6e222304f36 --- /dev/null +++ b/adapters/gumgum/gumgumtest/params/race/banner.json @@ -0,0 +1,3 @@ +{ + "zone": "dc9d6be1" +} diff --git a/adapters/gumgum/params_test.go b/adapters/gumgum/params_test.go new file mode 100644 index 00000000000..4cb6f019197 --- /dev/null +++ b/adapters/gumgum/params_test.go @@ -0,0 +1,52 @@ +package gumgum + +import ( + "encoding/json" + "github.com/prebid/prebid-server/openrtb_ext" + "testing" +) + +func TestValidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, validParam := range validParams { + if err := validator.Validate(openrtb_ext.BidderGumGum, json.RawMessage(validParam)); err != nil { + t.Errorf("Schema rejected gumgum params: %s", validParam) + } + } +} + +func TestInvalidParams(t *testing.T) { + validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params") + if err != nil { + t.Fatalf("Failed to fetch the json-schemas. %v", err) + } + + for _, invalidParam := range invalidParams { + if err := validator.Validate(openrtb_ext.BidderGumGum, json.RawMessage(invalidParam)); err == nil { + t.Errorf("Schema allowed unexpected params: %s", invalidParam) + } + } +} + +var validParams = []string{ + `{"zone":"dc9d6be1"}`, +} + +var invalidParams = []string{ + `null`, + `nil`, + ``, + `{}`, + `[]`, + `true`, + `2`, + `{"zone":12345678}`, + `{"zone":""}`, + `{"placementId": 1}`, + `{"zone": true}`, + `{"placementId": 1, "zone":"1234567"}`, +} diff --git a/adapters/gumgum/usersync.go b/adapters/gumgum/usersync.go new file mode 100644 index 00000000000..226398d29b2 --- /dev/null +++ b/adapters/gumgum/usersync.go @@ -0,0 +1,18 @@ +package gumgum + +import ( + "github.com/prebid/prebid-server/adapters" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/usersync" + + "net/url" + "strings" +) + +func NewGumGumSyncer(cfg *config.Configuration) usersync.Usersyncer { + externalURL := strings.TrimRight(cfg.ExternalURL, "/") + redirectURI := url.QueryEscape(externalURL) + "%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D{{gdpr}}%26gdpr_consent%3D{{gdpr_consent}}%26uid%3D" + usersyncURL := cfg.Adapters[string(openrtb_ext.BidderGumGum)].UserSyncURL + return adapters.NewSyncer("gumgum", 61, adapters.ResolveMacros(usersyncURL+redirectURI), adapters.SyncTypeIframe) +} diff --git a/adapters/gumgum/usersync_test.go b/adapters/gumgum/usersync_test.go new file mode 100644 index 00000000000..955e7640cee --- /dev/null +++ b/adapters/gumgum/usersync_test.go @@ -0,0 +1,22 @@ +package gumgum + +import ( + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "github.com/stretchr/testify/assert" + + "testing" +) + +func TestGumGumSyncer(t *testing.T) { + syncer := NewGumGumSyncer(&config.Configuration{ExternalURL: "localhost", Adapters: map[string]config.Adapter{ + string(openrtb_ext.BidderGumGum): { + UserSyncURL: "https://rtb.gumgum.com/usync/prbds2s?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&r=", + }, + }}) + u := syncer.GetUsersyncInfo("1", "BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA") + assert.Equal(t, "https://rtb.gumgum.com/usync/prbds2s?gdpr=1&gdpr_consent=BOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA&r=localhost%2Fsetuid%3Fbidder%3Dgumgum%26gdpr%3D1%26gdpr_consent%3DBOPVK28OVJoTBABABAENBs-AAAAhuAKAANAAoACwAGgAPAAxAB0AHgAQAAiABOADkA%26uid%3D", u.URL) + assert.Equal(t, "iframe", u.Type) + assert.Equal(t, uint16(61), syncer.GDPRVendorID()) + assert.Equal(t, false, u.SupportCORS) +} diff --git a/config/config.go b/config/config.go index 8f47bdeaa15..2a9602a8642 100644 --- a/config/config.go +++ b/config/config.go @@ -382,6 +382,8 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("adapters.33across.endpoint", "http://ssc.33across.com/api/v1/hb") v.SetDefault("adapters.rhythmone.endpoint", "http://tag.1rx.io/rmp") v.SetDefault("adapters.rhythmone.usersync_url", "//sync.1rx.io/usersync2/rmphb?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&redir=") + v.SetDefault("adapters.gumgum.endpoint", "https://g2.gumgum.com/providers/prbds2s/bid") + v.SetDefault("adapters.gumgum.usersync_url", "https://rtb.gumgum.com/usync/prbds2s?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&r=") v.SetDefault("max_request_size", 1024*256) v.SetDefault("analytics.file.filename", "") diff --git a/exchange/adapter_map.go b/exchange/adapter_map.go index 35bad33e7f4..d7ee526c321 100644 --- a/exchange/adapter_map.go +++ b/exchange/adapter_map.go @@ -15,6 +15,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/eplanning" + "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/openx" @@ -41,6 +42,7 @@ func newAdapterMap(client *http.Client, cfg *config.Configuration, infos adapter openrtb_ext.BidderBeachfront: beachfront.NewBeachfrontBidder(), openrtb_ext.BidderBrightroll: brightroll.NewBrightrollBidder(cfg.Adapters[string(openrtb_ext.BidderBrightroll)].Endpoint), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningBidder(client, cfg.Adapters[string(openrtb_ext.BidderEPlanning)].Endpoint), + openrtb_ext.BidderGumGum: gumgum.NewGumGumBidder(cfg.Adapters[string(openrtb_ext.BidderGumGum)].Endpoint), openrtb_ext.BidderOpenx: openx.NewOpenxBidder(cfg.Adapters[string(openrtb_ext.BidderOpenx)].Endpoint), openrtb_ext.BidderPubmatic: pubmatic.NewPubmaticBidder(client, cfg.Adapters[string(openrtb_ext.BidderPubmatic)].Endpoint), openrtb_ext.BidderRhythmone: rhythmone.NewRhythmoneBidder(cfg.Adapters[string(openrtb_ext.BidderRhythmone)].Endpoint), diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go index 2f5eeeaad59..7b42d7be586 100644 --- a/openrtb_ext/bidders.go +++ b/openrtb_ext/bidders.go @@ -29,6 +29,7 @@ const ( BidderConversant BidderName = "conversant" BidderEPlanning BidderName = "eplanning" BidderFacebook BidderName = "audienceNetwork" + BidderGumGum BidderName = "gumgum" BidderIx BidderName = "ix" BidderLifestreet BidderName = "lifestreet" BidderOpenx BidderName = "openx" @@ -52,6 +53,7 @@ var BidderMap = map[string]BidderName{ "brightroll": BidderBrightroll, "conversant": BidderConversant, "eplanning": BidderEPlanning, + "gumgum": BidderGumGum, "ix": BidderIx, "lifestreet": BidderLifestreet, "openx": BidderOpenx, diff --git a/openrtb_ext/imp_gumgum.go b/openrtb_ext/imp_gumgum.go new file mode 100644 index 00000000000..7959e1cccc3 --- /dev/null +++ b/openrtb_ext/imp_gumgum.go @@ -0,0 +1,5 @@ +package openrtb_ext + +type ExtImpGumGum struct { + Zone string `json:"zone"` +} diff --git a/static/bidder-info/gumgum.yaml b/static/bidder-info/gumgum.yaml new file mode 100644 index 00000000000..b8a3981c9f0 --- /dev/null +++ b/static/bidder-info/gumgum.yaml @@ -0,0 +1,6 @@ +maintainer: + email: "pubtech@gumgum.com" +capabilities: + site: + mediaTypes: + - banner diff --git a/static/bidder-params/gumgum.json b/static/bidder-params/gumgum.json new file mode 100644 index 00000000000..5ec2b8e0cbf --- /dev/null +++ b/static/bidder-params/gumgum.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "GumGum Adapter Params", + "description": "A schema which validates params accepted by the GumGum adapter", + "type": "object", + "properties": { + "zone": { + "type": "string", + "description": "A tracking id used to identify GumGum zone.", + "minLength": 8 + } + }, + "required": ["zone"] +} diff --git a/usersync/usersyncers/syncer.go b/usersync/usersyncers/syncer.go index 9dc0dd689b6..52c92d9e38d 100644 --- a/usersync/usersyncers/syncer.go +++ b/usersync/usersyncers/syncer.go @@ -11,6 +11,7 @@ import ( "github.com/prebid/prebid-server/adapters/brightroll" "github.com/prebid/prebid-server/adapters/conversant" "github.com/prebid/prebid-server/adapters/eplanning" + "github.com/prebid/prebid-server/adapters/gumgum" "github.com/prebid/prebid-server/adapters/ix" "github.com/prebid/prebid-server/adapters/lifestreet" "github.com/prebid/prebid-server/adapters/openx" @@ -39,6 +40,7 @@ func NewSyncerMap(cfg *config.Configuration) map[openrtb_ext.BidderName]usersync openrtb_ext.BidderBrightroll: brightroll.NewBrightrollSyncer(cfg), openrtb_ext.BidderConversant: conversant.NewConversantSyncer(cfg), openrtb_ext.BidderEPlanning: eplanning.NewEPlanningSyncer(cfg), + openrtb_ext.BidderGumGum: gumgum.NewGumGumSyncer(cfg), openrtb_ext.BidderIx: ix.NewIxSyncer(cfg), openrtb_ext.BidderLifestreet: lifestreet.NewLifestreetSyncer(cfg), openrtb_ext.BidderOpenx: openx.NewOpenxSyncer(cfg), diff --git a/usersync/usersyncers/syncer_test.go b/usersync/usersyncers/syncer_test.go index a9684ead385..fd1387c584a 100644 --- a/usersync/usersyncers/syncer_test.go +++ b/usersync/usersyncers/syncer_test.go @@ -19,6 +19,7 @@ func TestNewSyncerMap(t *testing.T) { string(openrtb_ext.BidderBrightroll): {}, string(openrtb_ext.BidderConversant): {}, string(openrtb_ext.BidderEPlanning): {}, + string(openrtb_ext.BidderGumGum): {}, string(openrtb_ext.BidderIx): {}, string(openrtb_ext.BidderLifestreet): {}, string(openrtb_ext.BidderOpenx): {},