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

Commit

Permalink
Restore the AMP privacy exception as an option. (prebid#1311)
Browse files Browse the repository at this point in the history
* Restore the AMP privacy exception as an option.

* Adds missing test case

* More PR feedback

* Remove unused constant

* Comment tweak
  • Loading branch information
hhhjort authored May 21, 2020
1 parent 18d9f43 commit 0f278d8
Show file tree
Hide file tree
Showing 12 changed files with 167 additions and 66 deletions.
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ type GDPR struct {
Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"`
NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"`
NonStandardPublisherMap map[string]int
AMPException bool `mapstructure:"amp_exception"`
}

func (cfg *GDPR) validate(errs configErrors) configErrors {
Expand Down Expand Up @@ -768,6 +769,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0)
v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0)
v.SetDefault("gdpr.non_standard_publishers", []string{""})
v.SetDefault("gdpr.amp_exception", false)
v.SetDefault("ccpa.enforce", false)
v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json")
v.SetDefault("currency_converter.fetch_interval_seconds", 1800) // fetch currency rates every 30 minutes
Expand Down
4 changes: 4 additions & 0 deletions endpoints/auction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,10 @@ func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder
return m.allowPI, nil
}

func (m *auctionMockPermissions) AMPException() bool {
return false
}

func TestBidSizeValidate(t *testing.T) {
bids := make(pbs.PBSBidSlice, 0)
// bid1 will be rejected due to undefined size when adunit has multiple sizes
Expand Down
4 changes: 4 additions & 0 deletions endpoints/cookie_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,3 +257,7 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi
func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
return true, nil
}

func (g *gdprPerms) AMPException() bool {
return false
}
4 changes: 4 additions & 0 deletions endpoints/setuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrt
return g.allowPI, nil
}

func (g *mockPermsSetUID) AMPException() bool {
return false
}

func newFakeSyncer(familyName string) usersync.Usersyncer {
return fakeSyncer{
familyName: familyName,
Expand Down
3 changes: 2 additions & 1 deletion exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func cleanOpenRTBRequests(ctx context.Context,

gdpr := extractGDPR(orig, usersyncIfAmbiguous)
consent := extractConsent(orig)
ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException()

privacyEnforcement := privacy.Enforcement{
COPPA: orig.Regs != nil && orig.Regs.COPPA == 1,
Expand All @@ -65,7 +66,7 @@ func cleanOpenRTBRequests(ctx context.Context,
privacyEnforcement.GDPR = false
}

privacyEnforcement.Apply(bidReq)
privacyEnforcement.Apply(bidReq, ampGDPRException)
}

return
Expand Down
4 changes: 4 additions & 0 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrt
return false, nil
}

func (p *permissionsMock) AMPException() bool {
return false
}

func assertReq(t *testing.T, reqByBidders map[openrtb_ext.BidderName]*openrtb.BidRequest,
applyCOPPA bool, consentedVendors map[string]bool) {
// assert individual bidder requests
Expand Down
3 changes: 3 additions & 0 deletions gdpr/gdpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Permissions interface {
//
// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error)

// Exposes the AMP execption flag
AMPException() bool
}

const (
Expand Down
8 changes: 8 additions & 0 deletions gdpr/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrt
return false, nil
}

func (p *permissionsImpl) AMPException() bool {
return p.cfg.AMPException
}

func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consent string) (bool, error) {
// If we're not given a consent string, respect the preferences in the app config.
if consent == "" {
Expand Down Expand Up @@ -145,3 +149,7 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
return true, nil
}

func (a AlwaysAllow) AMPException() bool {
return false
}
17 changes: 10 additions & 7 deletions privacy/enforcement.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ func (e Enforcement) Any() bool {
}

// Apply cleans personally identifiable information from an OpenRTB bid request.
func (e Enforcement) Apply(bidRequest *openrtb.BidRequest) {
e.apply(bidRequest, NewScrubber())
func (e Enforcement) Apply(bidRequest *openrtb.BidRequest, ampGDPRException bool) {
e.apply(bidRequest, ampGDPRException, NewScrubber())
}

func (e Enforcement) apply(bidRequest *openrtb.BidRequest, scrubber Scrubber) {
func (e Enforcement) apply(bidRequest *openrtb.BidRequest, ampGDPRException bool, scrubber Scrubber) {
if bidRequest != nil && e.Any() {
bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy())
bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getDemographicScrubStrategy(), e.getGeoScrubStrategy())
bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(ampGDPRException), e.getGeoScrubStrategy())
}
}

Expand Down Expand Up @@ -52,10 +52,13 @@ func (e Enforcement) getGeoScrubStrategy() ScrubStrategyGeo {
return ScrubStrategyGeoNone
}

func (e Enforcement) getDemographicScrubStrategy() ScrubStrategyDemographic {
func (e Enforcement) getUserScrubStrategy(ampGDPRException bool) ScrubStrategyUser {
if e.COPPA {
return ScrubStrategyDemographicAgeAndGender
return ScrubStrategyUserIDAndDemographic
}

return ScrubStrategyDemographicNone
if e.GDPR && ampGDPRException {
return ScrubStrategyUserNone
}
return ScrubStrategyUserID
}
100 changes: 72 additions & 28 deletions privacy/enforcement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,13 @@ func TestAny(t *testing.T) {

func TestApply(t *testing.T) {
testCases := []struct {
description string
enforcement Enforcement
expectedDeviceIPv6 ScrubStrategyIPV6
expectedDeviceGeo ScrubStrategyGeo
expectedUserDemographic ScrubStrategyDemographic
expectedUserGeo ScrubStrategyGeo
description string
enforcement Enforcement
ampGDPRException bool
expectedDeviceIPv6 ScrubStrategyIPV6
expectedDeviceGeo ScrubStrategyGeo
expectedUser ScrubStrategyUser
expectedUserGeo ScrubStrategyGeo
}{
{
description: "All Enforced",
Expand All @@ -65,10 +66,11 @@ func TestApply(t *testing.T) {
COPPA: true,
GDPR: true,
},
expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
expectedDeviceGeo: ScrubStrategyGeoFull,
expectedUserDemographic: ScrubStrategyDemographicAgeAndGender,
expectedUserGeo: ScrubStrategyGeoFull,
ampGDPRException: false,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
expectedDeviceGeo: ScrubStrategyGeoFull,
expectedUser: ScrubStrategyUserIDAndDemographic,
expectedUserGeo: ScrubStrategyGeoFull,
},
{
description: "CCPA Only",
Expand All @@ -77,10 +79,11 @@ func TestApply(t *testing.T) {
COPPA: false,
GDPR: false,
},
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUserDemographic: ScrubStrategyDemographicNone,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
ampGDPRException: false,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUser: ScrubStrategyUserID,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
},
{
description: "COPPA Only",
Expand All @@ -89,10 +92,11 @@ func TestApply(t *testing.T) {
COPPA: true,
GDPR: false,
},
expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
expectedDeviceGeo: ScrubStrategyGeoFull,
expectedUserDemographic: ScrubStrategyDemographicAgeAndGender,
expectedUserGeo: ScrubStrategyGeoFull,
ampGDPRException: false,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
expectedDeviceGeo: ScrubStrategyGeoFull,
expectedUser: ScrubStrategyUserIDAndDemographic,
expectedUserGeo: ScrubStrategyGeoFull,
},
{
description: "GDPR Only",
Expand All @@ -101,10 +105,50 @@ func TestApply(t *testing.T) {
COPPA: false,
GDPR: true,
},
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUserDemographic: ScrubStrategyDemographicNone,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
ampGDPRException: false,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUser: ScrubStrategyUserID,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
},
{
description: "GDPR Only, ampGDPRException",
enforcement: Enforcement{
CCPA: false,
COPPA: false,
GDPR: true,
},
ampGDPRException: true,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUser: ScrubStrategyUserNone,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
},
{
description: "CCPA Only, ampGDPRException",
enforcement: Enforcement{
CCPA: true,
COPPA: false,
GDPR: false,
},
ampGDPRException: true,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest16,
expectedDeviceGeo: ScrubStrategyGeoReducedPrecision,
expectedUser: ScrubStrategyUserID,
expectedUserGeo: ScrubStrategyGeoReducedPrecision,
},
{
description: "COPPA and GDPR, ampGDPRException",
enforcement: Enforcement{
CCPA: false,
COPPA: true,
GDPR: true,
},
ampGDPRException: true,
expectedDeviceIPv6: ScrubStrategyIPV6Lowest32,
expectedDeviceGeo: ScrubStrategyGeoFull,
expectedUser: ScrubStrategyUserIDAndDemographic,
expectedUserGeo: ScrubStrategyGeoFull,
},
}

Expand All @@ -118,9 +162,9 @@ func TestApply(t *testing.T) {

m := &mockScrubber{}
m.On("ScrubDevice", req.Device, test.expectedDeviceIPv6, test.expectedDeviceGeo).Return(replacedDevice).Once()
m.On("ScrubUser", req.User, test.expectedUserDemographic, test.expectedUserGeo).Return(replacedUser).Once()
m.On("ScrubUser", req.User, test.expectedUser, test.expectedUserGeo).Return(replacedUser).Once()

test.enforcement.apply(req, m)
test.enforcement.apply(req, test.ampGDPRException, m)

m.AssertExpectations(t)
assert.Same(t, replacedDevice, req.Device, "Device")
Expand All @@ -138,7 +182,7 @@ func TestApplyNoneApplicable(t *testing.T) {
COPPA: false,
GDPR: false,
}
enforcement.apply(req, m)
enforcement.apply(req, false, m)

m.AssertNotCalled(t, "ScrubDevice")
m.AssertNotCalled(t, "ScrubUser")
Expand All @@ -148,7 +192,7 @@ func TestApplyNil(t *testing.T) {
m := &mockScrubber{}

enforcement := Enforcement{}
enforcement.apply(nil, m)
enforcement.apply(nil, false, m)

m.AssertNotCalled(t, "ScrubDevice")
m.AssertNotCalled(t, "ScrubUser")
Expand All @@ -163,7 +207,7 @@ func (m *mockScrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV
return args.Get(0).(*openrtb.Device)
}

func (m *mockScrubber) ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User {
args := m.Called(user, demographic, geo)
func (m *mockScrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User {
args := m.Called(user, strategy, geo)
return args.Get(0).(*openrtb.User)
}
30 changes: 18 additions & 12 deletions privacy/scrubber.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,21 +34,24 @@ const (
ScrubStrategyGeoReducedPrecision
)

// ScrubStrategyDemographic defines the approach to non-location demographic data.
type ScrubStrategyDemographic int
// ScrubStrategyUser defines the approach to scrub PII from user data.
type ScrubStrategyUser int

const (
// ScrubStrategyDemographicNone does not remove non-location demographic data.
ScrubStrategyDemographicNone ScrubStrategyDemographic = iota
// ScrubStrategyUserNone does not remove non-location data.
ScrubStrategyUserNone ScrubStrategyUser = iota

// ScrubStrategyDemographicAgeAndGender removes age and gender data.
ScrubStrategyDemographicAgeAndGender
// ScrubStrategyUserIDAndDemographic removes the user's buyer id, exchange id year of birth, and gender.
ScrubStrategyUserIDAndDemographic

// ScrubStrategyUserID removes the user's buyer id.
ScrubStrategyUserID
)

// Scrubber removes PII from parts of an OpenRTB request.
type Scrubber interface {
ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb.Device
ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User
ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User
}

type scrubber struct{}
Expand Down Expand Up @@ -90,19 +93,22 @@ func (scrubber) ScrubDevice(device *openrtb.Device, ipv6 ScrubStrategyIPV6, geo
return &deviceCopy
}

func (scrubber) ScrubUser(user *openrtb.User, demographic ScrubStrategyDemographic, geo ScrubStrategyGeo) *openrtb.User {
func (scrubber) ScrubUser(user *openrtb.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb.User {
if user == nil {
return nil
}

userCopy := *user
userCopy.BuyerUID = ""
userCopy.ID = ""

switch demographic {
case ScrubStrategyDemographicAgeAndGender:
switch strategy {
case ScrubStrategyUserIDAndDemographic:
userCopy.BuyerUID = ""
userCopy.ID = ""
userCopy.Yob = 0
userCopy.Gender = ""
case ScrubStrategyUserID:
userCopy.BuyerUID = ""
userCopy.ID = ""
}

switch geo {
Expand Down
Loading

0 comments on commit 0f278d8

Please sign in to comment.