Skip to content

Commit

Permalink
Refactor Bid Splitter Privacy Functions (prebid#3645)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxNode authored and krdzo committed Oct 30, 2024
1 parent f7caea5 commit 0da2c07
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 119 deletions.
8 changes: 4 additions & 4 deletions endpoints/cookie_sync_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2216,9 +2216,9 @@ func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ex
return args.Bool(0), args.Error(1)
}

func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
args := m.Called(ctx, bidderCoreName, bidder)
return args.Get(0).(gdpr.AuctionPermissions), args.Error(1)
return args.Get(0).(gdpr.AuctionPermissions)
}

type FakeAccountsFetcher struct {
Expand Down Expand Up @@ -2248,10 +2248,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return true, nil
}

func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
return gdpr.AuctionPermissions{
AllowBidRequest: true,
}, nil
}
}

func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
Expand Down
4 changes: 2 additions & 2 deletions endpoints/openrtb2/test_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -1429,10 +1429,10 @@ func (p *fakePermissions) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return true, nil
}

func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
return gdpr.AuctionPermissions{
AllowBidRequest: true,
}, nil
}
}

type mockPlanBuilder struct {
Expand Down
4 changes: 2 additions & 2 deletions endpoints/setuid_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1701,12 +1701,12 @@ func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return false, nil
}

func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) {
func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
return gdpr.AuctionPermissions{
AllowBidRequest: g.personalInfoAllowed,
PassGeo: g.personalInfoAllowed,
PassID: g.personalInfoAllowed,
}, nil
}
}

type fakeSyncer struct {
Expand Down
159 changes: 82 additions & 77 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
return
}

allowedBidderRequests = make([]BidderRequest, 0)

bidderImpWithBidResp := stored_responses.InitStoredBidResponses(req.BidRequest, auctionReq.StoredBidResponses)

hasStoredAuctionResponses := len(auctionReq.StoredAuctionResponses) > 0
Expand Down Expand Up @@ -148,102 +146,110 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
gdprPerms = rs.gdprPermsBuilder(auctionReq.TCF2Config, gdprRequestInfo)
}

// bidder level privacy policies
allowedBidderRequests = make([]BidderRequest, 0, len(allBidderRequests))

for _, bidderRequest := range allBidderRequests {
// fetchBids activity
scopedName := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()}
fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, scopedName, privacy.NewRequestFromBidRequest(*req))
if !fetchBidsActivityAllowed {
// skip the call to a bidder if fetchBids activity is not allowed
// do not add this bidder to allowedBidderRequests
auctionPermissions := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName)

// privacy blocking
if rs.isBidderBlockedByPrivacy(req, auctionReq.Activities, auctionPermissions, bidderRequest.BidderCoreName, bidderRequest.BidderName) {
continue
}

var auctionPermissions gdpr.AuctionPermissions
var gdprErr error
// fpd
applyFPD(auctionReq.FirstPartyData, bidderRequest)

if gdprEnforced {
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
}
// privacy scrubbing
if err := rs.applyPrivacy(&bidderRequest, auctionReq, auctionPermissions, ccpaEnforcer, lmt, coppa); err != nil {
errs = append(errs, err)
continue
}

ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config}
// GPP downgrade: always downgrade unless we can confirm GPP is supported
if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) {
setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp)
setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp)
}

// FPD should be applied before policies, otherwise it overrides policies and activities restricted data
applyFPD(auctionReq.FirstPartyData, bidderRequest)
allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
}

reqWrapper := &openrtb_ext.RequestWrapper{
BidRequest: ortb.CloneBidRequestPartial(bidderRequest.BidRequest),
}
return
}

func (rs *requestSplitter) isBidderBlockedByPrivacy(r *openrtb_ext.RequestWrapper, activities privacy.ActivityControl, auctionPermissions gdpr.AuctionPermissions, coreBidder, bidderName openrtb_ext.BidderName) bool {
// activities control
scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName.String()}
fetchBidsActivityAllowed := activities.Allow(privacy.ActivityFetchBids, scope, privacy.NewRequestFromBidRequest(*r))
if !fetchBidsActivityAllowed {
return true
}

// gdpr
if !auctionPermissions.AllowBidRequest {
rs.me.RecordAdapterGDPRRequestBlocked(coreBidder)
return true
}

passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scopedName, privacy.NewRequestFromBidRequest(*req))
buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != ""
buyerUIDRemoved := false
if !passIDActivityAllowed {
privacy.ScrubUserFPD(reqWrapper)
return false
}

func (rs *requestSplitter) applyPrivacy(bidderRequest *BidderRequest, auctionReq AuctionRequest, auctionPermissions gdpr.AuctionPermissions, ccpaEnforcer privacy.PolicyEnforcer, lmt bool, coppa bool) error {
scope := privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderRequest.BidderName.String()}
ipConf := privacy.IPConf{IPV6: auctionReq.Account.Privacy.IPv6Config, IPV4: auctionReq.Account.Privacy.IPv4Config}

reqWrapper := &openrtb_ext.RequestWrapper{
BidRequest: ortb.CloneBidRequestPartial(bidderRequest.BidRequest),
}

passIDActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitUserFPD, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
buyerUIDSet := reqWrapper.User != nil && reqWrapper.User.BuyerUID != ""
buyerUIDRemoved := false
if !passIDActivityAllowed {
privacy.ScrubUserFPD(reqWrapper)
buyerUIDRemoved = true
} else {
if !auctionPermissions.PassID {
privacy.ScrubGdprID(reqWrapper)
buyerUIDRemoved = true
} else {
// run existing policies (GDPR, CCPA, COPPA, LMT)
// potentially block passing IDs based on GDPR
if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassID) {
privacy.ScrubGdprID(reqWrapper)
buyerUIDRemoved = true
}
// potentially block passing IDs based on CCPA
if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
buyerUIDRemoved = true
}
}
if buyerUIDSet && buyerUIDRemoved {
rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName)
}

passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scopedName, privacy.NewRequestFromBidRequest(*req))
if !passGeoActivityAllowed {
privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
} else {
// run existing policies (GDPR, CCPA, COPPA, LMT)
// potentially block passing geo based on GDPR
if gdprEnforced && (gdprErr != nil || !auctionPermissions.PassGeo) {
privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
}
// potentially block passing geo based on CCPA
if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
}
if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
buyerUIDRemoved = true
}
}
if buyerUIDSet && buyerUIDRemoved {
rs.me.RecordAdapterBuyerUIDScrubbed(bidderRequest.BidderCoreName)
}

if lmt || coppa {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa)
passGeoActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitPreciseGeo, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
if !passGeoActivityAllowed {
privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
} else {
if !auctionPermissions.PassGeo {
privacy.ScrubGeoAndDeviceIP(reqWrapper, ipConf)
}

passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scopedName, privacy.NewRequestFromBidRequest(*req))
if !passTIDAllowed {
privacy.ScrubTID(reqWrapper)
if ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", false)
}
}

err := reqWrapper.RebuildRequest()
if err != nil {
errs = append(errs, err)
}
bidderRequest.BidRequest = reqWrapper.BidRequest
if lmt || coppa {
privacy.ScrubDeviceIDsIPsUserDemoExt(reqWrapper, ipConf, "eids", coppa)
}

allowedBidderRequests = append(allowedBidderRequests, bidderRequest)
passTIDAllowed := auctionReq.Activities.Allow(privacy.ActivityTransmitTIDs, scope, privacy.NewRequestFromBidRequest(*reqWrapper))
if !passTIDAllowed {
privacy.ScrubTID(reqWrapper)
}

// GPP downgrade: always downgrade unless we can confirm GPP is supported
if shouldSetLegacyPrivacy(rs.bidderInfo, string(bidderRequest.BidderCoreName)) {
setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp)
setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp)
}
if err := reqWrapper.RebuildRequest(); err != nil {
return err
}

return
bidderRequest.BidRequest = reqWrapper.BidRequest
return nil
}

func shouldSetLegacyPrivacy(bidderInfo config.BidderInfos, bidder string) bool {
Expand Down Expand Up @@ -456,7 +462,6 @@ func buildRequestExtForBidder(bidder string, requestExt json.RawMessage, request
}

func buildRequestExtAlternateBidderCodes(bidder string, accABC *openrtb_ext.ExtAlternateBidderCodes, reqABC *openrtb_ext.ExtAlternateBidderCodes) *openrtb_ext.ExtAlternateBidderCodes {

if altBidderCodes := copyExtAlternateBidderCodes(bidder, reqABC); altBidderCodes != nil {
return altBidderCodes
}
Expand Down
6 changes: 3 additions & 3 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
return true, nil
}

func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (gdpr.AuctionPermissions, error) {
func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) gdpr.AuctionPermissions {
permissions := gdpr.AuctionPermissions{
PassGeo: p.passGeo,
PassID: p.passID,
}

if p.allowAllBidders {
permissions.AllowBidRequest = true
return permissions, p.activitiesError
return permissions
}

for _, allowedBidder := range p.allowedBidders {
Expand All @@ -63,7 +63,7 @@ func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCo
}
}

return permissions, p.activitiesError
return permissions
}

type fakePermissionsBuilder struct {
Expand Down
4 changes: 2 additions & 2 deletions gdpr/gdpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type Permissions interface {

// Determines whether or not to send PI information to a bidder, or mask it out.
//
// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error)
// If the consent string was nonsensical, the no permissions are granted.
AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions
}

type PermissionsBuilder func(TCF2ConfigReader, RequestInfo) Permissions
Expand Down
33 changes: 18 additions & 15 deletions gdpr/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,33 +56,36 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
}

// AuctionActivitiesAllowed determines whether auction activities are permitted for a given bidder
func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions {
if _, ok := p.nonStandardPublishers[p.publisherID]; ok {
return AllowAll, nil
return AllowAll
}

if p.gdprSignal != SignalYes {
return AllowAll, nil
return AllowAll
}

if p.consent == "" {
return p.defaultPermissions(), nil
return p.defaultPermissions()
}

pc, err := parseConsent(p.consent)
if err != nil {
return p.defaultPermissions(), err
return p.defaultPermissions()
}

vendorID, _ := p.resolveVendorID(bidderCoreName, bidder)
vendor, err := p.getVendor(ctx, vendorID, *pc)
if err != nil {
return p.defaultPermissions(), err
return p.defaultPermissions()
}
vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}

permissions = AuctionPermissions{}
permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo)
permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor)
permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo)

return permissions, nil
vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
return AuctionPermissions{
AllowBidRequest: p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo),
PassGeo: p.allowGeo(bidderCoreName, pc.consentMeta, vendor),
PassID: p.allowID(bidderCoreName, pc.consentMeta, vendorInfo),
}
}

// defaultPermissions returns a permissions object that denies passing user IDs while
Expand Down Expand Up @@ -222,6 +225,6 @@ func (a AlwaysAllow) HostCookiesAllowed(ctx context.Context) (bool, error) {
func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) {
return true, nil
}
func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
return AllowAll, nil
func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) AuctionPermissions {
return AllowAll
}
Loading

0 comments on commit 0da2c07

Please sign in to comment.