Skip to content

Commit

Permalink
Add COPPA support (#973)
Browse files Browse the repository at this point in the history
  • Loading branch information
mansinahar authored Aug 5, 2019
1 parent 4c783b7 commit af33780
Show file tree
Hide file tree
Showing 5 changed files with 436 additions and 179 deletions.
7 changes: 4 additions & 3 deletions exchange/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/prebid/prebid-server/stored_requests"
"github.com/prebid/prebid-server/stored_requests/backends/file_fetcher"
"io/ioutil"
"net/http"
"net/http/httptest"
Expand All @@ -19,6 +17,8 @@ import (
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/currencies"
"github.com/prebid/prebid-server/prebid_cache_client"
"github.com/prebid/prebid-server/stored_requests"
"github.com/prebid/prebid-server/stored_requests/backends/file_fetcher"

"github.com/buger/jsonparser"
"github.com/mxmCherry/openrtb"
Expand Down Expand Up @@ -218,7 +218,8 @@ func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest {
Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
},
Regs: &openrtb.Regs{
Ext: json.RawMessage(`{"gdpr":1}`),
COPPA: 1,
Ext: json.RawMessage(`{"gdpr":1}`),
},
Imp: []openrtb.Imp{{
ID: "some-imp-id",
Expand Down
55 changes: 0 additions & 55 deletions exchange/gdpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package exchange

import (
"encoding/json"
"strings"

"github.com/mxmCherry/openrtb"
)
Expand Down Expand Up @@ -47,57 +46,3 @@ type userExt struct {
type regsExt struct {
GDPR *int `json:"gdpr,omitempty"`
}

// cleanPI removes IP address last byte, device ID, buyer ID, and rounds off latitude/longitude
func cleanPI(bidRequest *openrtb.BidRequest, isAMP bool) {
if bidRequest.User != nil {
// Need to duplicate pointer objects
user := *bidRequest.User
bidRequest.User = &user
if !isAMP {
bidRequest.User.BuyerUID = ""
}
bidRequest.User.Geo = cleanGeo(bidRequest.User.Geo)
}
if bidRequest.Device != nil {
// Need to duplicate pointer objects
device := *bidRequest.Device
bidRequest.Device = &device
bidRequest.Device.DIDMD5 = ""
bidRequest.Device.DIDSHA1 = ""
bidRequest.Device.DPIDMD5 = ""
bidRequest.Device.DPIDSHA1 = ""
bidRequest.Device.IP = cleanIP(bidRequest.Device.IP)
bidRequest.Device.IPv6 = cleanIPv6(bidRequest.Device.IPv6)
bidRequest.Device.Geo = cleanGeo(bidRequest.Device.Geo)
}
}

// Zero the last byte of an IP address
func cleanIP(fullIP string) string {
i := strings.LastIndex(fullIP, ".")
if i == -1 {
return ""
}
return fullIP[0:i] + ".0"
}

// Zero the last two bytes of an IPv6 address
func cleanIPv6(fullIP string) string {
i := strings.LastIndex(fullIP, ":")
if i == -1 {
return ""
}
return fullIP[0:i] + ":0000"
}

// Return a cleaned Geo object pointer (round off the latitude/longitude)
func cleanGeo(geo *openrtb.Geo) *openrtb.Geo {
if geo == nil {
return nil
}
newGeo := *geo
newGeo.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0
newGeo.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0
return &newGeo
}
92 changes: 0 additions & 92 deletions exchange/gdpr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,95 +42,3 @@ func TestGDPRUnknown(t *testing.T) {
assert.Equal(t, 0, gdpr)

}

func TestCleanPI(t *testing.T) {
bidReqOrig := openrtb.BidRequest{}

bidReqCopy := bidReqOrig
// Make sure cleanIP handles the empty case
cleanPI(&bidReqCopy, false)

// Add values to clean
bidReqOrig.User = &openrtb.User{
BuyerUID: "abc123",
}
bidReqOrig.Device = &openrtb.Device{
DIDMD5: "teapot",
IP: "12.123.56.128",
IPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
Geo: &openrtb.Geo{
Lat: 123.4567,
Lon: 7.9836,
},
}
// Make a shallow copy
bidReqCopy = bidReqOrig

cleanPI(&bidReqCopy, false)

// Verify cleaned values
assertStringEmpty(t, bidReqCopy.User.BuyerUID)
assertStringEmpty(t, bidReqCopy.Device.DIDMD5)
assert.Equal(t, "12.123.56.0", bidReqCopy.Device.IP)
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:0000", bidReqCopy.Device.IPv6)
assert.Equal(t, 123.46, bidReqCopy.Device.Geo.Lat)
assert.Equal(t, 7.98, bidReqCopy.Device.Geo.Lon)

// verify original untouched, as we want to only modify the cleaned copy for the bidder
assert.Equal(t, "abc123", bidReqOrig.User.BuyerUID)
assert.Equal(t, "teapot", bidReqOrig.Device.DIDMD5)
assert.Equal(t, "12.123.56.128", bidReqOrig.Device.IP)
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", bidReqOrig.Device.IPv6)
assert.Equal(t, 123.4567, bidReqOrig.Device.Geo.Lat)
assert.Equal(t, 7.9836, bidReqOrig.Device.Geo.Lon)

}

func TestCleanPIAmp(t *testing.T) {
bidReqOrig := openrtb.BidRequest{}

bidReqCopy := bidReqOrig
// Make sure cleanIP handles the empty case
cleanPI(&bidReqCopy, false)

// Add values to clean
bidReqOrig.User = &openrtb.User{
BuyerUID: "abc123",
}
bidReqOrig.Device = &openrtb.Device{
DIDMD5: "teapot",
IP: "12.123.56.128",
IPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
Geo: &openrtb.Geo{
Lat: 123.4567,
Lon: 7.9836,
},
}
// Make a shallow copy
bidReqCopy = bidReqOrig

cleanPI(&bidReqCopy, true)

// Verify cleaned values
assert.Equal(t, "abc123", bidReqCopy.User.BuyerUID)
assertStringEmpty(t, bidReqCopy.Device.DIDMD5)
assert.Equal(t, "12.123.56.0", bidReqCopy.Device.IP)
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:0000", bidReqCopy.Device.IPv6)
assert.Equal(t, 123.46, bidReqCopy.Device.Geo.Lat)
assert.Equal(t, 7.98, bidReqCopy.Device.Geo.Lon)
}

func assertStringEmpty(t *testing.T, str string) {
t.Helper()
if str != "" {
t.Errorf("Expected an empty string, got %s", str)
}
}

func TestBadIPs(t *testing.T) {
assertStringEmpty(t, cleanIP("not an IP"))
assertStringEmpty(t, cleanIP(""))
assertStringEmpty(t, cleanIP("36278042"))
assertStringEmpty(t, cleanIPv6("not an IP"))
assertStringEmpty(t, cleanIPv6(""))
}
126 changes: 122 additions & 4 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"fmt"
"math/rand"
"strings"

"github.com/buger/jsonparser"
"github.com/mxmCherry/openrtb"
Expand Down Expand Up @@ -41,16 +42,31 @@ func cleanOpenRTBRequests(ctx context.Context,
// Clean PI from bidrequests if not allowed per GDPR
gdpr := extractGDPR(orig, usersyncIfAmbiguous)
consent := extractConsent(orig)
if gdpr == 1 {
for bidder, bidReq := range requestsByBidder {
// Fixes #820

// Check if it's an AMP request
isAMP := labels.RType == pbsmetrics.ReqTypeAMP

// Check if COPPA applies for this request
var applyCOPPA bool
if orig.Regs != nil && orig.Regs.COPPA == 1 {
applyCOPPA = true
}

for bidder, bidReq := range requestsByBidder {
var applyGDPR bool
// Fixes #820
if gdpr == 1 {
coreBidder := resolveBidder(bidder.String(), aliases)

var publisherID = labels.PubID
if ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent); !ok && err == nil {
cleanPI(bidReq, labels.RType == pbsmetrics.ReqTypeAMP)
applyGDPR = true
}
}

if applyGDPR || applyCOPPA {
applyRegs(bidReq, isAMP, applyGDPR, applyCOPPA)
}
}

return
Expand Down Expand Up @@ -297,3 +313,105 @@ func randomizeList(list []openrtb_ext.BidderName) {
list[i], list[j] = list[j], list[i]
}
}

func applyRegs(bidRequest *openrtb.BidRequest, isAMP, applyGDPR, applyCOPPA bool) {
if bidRequest.User != nil {
// Need to duplicate pointer objects
user := *bidRequest.User
bidRequest.User = &user

// There's no way for AMP to send a GDPR consent string yet so it's hard
// to know if the vendor is consented or not and therefore for AMP requests
// we keep the BuyerUID as is
if !isAMP {
bidRequest.User.BuyerUID = ""
}
if applyCOPPA {
bidRequest.User.ID = ""
bidRequest.User.Yob = 0
bidRequest.User.Gender = ""
bidRequest.User.BuyerUID = ""
}
bidRequest.User.Geo = cleanGeo(bidRequest.User.Geo, applyGDPR, applyCOPPA)
}
if bidRequest.Device != nil {
// Need to duplicate pointer objects
device := *bidRequest.Device
bidRequest.Device = &device

bidRequest.Device.DIDMD5 = ""
bidRequest.Device.DIDSHA1 = ""
bidRequest.Device.DPIDMD5 = ""
bidRequest.Device.DPIDSHA1 = ""
if applyCOPPA {
bidRequest.Device.MACSHA1 = ""
bidRequest.Device.MACMD5 = ""
bidRequest.Device.IFA = ""
}
bidRequest.Device.IP = cleanIP(bidRequest.Device.IP)
bidRequest.Device.IPv6 = cleanIPV6(bidRequest.Device.IPv6, applyGDPR, applyCOPPA)
bidRequest.Device.Geo = cleanGeo(bidRequest.Device.Geo, applyGDPR, applyCOPPA)
}
}

// Zero the last byte of an IP address
func cleanIP(fullIP string) string {
i := strings.LastIndex(fullIP, ".")
if i == -1 {
return ""
}
return fullIP[0:i] + ".0"
}

func cleanIPV6(fullIP string, applyGDPR, applyCOPPA bool) string {
// If neither GDPR nor COPPA applies then do nothing
if !applyGDPR && !applyCOPPA {
return fullIP
}

i := strings.LastIndex(fullIP, ":")
if i == -1 {
return ""
}
fullIP = fullIP[0:i]

// If COPPA then remove the lowest 32 bits of the IP
if applyCOPPA {
fullIP = fullIP[:strings.LastIndex(fullIP, ":")+1] + "0:0"
return fullIP
}
// If GDPR then remove the lowest 32 bits of the IP
if applyGDPR {
fullIP = fullIP + ":0"
}
return fullIP
}

// Return a cleaned Geo object pointer (round off the latitude/longitude)
func cleanGeo(geo *openrtb.Geo, applyGDPR, applyCOPPA bool) *openrtb.Geo {
// If neither GDPR nor COPPA applies then do nothing
if !applyCOPPA && !applyGDPR {
return geo
}

if geo == nil {
return nil
}
newGeo := *geo

// If GDPR applies then round off the Lat and LON values
if applyGDPR {
newGeo.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0
newGeo.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0
}

// If COPPA applies then remove Lat, Lon, Metro, City and Zip values
if applyCOPPA {
newGeo.Lat = 0
newGeo.Lon = 0
newGeo.Metro = ""
newGeo.City = ""
newGeo.ZIP = ""
}
return &newGeo
}
Loading

0 comments on commit af33780

Please sign in to comment.