Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add zeroclickfraud adapter #1207

Merged
merged 3 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions adapters/zeroclickfraud/usersync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zeroclickfraud

import (
"text/template"

"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/usersync"
)

func NewZeroClickFraudSyncer(temp *template.Template) usersync.Usersyncer {
return adapters.NewSyncer("zeroclickfraud", 0, temp, adapters.SyncTypeIframe)
}
34 changes: 34 additions & 0 deletions adapters/zeroclickfraud/usersync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package zeroclickfraud

import (
"testing"
"text/template"

"github.com/prebid/prebid-server/privacy"
"github.com/prebid/prebid-server/privacy/ccpa"
"github.com/prebid/prebid-server/privacy/gdpr"
"github.com/stretchr/testify/assert"
)

func TestZeroClickFraudSyncer(t *testing.T) {
syncURL := "https://s.0cf.io/sync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D{{.GDPR}}%26gdpr_consent%3D{{.GDPRConsent}}%26uid%3D%24%7Buid%7D"
syncURLTemplate := template.Must(
template.New("sync-template").Parse(syncURL),
)

syncer := NewZeroClickFraudSyncer(syncURLTemplate)
syncInfo, err := syncer.GetUsersyncInfo(privacy.Policies{
GDPR: gdpr.Policy{
Signal: "1",
Consent: "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw",
},
CCPA: ccpa.Policy{
Value: "1NYN",
},
})

assert.NoError(t, err)
assert.Equal(t, "https://s.0cf.io/sync?gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw&us_privacy=1NYN&r=https%3A%2F%2Flocalhost%3A8888%2Fsetuid%3Fbidder%3Dzeroclickfraud%26gdpr%3D1%26gdpr_consent%3DBONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw%26uid%3D%24%7Buid%7D", syncInfo.URL)
assert.Equal(t, "iframe", syncInfo.Type)
assert.Equal(t, false, syncInfo.SupportCORS)
}
187 changes: 187 additions & 0 deletions adapters/zeroclickfraud/zeroclickfraud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package zeroclickfraud

import (
"encoding/json"
"fmt"
"github.com/golang/glog"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/macros"
"github.com/prebid/prebid-server/openrtb_ext"
"net/http"
"strconv"
"text/template"
)

type ZeroClickFraudAdapter struct {
EndpointTemplate template.Template
}

func (a *ZeroClickFraudAdapter) MakeRequests(request *openrtb.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {

errs := make([]error, 0, len(request.Imp))
headers := http.Header{
"Content-Type": {"application/json"},
"Accept": {"application/json"},
}

// Pull the host and source ID info from the bidder params.
reqImps, err := splitImpressions(request.Imp)

if err != nil {
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
errs = append(errs, err)
}

requests := []*adapters.RequestData{}

for reqExt, reqImp := range reqImps {
request.Imp = reqImp
reqJson, err := json.Marshal(request)

if err != nil {
errs = append(errs, err)
continue
}

urlParams := macros.EndpointTemplateParams{Host: reqExt.Host, SourceId: strconv.Itoa(reqExt.SourceId)}
url, err := macros.ResolveMacros(a.EndpointTemplate, urlParams)

if err != nil {
errs = append(errs, err)
continue
}

request := adapters.RequestData{
Method: "POST",
Uri: url,
Body: reqJson,
Headers: headers}

requests = append(requests, &request)
}

return requests, errs
}

/*
internal original request in OpenRTB, external = result of us having converted it (what comes out of MakeRequests)
*/
func (a *ZeroClickFraudAdapter) MakeBids(
internalRequest *openrtb.BidRequest,
externalRequest *adapters.RequestData,
response *adapters.ResponseData,
) (*adapters.BidderResponse, []error) {

if response.StatusCode == http.StatusNoContent {
return nil, nil
}

SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
if response.StatusCode == http.StatusBadRequest {
return nil, []error{&errortypes.BadInput{
Message: fmt.Sprintf("ERR, bad input %d", response.StatusCode),
}}
} else if response.StatusCode != http.StatusOK {
return nil, []error{&errortypes.BadServerResponse{
Message: fmt.Sprintf("ERR, response with status %d", response.StatusCode),
}}
}

var bidResp openrtb.BidResponse

if err := json.Unmarshal(response.Body, &bidResp); err != nil {
return nil, []error{err}
}

bidResponse := adapters.NewBidderResponse()
bidResponse.Currency = bidResp.Cur

for _, seatBid := range bidResp.SeatBid {
for i := 0; i < len(seatBid.Bid); i++ {
bid := seatBid.Bid[i]
bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
Bid: &bid,
BidType: getMediaType(bid.ImpID, internalRequest.Imp),
})
}
}

return bidResponse, nil
}

func splitImpressions(imps []openrtb.Imp) (map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp, error) {

var m = make(map[openrtb_ext.ExtImpZeroClickFraud][]openrtb.Imp)

for _, imp := range imps {
bidderParams, err := getBidderParams(&imp)
if err != nil {
return nil, err
}

m[*bidderParams] = append(m[*bidderParams], imp)
}

return m, nil
}

func getBidderParams(imp *openrtb.Imp) (*openrtb_ext.ExtImpZeroClickFraud, error) {
var bidderExt adapters.ExtImpBidder
if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("Missing bidder ext: %s", err.Error()),
}
}
var zeroclickfraudExt openrtb_ext.ExtImpZeroClickFraud
if err := json.Unmarshal(bidderExt.Bidder, &zeroclickfraudExt); err != nil {
return nil, &errortypes.BadInput{
Message: fmt.Sprintf("Cannot Resolve host or sourceId: %s", err.Error()),
}
}

if zeroclickfraudExt.SourceId < 1 {
return nil, &errortypes.BadInput{
Message: "Invalid/Missing SourceId",
}
}

if len(zeroclickfraudExt.Host) < 1 {
return nil, &errortypes.BadInput{
Message: "Invalid/Missing Host",
}
}

return &zeroclickfraudExt, nil
}

func getMediaType(impID string, imps []openrtb.Imp) openrtb_ext.BidType {

bidType := openrtb_ext.BidTypeBanner

for _, imp := range imps {
if imp.ID == impID {
if imp.Video != nil {
bidType = openrtb_ext.BidTypeVideo
break
} else if imp.Native != nil {
bidType = openrtb_ext.BidTypeNative
break
} else {
bidType = openrtb_ext.BidTypeBanner
break
}
}
}

return bidType
}

func NewZeroClickFraudBidder(endpoint string) *ZeroClickFraudAdapter {
template, err := template.New("endpointTemplate").Parse(endpoint)
if err != nil {
glog.Fatal("Unable to parse endpoint url template")
return nil
}

return &ZeroClickFraudAdapter{EndpointTemplate: *template}
}
11 changes: 11 additions & 0 deletions adapters/zeroclickfraud/zeroclickfraud_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zeroclickfraud

import (
"testing"

"github.com/prebid/prebid-server/adapters/adapterstest"
)

func TestJsonSamples(t *testing.T) {
adapterstest.RunJSONBidderTest(t, "zeroclickfraudtest", NewZeroClickFraudBidder("http://{{.Host}}/openrtb2?sid={{.SourceId}}"))
}
Loading