Skip to content

Commit

Permalink
TransmitUserFPD and TransmitPreciseGeo activity integration (#2906)
Browse files Browse the repository at this point in the history
  • Loading branch information
VeronikaSolovei9 authored Jul 27, 2023
1 parent b722123 commit 9d72dbe
Show file tree
Hide file tree
Showing 8 changed files with 629 additions and 115 deletions.
81 changes: 54 additions & 27 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,13 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
lmtEnforcer := extractLMT(req.BidRequest, rs.privacyConfig)

// request level privacy policies
privacyEnforcement := privacy.Enforcement{
COPPA: req.BidRequest.Regs != nil && req.BidRequest.Regs.COPPA == 1,
LMT: lmtEnforcer.ShouldEnforce(unknownBidder),
}
coppa := req.BidRequest.Regs != nil && req.BidRequest.Regs.COPPA == 1
lmt := lmtEnforcer.ShouldEnforce(unknownBidder)

privacyLabels.CCPAProvided = ccpaEnforcer.CanEnforce()
privacyLabels.CCPAEnforced = ccpaEnforcer.ShouldEnforce(unknownBidder)
privacyLabels.COPPAEnforced = privacyEnforcement.COPPA
privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder)
privacyLabels.COPPAEnforced = coppa
privacyLabels.LMTEnforced = lmt

var gdprEnforced bool
var gdprPerms gdpr.Permissions = &gdpr.AlwaysAllow{}
Expand Down Expand Up @@ -148,46 +146,75 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,

// bidder level privacy policies
for _, bidderRequest := range allBidderRequests {
bidRequestAllowed := true
privacyEnforcement := privacy.Enforcement{
COPPA: coppa,
LMT: lmt,
}

// fetchBids activity
fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids,
privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()})
scopedName := privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}
fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName)
if fetchBidsActivityAllowed == privacy.ActivityDeny {
// skip the call to a bidder if fetchBids activity is not allowed
// do not add this bidder to allowedBidderRequests
continue
}

// CCPA
privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String())
var auctionPermissions gdpr.AuctionPermissions
var gdprErr error

// GDPR
if gdprEnforced {
auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName)
bidRequestAllowed = auctionPermissions.AllowBidRequest

if err == nil {
privacyEnforcement.GDPRGeo = !auctionPermissions.PassGeo
privacyEnforcement.GDPRID = !auctionPermissions.PassID
} else {
privacyEnforcement.GDPRGeo = true
privacyEnforcement.GDPRID = true
auctionPermissions, gdprErr = gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName)
if !auctionPermissions.AllowBidRequest {
// auction request is not permitted by GDPR
// do not add this bidder to allowedBidderRequests
rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName)
continue
}
}

if !bidRequestAllowed {
rs.me.RecordAdapterGDPRRequestBlocked(bidderRequest.BidderCoreName)
passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName)
if passIDActivityAllowed == privacy.ActivityDeny {
privacyEnforcement.UFPD = true
} else {
// run existing policies (GDPR, CCPA, COPPA, LMT)
// potentially block passing IDs based on GDPR
if gdprEnforced {
if gdprErr == nil {
privacyEnforcement.GDPRID = !auctionPermissions.PassID
} else {
privacyEnforcement.GDPRID = true
}
}
// potentially block passing IDs based on CCPA
privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String())
}

passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName)
if passGeoActivityAllowed == privacy.ActivityDeny {
privacyEnforcement.PreciseGeo = true
} else {
// run existing policies (GDPR, CCPA, COPPA, LMT)
// potentially block passing geo based on GDPR
if gdprEnforced {
if gdprErr == nil {
privacyEnforcement.GDPRGeo = !auctionPermissions.PassGeo
} else {
privacyEnforcement.GDPRGeo = true
}
}
// potentially block passing geo based on CCPA
privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String())

}

if auctionReq.FirstPartyData != nil && auctionReq.FirstPartyData[bidderRequest.BidderName] != nil {
applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest)
}

if bidRequestAllowed {
privacyEnforcement.Apply(bidderRequest.BidRequest)
allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
}
privacyEnforcement.Apply(bidderRequest.BidRequest)
allowedBidderRequests = append(allowedBidderRequests, bidderRequest)

// GPP downgrade: always downgrade unless we can confirm GPP is supported
if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) {
setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp)
Expand Down
137 changes: 104 additions & 33 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2287,6 +2287,7 @@ func TestCleanOpenRTBRequestsWithOpenRTBDowngrade(t *testing.T) {
bidReq.User.ID = ""
bidReq.User.BuyerUID = ""
bidReq.User.Yob = 0
bidReq.User.Geo = &openrtb2.Geo{Lat: 123.46}

downgradedRegs := *bidReq.Regs
downgradedUser := *bidReq.User
Expand Down Expand Up @@ -2601,6 +2602,7 @@ func newBidRequest(t *testing.T) *openrtb2.BidRequest {
BuyerUID: "their-id",
Yob: 1982,
Ext: json.RawMessage(`{}`),
Geo: &openrtb2.Geo{Lat: 123.456},
},
Imp: []openrtb2.Imp{{
ID: "some-imp-id",
Expand Down Expand Up @@ -4266,33 +4268,76 @@ func TestGetMediaTypeForBid(t *testing.T) {
}
}

func TemporarilyDisabledTestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) {
func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) {
testCases := []struct {
name string
req *openrtb2.BidRequest
componentName string
allow bool
expectedReqNumber int
name string
req *openrtb2.BidRequest
privacyConfig *config.AccountPrivacy
componentName string
allow bool
expectedReqNumber int
expectedUserYOB int64
expectedUserLat float64
expectedDeviceDIDMD5 string
}{
{
name: "request_with_one_bidder_allowed",
req: newBidRequest(t),
componentName: "appnexus",
allow: true,
expectedReqNumber: 1,
},
{
name: "request_with_one_bidder_not_allowed",
req: newBidRequest(t),
componentName: "appnexus",
allow: false,
expectedReqNumber: 0,
name: "fetch_bids_request_with_one_bidder_allowed",
req: newBidRequest(t),
privacyConfig: getFetchBidsActivityConfig("appnexus", true),
expectedReqNumber: 1,
expectedUserYOB: 1982,
expectedUserLat: 123.456,
expectedDeviceDIDMD5: "some device ID hash",
},
{
name: "fetch_bids_request_with_one_bidder_not_allowed",
req: newBidRequest(t),
privacyConfig: getFetchBidsActivityConfig("appnexus", false),
expectedReqNumber: 0,
expectedUserYOB: 1982,
expectedUserLat: 123.456,
expectedDeviceDIDMD5: "some device ID hash",
},
{
name: "transmit_ufpd_allowed",
req: newBidRequest(t),
privacyConfig: getTransmitUFPDActivityConfig("appnexus", true),
expectedReqNumber: 1,
expectedUserYOB: 1982,
expectedUserLat: 123.456,
expectedDeviceDIDMD5: "some device ID hash",
},
{
name: "transmit_ufpd_deny",
req: newBidRequest(t),
privacyConfig: getTransmitUFPDActivityConfig("appnexus", false),
expectedReqNumber: 1,
expectedUserYOB: 0,
expectedUserLat: 123.456,
expectedDeviceDIDMD5: "",
},
{
name: "transmit_precise_geo_allowed",
req: newBidRequest(t),
privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", true),
expectedReqNumber: 1,
expectedUserYOB: 1982,
expectedUserLat: 123.456,
expectedDeviceDIDMD5: "some device ID hash",
},
{
name: "transmit_precise_geo_deny",
req: newBidRequest(t),
privacyConfig: getTransmitPreciseGeoActivityConfig("appnexus", false),
expectedReqNumber: 1,
expectedUserYOB: 1982,
expectedUserLat: 123.46,
expectedDeviceDIDMD5: "some device ID hash",
},
}

for _, test := range testCases {
privacyConfig := getDefaultActivityConfig(test.componentName, test.allow)
activities, err := privacy.NewActivityControl(privacyConfig)
activities, err := privacy.NewActivityControl(test.privacyConfig)
assert.NoError(t, err, "")
auctionReq := AuctionRequest{
BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req},
Expand All @@ -4312,25 +4357,51 @@ func TemporarilyDisabledTestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T
bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo)
assert.Empty(t, errs)
assert.Len(t, bidderRequests, test.expectedReqNumber)

if test.expectedReqNumber == 1 {
assert.Equal(t, test.expectedUserYOB, bidderRequests[0].BidRequest.User.Yob)
assert.Equal(t, test.expectedUserLat, bidderRequests[0].BidRequest.User.Geo.Lat)
assert.Equal(t, test.expectedDeviceDIDMD5, bidderRequests[0].BidRequest.Device.DIDMD5)
}
})
}
}

func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
return &config.AccountPrivacy{
AllowActivities: config.AllowActivities{
FetchBids: config.Activity{
Default: ptrutil.ToPtr(true),
Rules: []config.ActivityRule{
{
Allow: allow,
Condition: config.ActivityCondition{
ComponentName: []string{componentName},
ComponentType: []string{"bidder"},
},
},
func buildDefaultActivityConfig(componentName string, allow bool) config.Activity {
return config.Activity{
Default: ptrutil.ToPtr(true),
Rules: []config.ActivityRule{
{
Allow: allow,
Condition: config.ActivityCondition{
ComponentName: []string{componentName},
ComponentType: []string{"bidder"},
},
},
},
}
}

func getFetchBidsActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
return &config.AccountPrivacy{
AllowActivities: config.AllowActivities{
FetchBids: buildDefaultActivityConfig(componentName, allow),
},
}
}

func getTransmitUFPDActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
return &config.AccountPrivacy{
AllowActivities: config.AllowActivities{
TransmitUserFPD: buildDefaultActivityConfig(componentName, allow),
},
}
}

func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
return &config.AccountPrivacy{
AllowActivities: config.AllowActivities{
TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allow),
},
}
}
25 changes: 21 additions & 4 deletions privacy/enforcement.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,39 @@ type Enforcement struct {
GDPRGeo bool
GDPRID bool
LMT bool

// activities
UFPD bool
Eids bool
PreciseGeo bool
TID bool
}

// Any returns true if at least one privacy policy requires enforcement.
func (e Enforcement) Any() bool {
func (e Enforcement) AnyLegacy() bool {
return e.CCPA || e.COPPA || e.GDPRGeo || e.GDPRID || e.LMT
}

func (e Enforcement) AnyActivities() bool {
return e.UFPD || e.PreciseGeo || e.Eids || e.TID
}

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

func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) {
if bidRequest != nil && e.Any() {
bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy())
bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy())
if bidRequest != nil {
if e.AnyActivities() {
bidRequest = scrubber.ScrubRequest(bidRequest, e)
}
if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo && e.Eids) {
bidRequest.User = scrubber.ScrubUser(bidRequest.User, e.getUserScrubStrategy(), e.getGeoScrubStrategy())
}
if e.AnyLegacy() && !(e.UFPD && e.PreciseGeo) {
bidRequest.Device = scrubber.ScrubDevice(bidRequest.Device, e.getDeviceIDScrubStrategy(), e.getIPv4ScrubStrategy(), e.getIPv6ScrubStrategy(), e.getGeoScrubStrategy())
}
}
}

Expand Down
Loading

0 comments on commit 9d72dbe

Please sign in to comment.