diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go index 832ec9258f8..7bc1fdc0370 100644 --- a/exchange/exchange_test.go +++ b/exchange/exchange_test.go @@ -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" @@ -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" @@ -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", diff --git a/exchange/gdpr.go b/exchange/gdpr.go index 30b9bc7d76b..2e2343d6db5 100644 --- a/exchange/gdpr.go +++ b/exchange/gdpr.go @@ -2,7 +2,6 @@ package exchange import ( "encoding/json" - "strings" "github.com/mxmCherry/openrtb" ) @@ -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 -} diff --git a/exchange/gdpr_test.go b/exchange/gdpr_test.go index fd7147085dc..2d8fff86bd2 100644 --- a/exchange/gdpr_test.go +++ b/exchange/gdpr_test.go @@ -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("")) -} diff --git a/exchange/utils.go b/exchange/utils.go index 560ef0a0a5e..c77d907ce0f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "math/rand" + "strings" "github.com/buger/jsonparser" "github.com/mxmCherry/openrtb" @@ -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 @@ -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 +} diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 91e4f0fb417..612fc100707 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -31,22 +31,8 @@ func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrt return false, nil } -func assertReqWithoutAliases(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest) { - // assert individual bidder requests - assert.NotEqual(t, len(reqByBidders), 0, "cleanOpenRTBRequest should split request into individual bidder requests") - - // assert for PI data - // Only appnexus should be allowed - for bidderName, bidder := range reqByBidders { - if bidderName == "appnexus" { - assert.NotEqual(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest shouldn't clean PI data for a consented vendor") - } else { - assert.Equal(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest should clean PI data for a non-consented vendor") - } - } -} - -func assertReqWithAliases(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest) { +func assertReq(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest, + applyCOPPA bool, consentedVendors map[string]bool) { // assert individual bidder requests assert.NotEqual(t, reqByBidders, 0, "cleanOpenRTBRequest should split request into individual bidder requests") @@ -54,12 +40,12 @@ func assertReqWithAliases(t *testing.T, reqByBidders map[openrtb_ext.BidderName] // Both appnexus and brightroll should be allowed since brightroll // is used as an alias for appnexus in the test request for bidderName, bidder := range reqByBidders { - if bidderName == "appnexus" || bidderName == "brightroll" { - assert.NotEqual(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest shouldn't clean PI data for a consented vendor") - assert.NotEqual(t, bidder.Device.DIDMD5, "", "cleanOpenRTBRequest shouldn't clean PI data for a consented vendor") + if !applyCOPPA && consentedVendors[bidderName.String()] { + assert.NotEqual(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest shouldn't clean PI data as per COPPA or for a consented vendor as per GDPR") + assert.NotEqual(t, bidder.Device.DIDMD5, "", "cleanOpenRTBRequest shouldn't clean PI data as per COPPA or for a consented vendor as per GDPR") } else { - assert.Equal(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest should clean PI data for a non-consented vendor") - assert.Equal(t, bidder.Device.DIDMD5, "", "cleanOpenRTBRequest shouldn't clean PI data for a consented vendor") + assert.Equal(t, bidder.User.BuyerUID, "", "cleanOpenRTBRequest should clean PI data as per COPPA or for a non-consented vendor as per GDPR ", bidderName.String()) + assert.Equal(t, bidder.Device.DIDMD5, "", "cleanOpenRTBRequest should clean PI data as per COPPA or for a non-consented vendor as per GDPR", bidderName.String()) } } } @@ -68,11 +54,16 @@ func assertReqWithAliases(t *testing.T, reqByBidders map[openrtb_ext.BidderName] func TestCleanOpenRTBRequests(t *testing.T) { testCases := []struct { req *openrtb.BidRequest - bidReqAssertions func(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest) + bidReqAssertions func(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest, + applyCOPPA bool, consentedVendors map[string]bool) hasError bool + applyCOPPA bool + consentedVendors map[string]bool }{ - {req: newRaceCheckingRequest(t), bidReqAssertions: assertReqWithoutAliases, hasError: false}, - {req: newAdapterAliasBidRequest(t), bidReqAssertions: assertReqWithAliases, hasError: false}, + {req: newRaceCheckingRequest(t), bidReqAssertions: assertReq, hasError: false, + applyCOPPA: true, consentedVendors: map[string]bool{"appnexus": true}}, + {req: newAdapterAliasBidRequest(t), bidReqAssertions: assertReq, hasError: false, + applyCOPPA: false, consentedVendors: map[string]bool{"appnexus": true, "brightroll": true}}, } for _, test := range testCases { @@ -81,7 +72,7 @@ func TestCleanOpenRTBRequests(t *testing.T) { assert.NotNil(t, err, "Error shouldn't be nil") } else { assert.Nil(t, err, "Err should be nil") - test.bidReqAssertions(t, reqByBidders) + test.bidReqAssertions(t, reqByBidders, test.applyCOPPA, test.consentedVendors) } } } @@ -153,3 +144,297 @@ func TestRandomizeList(t *testing.T) { } } + +func TestCleanIP(t *testing.T) { + testCases := []struct { + IP string + cleanedIP string + description string + }{ + { + IP: "0.0.0.0", + cleanedIP: "0.0.0.0", + description: "Shouldn't do anything for a 0.0.0.0 IP address", + }, + { + IP: "192.127.111.134", + cleanedIP: "192.127.111.0", + description: "Should remove the lowest 8 bits for GDPR/COPPA compliance", + }, + { + IP: "192.127.111.0", + cleanedIP: "192.127.111.0", + description: "Shouldn't change anything if the lowest 8 bits are already 0", + }, + { + IP: "not an ip", + cleanedIP: "", + description: "Should return an empty string for a bad IP", + }, + { + IP: "", + cleanedIP: "", + description: "Should return an empty string for a bad IP", + }, + { + IP: "36278042", + cleanedIP: "", + description: "Should return an empty string for a bad IP", + }, + } + + for _, test := range testCases { + assert.Equal(t, cleanIP(test.IP), test.cleanedIP, "Should properly remove the last 8 bits of the IP") + } +} + +func TestCleanIPV6(t *testing.T) { + testCases := []struct { + IP string + cleanedIP string + applyGDPR bool + applyCOPPA bool + description string + }{ + { + IP: "0:0:0:0", + cleanedIP: "0:0:0:0", + description: "Shouldn't do anything for a 0:0:0:0 IP address", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", + applyCOPPA: true, + description: "Should remove lowest 32 bits for COPPA compliance", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", + applyGDPR: true, + description: "Should remove lowest 16 bits for GDPR compliance", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0042:0", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", + applyGDPR: true, + description: "Shouldn't do anything if the lowest 16 bits are already 0 for GDPR compliance", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0:0", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", + applyGDPR: true, + description: "Shouldn't do anything if the lowest 16 bits are already 0 for GDPR compliance", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0042:0", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", + applyCOPPA: true, + description: "Shouldn't do anything if the lowest 32 bits are already 0 for COPPA compliance", + }, + { + IP: "2001:0db8:0000:0000:0000:ff00:0:0", + cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", + applyCOPPA: true, + description: "Shouldn't do anything if the lowest 32 bits are already 0 for COPPA compliance", + }, + { + IP: "not an ip", + cleanedIP: "", + applyCOPPA: true, + description: "Should return an empty string for a bad IP", + }, + { + IP: "not an ip", + cleanedIP: "", + applyGDPR: true, + description: "Should return an empty string for a bad IP", + }, + { + IP: "", + cleanedIP: "", + applyCOPPA: true, + description: "Should return an empty string for a bad IP", + }, + } + + for _, test := range testCases { + assert.Equal(t, cleanIPV6(test.IP, test.applyGDPR, test.applyCOPPA), test.cleanedIP, "Should properly remove the last 8 bits of the IP") + } +} + +func TestCleanGeo(t *testing.T) { + testCases := []struct { + geo *openrtb.Geo + cleanedGeo *openrtb.Geo + applyGDPR bool + applyCOPPA bool + description string + }{ + { + geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + cleanedGeo: &openrtb.Geo{ + Lat: 123.46, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + applyGDPR: true, + description: "Should only round off Lat and Lon values for GDPR compliance", + }, + { + geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + cleanedGeo: &openrtb.Geo{ + Lat: 0, + Lon: 0, + Metro: "", + City: "", + ZIP: "", + }, + applyCOPPA: true, + description: "Should suppress all Geo values for GDPR compliance", + }, + { + geo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + cleanedGeo: &openrtb.Geo{ + Lat: 123.456, + Lon: 678.89, + Metro: "some metro", + City: "some city", + ZIP: "some zip", + }, + applyGDPR: false, + applyCOPPA: false, + description: "Should do nothing if neither GDPR nor COPPA applies", + }, + } + + for _, test := range testCases { + cleanedGeo := cleanGeo(test.geo, test.applyGDPR, test.applyCOPPA) + assert.Equal(t, cleanedGeo, test.cleanedGeo, test.description) + } +} + +func TestApplyRegs(t *testing.T) { + bidReqOrig := openrtb.BidRequest{ + User: &openrtb.User{ + BuyerUID: "abc123", + ID: "123", + Yob: 2050, + Gender: "Female", + }, + Device: &openrtb.Device{ + DIDMD5: "teapot", + MACSHA1: "someshahash", + IP: "12.123.56.128", + IPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + Geo: &openrtb.Geo{ + Lat: 123.4567, + Lon: 7.9836, + }, + }, + } + + testCases := []struct { + bidReq openrtb.BidRequest + cleanedIP string + cleanedIPv6 string + applyCOPPA bool + applyGDPR bool + isAMP bool + description string + }{ + { + bidReq: bidReqOrig, + cleanedIP: "12.123.56.0", + cleanedIPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:0", + applyGDPR: true, + description: "Should clean recommended personal information for GDPR compliance", + }, + { + bidReq: bidReqOrig, + cleanedIP: "12.123.56.0", + cleanedIPv6: "2001:0db8:85a3:0000:0000:8a2e:0370:0", + applyGDPR: true, + isAMP: true, + description: "Should clean recommended personal information for GDPR compliance", + }, + { + bidReq: bidReqOrig, + cleanedIP: "12.123.56.0", + cleanedIPv6: "2001:0db8:85a3:0000:0000:8a2e:0:0", + applyGDPR: true, + isAMP: true, + applyCOPPA: true, + description: "Should clean recommended personal information for GDPR compliance", + }, + { + bidReq: bidReqOrig, + cleanedIP: "12.123.56.0", + cleanedIPv6: "2001:0db8:85a3:0000:0000:8a2e:0:0", + applyCOPPA: true, + description: "Should clean recommended personal information for COPPA compliance", + }, + { + bidReq: openrtb.BidRequest{}, + cleanedIP: "", + cleanedIPv6: "", + description: "Shouldn't do anything for an empty bid request", + }, + } + + for _, test := range testCases { + // Make a shallow copy + bidReqCopy := test.bidReq + applyRegs(&bidReqCopy, test.isAMP, test.applyGDPR, test.applyCOPPA) + + if bidReqCopy.User != nil { + if test.isAMP && !test.applyCOPPA { + assert.Equal(t, "abc123", bidReqCopy.User.BuyerUID, test.description) + } else { + assert.Empty(t, bidReqCopy.User.BuyerUID, test.description) + } + + if test.applyCOPPA { + assert.Empty(t, bidReqCopy.User.ID, test.description) + assert.Empty(t, bidReqCopy.User.Yob, test.description) + assert.Empty(t, bidReqCopy.User.Gender, test.description) + } + } + + if bidReqCopy.Device != nil { + if test.applyCOPPA { + assert.Empty(t, bidReqCopy.Device.MACSHA1, test.description) + } + assert.Empty(t, bidReqCopy.Device.DIDMD5, test.description) + assert.Equal(t, test.cleanedIP, bidReqCopy.Device.IP, test.description) + assert.Equal(t, test.cleanedIPv6, bidReqCopy.Device.IPv6, test.description) + } + + // 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) + } +}