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

Commit

Permalink
Privacy: Limit Ad Tracking (prebid#1334)
Browse files Browse the repository at this point in the history
  • Loading branch information
SyntaxNode authored Jun 15, 2020
1 parent 86e3eb6 commit 1e91339
Show file tree
Hide file tree
Showing 12 changed files with 473 additions and 36 deletions.
13 changes: 13 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ type Configuration struct {
AMPTimeoutAdjustment int64 `mapstructure:"amp_timeout_adjustment_ms"`
GDPR GDPR `mapstructure:"gdpr"`
CCPA CCPA `mapstructure:"ccpa"`
LMT LMT `mapstructure:"lmt"`
CurrencyConverter CurrencyConverter `mapstructure:"currency_converter"`
DefReqConfig DefReqConfig `mapstructure:"default_request"`

Expand Down Expand Up @@ -139,6 +140,13 @@ func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Du
return requested
}

// Privacy is a grouping of privacy related configs to assist in dependency injection.
type Privacy struct {
CCPA CCPA
GDPR GDPR
LMT LMT
}

type GDPR struct {
HostVendorID int `mapstructure:"host_vendor_id"`
UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"`
Expand Down Expand Up @@ -193,6 +201,10 @@ type CCPA struct {
Enforce bool `mapstructure:"enforce"`
}

type LMT struct {
Enforce bool `mapstructure:"enforce"`
}

type Analytics struct {
File FileLogs `mapstructure:"file"`
}
Expand Down Expand Up @@ -836,6 +848,7 @@ func SetupViper(v *viper.Viper, filename string) {
v.SetDefault("gdpr.tcf2.purpose_one_treatement.access_allowed", true)
v.SetDefault("gdpr.amp_exception", false)
v.SetDefault("ccpa.enforce", false)
v.SetDefault("lmt.enforce", true)
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
v.SetDefault("default_request.type", "")
Expand Down
3 changes: 3 additions & 0 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ gdpr:
non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"]
ccpa:
enforce: true
lmt:
enforce: true
host_cookie:
cookie_name: userid
family: prebid
Expand Down Expand Up @@ -240,6 +242,7 @@ func TestFullConfig(t *testing.T) {
cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false)

cmpBools(t, "ccpa.enforce", cfg.CCPA.Enforce, true)
cmpBools(t, "lmt.enforce", cfg.LMT.Enforce, true)

//Assert the NonStandardPublishers was correctly unmarshalled
cmpStrings(t, "blacklisted_apps", cfg.BlacklistedApps[0], "spamAppID")
Expand Down
10 changes: 7 additions & 3 deletions exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ type exchange struct {
currencyConverter *currencies.RateConverter
UsersyncIfAmbiguous bool
defaultTTLs config.DefaultTTLs
enforceCCPA bool
privacyConfig config.Privacy
}

// Container to pass out response ext data from the GetAllBids goroutines back into the main thread
Expand Down Expand Up @@ -77,7 +77,11 @@ func NewExchange(client *http.Client, cache prebid_cache_client.Client, cfg *con
e.currencyConverter = currencyConverter
e.UsersyncIfAmbiguous = cfg.GDPR.UsersyncIfAmbiguous
e.defaultTTLs = cfg.CacheURL.DefaultTTLs
e.enforceCCPA = cfg.CCPA.Enforce
e.privacyConfig = config.Privacy{
CCPA: cfg.CCPA,
GDPR: cfg.GDPR,
LMT: cfg.LMT,
}
return e
}

Expand All @@ -100,7 +104,7 @@ func (e *exchange) HoldAuction(ctx context.Context, bidRequest *openrtb.BidReque

// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
blabels := make(map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels)
cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.enforceCCPA)
cleanRequests, aliases, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)

// List of bidders we have requests for.
liveAdapters := listBiddersWithRequests(cleanRequests)
Expand Down
17 changes: 14 additions & 3 deletions exchange/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,17 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
if len(errs) != 0 {
t.Fatalf("%s: Failed to parse aliases", filename)
}
ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, spec.EnforceCCPA)

privacyConfig := config.Privacy{
CCPA: config.CCPA{
Enforce: spec.EnforceCCPA,
},
LMT: config.LMT{
Enforce: spec.EnforceLMT,
},
}

ex := newExchangeForTests(t, filename, spec.OutgoingRequests, aliases, privacyConfig)
biddersInAuction := findBiddersInAuction(t, filename, &spec.IncomingRequest.OrtbRequest)
categoriesFetcher, error := newCategoryFetcher("./test/category-mapping")
if error != nil {
Expand Down Expand Up @@ -816,7 +826,7 @@ func extractResponseTimes(t *testing.T, context string, bid *openrtb.BidResponse
}
}

func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, enforceCCPA bool) Exchange {
func newExchangeForTests(t *testing.T, filename string, expectations map[string]*bidderSpec, aliases map[string]string, privacyConfig config.Privacy) Exchange {
adapters := make(map[openrtb_ext.BidderName]adaptedBidder)
for _, bidderName := range openrtb_ext.BidderMap {
if spec, ok := expectations[string(bidderName)]; ok {
Expand Down Expand Up @@ -854,7 +864,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
gDPR: gdpr.AlwaysAllow{},
currencyConverter: currencies.NewRateConverterDefault(),
UsersyncIfAmbiguous: false,
enforceCCPA: enforceCCPA,
privacyConfig: privacyConfig,
}
}

Expand Down Expand Up @@ -1620,6 +1630,7 @@ type exchangeSpec struct {
OutgoingRequests map[string]*bidderSpec `json:"outgoingRequests"`
Response exchangeResponse `json:"response,omitempty"`
EnforceCCPA bool `json:"enforceCcpa"`
EnforceLMT bool `json:"enforceLmt"`
DebugLog *DebugLog `json:"debuglog,omitempty"`
}

Expand Down
63 changes: 63 additions & 0 deletions exchange/exchangetest/lmt-featureflag-off.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"enforceLmt": false,
"incomingRequest": {
"ortbRequest": {
"id": "some-request-id",
"site": {
"page": "test.somepage.com"
},
"imp": [{
"id": "my-imp-id",
"video": {
"mimes": ["video/mp4"]
},
"ext": {
"appnexus": {
"placementId": 1
}
}
}],
"device": {
"lmt": 1
},
"user": {
"id": "some-id",
"buyeruid": "some-buyer-id"
}
}
},
"outgoingRequests": {
"appnexus": {
"expectRequest": {
"ortbRequest": {
"id": "some-request-id",
"site": {
"page": "test.somepage.com"
},
"imp": [{
"id": "my-imp-id",
"video": {
"mimes": ["video/mp4"]
},
"ext": {
"bidder": {
"placementId": 1
}
}
}],
"device": {
"lmt": 1
},
"user": {
"id": "some-id",
"buyeruid": "some-buyer-id"
}
},
"bidAdjustment": 1.0
},
"mockResponse": {
"errors": ["appnexus-error"]
}
}
}
}
61 changes: 61 additions & 0 deletions exchange/exchangetest/lmt-featureflag-on.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"enforceLmt": true,
"incomingRequest": {
"ortbRequest": {
"id": "some-request-id",
"site": {
"page": "test.somepage.com"
},
"imp": [{
"id": "my-imp-id",
"video": {
"mimes": ["video/mp4"]
},
"ext": {
"appnexus": {
"placementId": 1
}
}
}],
"device": {
"lmt": 1
},
"user": {
"id": "some-id",
"buyeruid": "some-buyer-id"
}
}
},
"outgoingRequests": {
"appnexus": {
"expectRequest": {
"ortbRequest": {
"id": "some-request-id",
"site": {
"page": "test.somepage.com"
},
"imp": [{
"id": "my-imp-id",
"video": {
"mimes": ["video/mp4"]
},
"ext": {
"bidder": {
"placementId": 1
}
}
}],
"device": {
"lmt": 1
},
"user": {
}
},
"bidAdjustment": 1.0
},
"mockResponse": {
"errors": ["appnexus-error"]
}
}
}
}
25 changes: 18 additions & 7 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (

"github.com/buger/jsonparser"
"github.com/mxmCherry/openrtb"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/gdpr"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/prebid/prebid-server/pbsmetrics"
"github.com/prebid/prebid-server/privacy"
"github.com/prebid/prebid-server/privacy/ccpa"
"github.com/prebid/prebid-server/privacy/lmt"
)

// cleanOpenRTBRequests splits the input request into requests which are sanitized for each bidder. Intended behavior is:
Expand All @@ -26,8 +28,8 @@ func cleanOpenRTBRequests(ctx context.Context,
blables map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels,
labels pbsmetrics.Labels,
gDPR gdpr.Permissions,
usersyncIfAmbiguous,
enforceCCPA bool) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) {
usersyncIfAmbiguous bool,
privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, errs []error) {

impsByBidder, errs := splitImps(orig.Imp)
if len(errs) > 0 {
Expand All @@ -45,15 +47,24 @@ func cleanOpenRTBRequests(ctx context.Context,
consent := extractConsent(orig)
ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException()

privacyEnforcement := privacy.Enforcement{
COPPA: orig.Regs != nil && orig.Regs.COPPA == 1,
var ccpaPolicy ccpa.Policy
if privacyConfig.CCPA.Enforce {
ccpaPolicy, _ = ccpa.ReadPolicy(orig)
}

var lmtPolicy lmt.Policy
if privacyConfig.LMT.Enforce {
lmtPolicy = lmt.ReadPolicy(orig)
}

if enforceCCPA {
ccpaPolicy, _ := ccpa.ReadPolicy(orig)
privacyEnforcement.CCPA = ccpaPolicy.ShouldEnforce()
// request level privacy policies
privacyEnforcement := privacy.Enforcement{
CCPA: ccpaPolicy.ShouldEnforce(),
COPPA: orig.Regs != nil && orig.Regs.COPPA == 1,
LMT: lmtPolicy.ShouldEnforce(),
}

// bidder level privacy policies
for bidder, bidReq := range requestsByBidder {

if gdpr == 1 {
Expand Down
Loading

0 comments on commit 1e91339

Please sign in to comment.