Skip to content
This repository was archived by the owner on Dec 22, 2022. It is now read-only.

Commit 5e29d2c

Browse files
authored
Add COPPA support (prebid#973)
1 parent 0fe6838 commit 5e29d2c

File tree

5 files changed

+436
-179
lines changed

5 files changed

+436
-179
lines changed

exchange/exchange_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9-
"github.com/prebid/prebid-server/stored_requests"
10-
"github.com/prebid/prebid-server/stored_requests/backends/file_fetcher"
119
"io/ioutil"
1210
"net/http"
1311
"net/http/httptest"
@@ -19,6 +17,8 @@ import (
1917
"github.com/prebid/prebid-server/adapters"
2018
"github.com/prebid/prebid-server/currencies"
2119
"github.com/prebid/prebid-server/prebid_cache_client"
20+
"github.com/prebid/prebid-server/stored_requests"
21+
"github.com/prebid/prebid-server/stored_requests/backends/file_fetcher"
2222

2323
"github.com/buger/jsonparser"
2424
"github.com/mxmCherry/openrtb"
@@ -218,7 +218,8 @@ func newRaceCheckingRequest(t *testing.T) *openrtb.BidRequest {
218218
Ext: json.RawMessage(`{"consent":"BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw","digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
219219
},
220220
Regs: &openrtb.Regs{
221-
Ext: json.RawMessage(`{"gdpr":1}`),
221+
COPPA: 1,
222+
Ext: json.RawMessage(`{"gdpr":1}`),
222223
},
223224
Imp: []openrtb.Imp{{
224225
ID: "some-imp-id",

exchange/gdpr.go

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package exchange
22

33
import (
44
"encoding/json"
5-
"strings"
65

76
"github.com/mxmCherry/openrtb"
87
)
@@ -47,57 +46,3 @@ type userExt struct {
4746
type regsExt struct {
4847
GDPR *int `json:"gdpr,omitempty"`
4948
}
50-
51-
// cleanPI removes IP address last byte, device ID, buyer ID, and rounds off latitude/longitude
52-
func cleanPI(bidRequest *openrtb.BidRequest, isAMP bool) {
53-
if bidRequest.User != nil {
54-
// Need to duplicate pointer objects
55-
user := *bidRequest.User
56-
bidRequest.User = &user
57-
if !isAMP {
58-
bidRequest.User.BuyerUID = ""
59-
}
60-
bidRequest.User.Geo = cleanGeo(bidRequest.User.Geo)
61-
}
62-
if bidRequest.Device != nil {
63-
// Need to duplicate pointer objects
64-
device := *bidRequest.Device
65-
bidRequest.Device = &device
66-
bidRequest.Device.DIDMD5 = ""
67-
bidRequest.Device.DIDSHA1 = ""
68-
bidRequest.Device.DPIDMD5 = ""
69-
bidRequest.Device.DPIDSHA1 = ""
70-
bidRequest.Device.IP = cleanIP(bidRequest.Device.IP)
71-
bidRequest.Device.IPv6 = cleanIPv6(bidRequest.Device.IPv6)
72-
bidRequest.Device.Geo = cleanGeo(bidRequest.Device.Geo)
73-
}
74-
}
75-
76-
// Zero the last byte of an IP address
77-
func cleanIP(fullIP string) string {
78-
i := strings.LastIndex(fullIP, ".")
79-
if i == -1 {
80-
return ""
81-
}
82-
return fullIP[0:i] + ".0"
83-
}
84-
85-
// Zero the last two bytes of an IPv6 address
86-
func cleanIPv6(fullIP string) string {
87-
i := strings.LastIndex(fullIP, ":")
88-
if i == -1 {
89-
return ""
90-
}
91-
return fullIP[0:i] + ":0000"
92-
}
93-
94-
// Return a cleaned Geo object pointer (round off the latitude/longitude)
95-
func cleanGeo(geo *openrtb.Geo) *openrtb.Geo {
96-
if geo == nil {
97-
return nil
98-
}
99-
newGeo := *geo
100-
newGeo.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0
101-
newGeo.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0
102-
return &newGeo
103-
}

exchange/gdpr_test.go

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -42,95 +42,3 @@ func TestGDPRUnknown(t *testing.T) {
4242
assert.Equal(t, 0, gdpr)
4343

4444
}
45-
46-
func TestCleanPI(t *testing.T) {
47-
bidReqOrig := openrtb.BidRequest{}
48-
49-
bidReqCopy := bidReqOrig
50-
// Make sure cleanIP handles the empty case
51-
cleanPI(&bidReqCopy, false)
52-
53-
// Add values to clean
54-
bidReqOrig.User = &openrtb.User{
55-
BuyerUID: "abc123",
56-
}
57-
bidReqOrig.Device = &openrtb.Device{
58-
DIDMD5: "teapot",
59-
IP: "12.123.56.128",
60-
IPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
61-
Geo: &openrtb.Geo{
62-
Lat: 123.4567,
63-
Lon: 7.9836,
64-
},
65-
}
66-
// Make a shallow copy
67-
bidReqCopy = bidReqOrig
68-
69-
cleanPI(&bidReqCopy, false)
70-
71-
// Verify cleaned values
72-
assertStringEmpty(t, bidReqCopy.User.BuyerUID)
73-
assertStringEmpty(t, bidReqCopy.Device.DIDMD5)
74-
assert.Equal(t, "12.123.56.0", bidReqCopy.Device.IP)
75-
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:0000", bidReqCopy.Device.IPv6)
76-
assert.Equal(t, 123.46, bidReqCopy.Device.Geo.Lat)
77-
assert.Equal(t, 7.98, bidReqCopy.Device.Geo.Lon)
78-
79-
// verify original untouched, as we want to only modify the cleaned copy for the bidder
80-
assert.Equal(t, "abc123", bidReqOrig.User.BuyerUID)
81-
assert.Equal(t, "teapot", bidReqOrig.Device.DIDMD5)
82-
assert.Equal(t, "12.123.56.128", bidReqOrig.Device.IP)
83-
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:7334", bidReqOrig.Device.IPv6)
84-
assert.Equal(t, 123.4567, bidReqOrig.Device.Geo.Lat)
85-
assert.Equal(t, 7.9836, bidReqOrig.Device.Geo.Lon)
86-
87-
}
88-
89-
func TestCleanPIAmp(t *testing.T) {
90-
bidReqOrig := openrtb.BidRequest{}
91-
92-
bidReqCopy := bidReqOrig
93-
// Make sure cleanIP handles the empty case
94-
cleanPI(&bidReqCopy, false)
95-
96-
// Add values to clean
97-
bidReqOrig.User = &openrtb.User{
98-
BuyerUID: "abc123",
99-
}
100-
bidReqOrig.Device = &openrtb.Device{
101-
DIDMD5: "teapot",
102-
IP: "12.123.56.128",
103-
IPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
104-
Geo: &openrtb.Geo{
105-
Lat: 123.4567,
106-
Lon: 7.9836,
107-
},
108-
}
109-
// Make a shallow copy
110-
bidReqCopy = bidReqOrig
111-
112-
cleanPI(&bidReqCopy, true)
113-
114-
// Verify cleaned values
115-
assert.Equal(t, "abc123", bidReqCopy.User.BuyerUID)
116-
assertStringEmpty(t, bidReqCopy.Device.DIDMD5)
117-
assert.Equal(t, "12.123.56.0", bidReqCopy.Device.IP)
118-
assert.Equal(t, "2001:0db8:85a3:0000:0000:8a2e:0370:0000", bidReqCopy.Device.IPv6)
119-
assert.Equal(t, 123.46, bidReqCopy.Device.Geo.Lat)
120-
assert.Equal(t, 7.98, bidReqCopy.Device.Geo.Lon)
121-
}
122-
123-
func assertStringEmpty(t *testing.T, str string) {
124-
t.Helper()
125-
if str != "" {
126-
t.Errorf("Expected an empty string, got %s", str)
127-
}
128-
}
129-
130-
func TestBadIPs(t *testing.T) {
131-
assertStringEmpty(t, cleanIP("not an IP"))
132-
assertStringEmpty(t, cleanIP(""))
133-
assertStringEmpty(t, cleanIP("36278042"))
134-
assertStringEmpty(t, cleanIPv6("not an IP"))
135-
assertStringEmpty(t, cleanIPv6(""))
136-
}

exchange/utils.go

Lines changed: 122 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"fmt"
77
"math/rand"
8+
"strings"
89

910
"github.com/buger/jsonparser"
1011
"github.com/mxmCherry/openrtb"
@@ -41,16 +42,31 @@ func cleanOpenRTBRequests(ctx context.Context,
4142
// Clean PI from bidrequests if not allowed per GDPR
4243
gdpr := extractGDPR(orig, usersyncIfAmbiguous)
4344
consent := extractConsent(orig)
44-
if gdpr == 1 {
45-
for bidder, bidReq := range requestsByBidder {
46-
// Fixes #820
45+
46+
// Check if it's an AMP request
47+
isAMP := labels.RType == pbsmetrics.ReqTypeAMP
48+
49+
// Check if COPPA applies for this request
50+
var applyCOPPA bool
51+
if orig.Regs != nil && orig.Regs.COPPA == 1 {
52+
applyCOPPA = true
53+
}
54+
55+
for bidder, bidReq := range requestsByBidder {
56+
var applyGDPR bool
57+
// Fixes #820
58+
if gdpr == 1 {
4759
coreBidder := resolveBidder(bidder.String(), aliases)
4860

4961
var publisherID = labels.PubID
5062
if ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent); !ok && err == nil {
51-
cleanPI(bidReq, labels.RType == pbsmetrics.ReqTypeAMP)
63+
applyGDPR = true
5264
}
5365
}
66+
67+
if applyGDPR || applyCOPPA {
68+
applyRegs(bidReq, isAMP, applyGDPR, applyCOPPA)
69+
}
5470
}
5571

5672
return
@@ -297,3 +313,105 @@ func randomizeList(list []openrtb_ext.BidderName) {
297313
list[i], list[j] = list[j], list[i]
298314
}
299315
}
316+
317+
func applyRegs(bidRequest *openrtb.BidRequest, isAMP, applyGDPR, applyCOPPA bool) {
318+
if bidRequest.User != nil {
319+
// Need to duplicate pointer objects
320+
user := *bidRequest.User
321+
bidRequest.User = &user
322+
323+
// There's no way for AMP to send a GDPR consent string yet so it's hard
324+
// to know if the vendor is consented or not and therefore for AMP requests
325+
// we keep the BuyerUID as is
326+
if !isAMP {
327+
bidRequest.User.BuyerUID = ""
328+
}
329+
if applyCOPPA {
330+
bidRequest.User.ID = ""
331+
bidRequest.User.Yob = 0
332+
bidRequest.User.Gender = ""
333+
bidRequest.User.BuyerUID = ""
334+
}
335+
bidRequest.User.Geo = cleanGeo(bidRequest.User.Geo, applyGDPR, applyCOPPA)
336+
}
337+
if bidRequest.Device != nil {
338+
// Need to duplicate pointer objects
339+
device := *bidRequest.Device
340+
bidRequest.Device = &device
341+
342+
bidRequest.Device.DIDMD5 = ""
343+
bidRequest.Device.DIDSHA1 = ""
344+
bidRequest.Device.DPIDMD5 = ""
345+
bidRequest.Device.DPIDSHA1 = ""
346+
if applyCOPPA {
347+
bidRequest.Device.MACSHA1 = ""
348+
bidRequest.Device.MACMD5 = ""
349+
bidRequest.Device.IFA = ""
350+
}
351+
bidRequest.Device.IP = cleanIP(bidRequest.Device.IP)
352+
bidRequest.Device.IPv6 = cleanIPV6(bidRequest.Device.IPv6, applyGDPR, applyCOPPA)
353+
bidRequest.Device.Geo = cleanGeo(bidRequest.Device.Geo, applyGDPR, applyCOPPA)
354+
}
355+
}
356+
357+
// Zero the last byte of an IP address
358+
func cleanIP(fullIP string) string {
359+
i := strings.LastIndex(fullIP, ".")
360+
if i == -1 {
361+
return ""
362+
}
363+
return fullIP[0:i] + ".0"
364+
}
365+
366+
func cleanIPV6(fullIP string, applyGDPR, applyCOPPA bool) string {
367+
// If neither GDPR nor COPPA applies then do nothing
368+
if !applyGDPR && !applyCOPPA {
369+
return fullIP
370+
}
371+
372+
i := strings.LastIndex(fullIP, ":")
373+
if i == -1 {
374+
return ""
375+
}
376+
fullIP = fullIP[0:i]
377+
378+
// If COPPA then remove the lowest 32 bits of the IP
379+
if applyCOPPA {
380+
fullIP = fullIP[:strings.LastIndex(fullIP, ":")+1] + "0:0"
381+
return fullIP
382+
}
383+
// If GDPR then remove the lowest 32 bits of the IP
384+
if applyGDPR {
385+
fullIP = fullIP + ":0"
386+
}
387+
return fullIP
388+
}
389+
390+
// Return a cleaned Geo object pointer (round off the latitude/longitude)
391+
func cleanGeo(geo *openrtb.Geo, applyGDPR, applyCOPPA bool) *openrtb.Geo {
392+
// If neither GDPR nor COPPA applies then do nothing
393+
if !applyCOPPA && !applyGDPR {
394+
return geo
395+
}
396+
397+
if geo == nil {
398+
return nil
399+
}
400+
newGeo := *geo
401+
402+
// If GDPR applies then round off the Lat and LON values
403+
if applyGDPR {
404+
newGeo.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0
405+
newGeo.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0
406+
}
407+
408+
// If COPPA applies then remove Lat, Lon, Metro, City and Zip values
409+
if applyCOPPA {
410+
newGeo.Lat = 0
411+
newGeo.Lon = 0
412+
newGeo.Metro = ""
413+
newGeo.City = ""
414+
newGeo.ZIP = ""
415+
}
416+
return &newGeo
417+
}

0 commit comments

Comments
 (0)