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

Commit

Permalink
Privacy Request Metrics (prebid#1400)
Browse files Browse the repository at this point in the history
* Privacy Request Metrics

* Fix Bug + Add Unit Tests

* Fixed Tests

* Fix Typo
  • Loading branch information
SyntaxNode authored Jul 17, 2020
1 parent 4f78912 commit 7bf6b04
Show file tree
Hide file tree
Showing 12 changed files with 454 additions and 82 deletions.
7 changes: 3 additions & 4 deletions exchange/exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,10 @@ 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, cleanMetrics, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)
cleanRequests, aliases, privacyLabels, errs := cleanOpenRTBRequests(ctx, bidRequest, usersyncs, blabels, labels, e.gDPR, e.UsersyncIfAmbiguous, e.privacyConfig)

e.me.RecordRequestPrivacy(privacyLabels)

if cleanMetrics.gdprEnforced {
e.me.RecordTCFReq(pbsmetrics.TCFVersionToValue(cleanMetrics.gdprTcfVersion))
}
// List of bidders we have requests for.
liveAdapters := listBiddersWithRequests(cleanRequests)

Expand Down
22 changes: 10 additions & 12 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,6 @@ import (
"github.com/prebid/prebid-server/privacy/lmt"
)

// cleanMetrics is a struct to export any metrics data resulting from cleanOpenRTBRequests(). It starts with just
// the TCF version, but made a struct to facilitate future expansion
type cleanMetrics struct {
// A simple flag if GDPR is being enforced on this request.
gdprEnforced bool
// a zero value means a missing or invalid GDPR string
gdprTcfVersion int
}

func BidderToPrebidSChains(req *openrtb_ext.ExtRequest) (map[string]*openrtb_ext.ExtRequestPrebidSChainSChain, error) {
bidderToSChains := make(map[string]*openrtb_ext.ExtRequestPrebidSChainSChain)

Expand Down Expand Up @@ -63,7 +54,7 @@ func cleanOpenRTBRequests(ctx context.Context,
labels pbsmetrics.Labels,
gDPR gdpr.Permissions,
usersyncIfAmbiguous bool,
privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, cleanMetrics cleanMetrics, errs []error) {
privacyConfig config.Privacy) (requestsByBidder map[openrtb_ext.BidderName]*openrtb.BidRequest, aliases map[string]string, privacyLabels pbsmetrics.PrivacyLabels, errs []error) {

impsByBidder, errs := splitImps(orig.Imp)
if len(errs) > 0 {
Expand Down Expand Up @@ -98,13 +89,20 @@ func cleanOpenRTBRequests(ctx context.Context,
LMT: lmtPolicy.ShouldEnforce(),
}

privacyLabels.CCPAProvided = ccpaPolicy.Value != ""
privacyLabels.CCPAEnforced = privacyEnforcement.CCPA
privacyLabels.COPPAEnforced = privacyEnforcement.COPPA
privacyLabels.LMTEnforced = privacyEnforcement.LMT

if gdpr == 1 {
cleanMetrics.gdprEnforced = true
privacyLabels.GDPREnforced = true
parsedConsent, err := vendorconsent.ParseString(consent)
if err == nil {
cleanMetrics.gdprTcfVersion = int(parsedConsent.Version())
version := int(parsedConsent.Version())
privacyLabels.GDPRTCFVersion = pbsmetrics.TCFVersionToValue(version)
}
}

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

Expand Down
198 changes: 179 additions & 19 deletions exchange/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import (
// permissionsMock mocks the Permissions interface for tests
//
// It only allows appnexus for GDPR consent
type permissionsMock struct{}
type permissionsMock struct {
personalInfoAllowed bool
}

func (p *permissionsMock) HostCookiesAllowed(ctx context.Context, consent string) (bool, error) {
return true, nil
Expand All @@ -26,10 +28,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
}

func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, bool, error) {
if bidder == "appnexus" {
return true, true, nil
}
return false, false, nil
return p.personalInfoAllowed, p.personalInfoAllowed, nil
}

func (p *permissionsMock) AMPException() bool {
Expand Down Expand Up @@ -80,7 +79,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
}

for _, test := range testCases {
reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
reqByBidders, _, _, err := cleanOpenRTBRequests(context.Background(), test.req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
if test.hasError {
assert.NotNil(t, err, "Error shouldn't be nil")
} else {
Expand All @@ -92,26 +91,48 @@ func TestCleanOpenRTBRequests(t *testing.T) {

func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
testCases := []struct {
description string
enforceCCPA bool
expectDataScrub bool
description string
ccpaConsent string
enforceCCPA bool
expectDataScrub bool
expectPrivacyLabels pbsmetrics.PrivacyLabels
}{
{
description: "Feature Flag Enabled",
description: "Feature Flag Enabled - Opt Out",
ccpaConsent: "1-Y-",
enforceCCPA: true,
expectDataScrub: true,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
CCPAProvided: true,
CCPAEnforced: true,
},
},
{
description: "Feature Flag Enabled - Opt In",
ccpaConsent: "1-N-",
enforceCCPA: true,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
CCPAProvided: true,
CCPAEnforced: false,
},
},
{
description: "Feature Flag Disabled",
ccpaConsent: "1-Y-",
enforceCCPA: false,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
CCPAProvided: false,
CCPAEnforced: false,
},
},
}

for _, test := range testCases {
req := newBidRequest(t)
req.Regs = &openrtb.Regs{
Ext: json.RawMessage(`{"us_privacy":"1-Y-"}`),
Ext: json.RawMessage(`{"us_privacy":"` + test.ccpaConsent + `"}`),
}

privacyConfig := config.Privacy{
Expand All @@ -120,18 +141,62 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
},
}

results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
result := results["appnexus"]

assert.Nil(t, errs)

if test.expectDataScrub {
assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
} else {
assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
}

func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
testCases := []struct {
description string
coppa int8
expectDataScrub bool
expectPrivacyLabels pbsmetrics.PrivacyLabels
}{
{
description: "Enabled",
coppa: 1,
expectDataScrub: true,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
COPPAEnforced: true,
},
},
{
description: "Disabled",
coppa: 0,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
COPPAEnforced: false,
},
},
}

for _, test := range testCases {
req := newBidRequest(t)
req.Regs = &openrtb.Regs{COPPA: test.coppa}

results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, config.Privacy{})
result := results["appnexus"]

assert.Nil(t, errs)
if test.expectDataScrub {
assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.User.Yob, int64(0), test.description+":User.Yob")
} else {
assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.User.Yob, int64(0), test.description+":User.Yob")
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
}

Expand Down Expand Up @@ -227,34 +292,47 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
disabled int8 = 0
)
testCases := []struct {
description string
lmt *int8
enforceLMT bool
expectDataScrub bool
description string
lmt *int8
enforceLMT bool
expectDataScrub bool
expectPrivacyLabels pbsmetrics.PrivacyLabels
}{
{
description: "Feature Flag Enabled - OpenTRB Enabled",
lmt: &enabled,
enforceLMT: true,
expectDataScrub: true,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
LMTEnforced: true,
},
},
{
description: "Feature Flag Disabled - OpenTRB Enabled",
lmt: &enabled,
enforceLMT: false,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
LMTEnforced: false,
},
},
{
description: "Feature Flag Enabled - OpenTRB Disabled",
lmt: &disabled,
enforceLMT: true,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
LMTEnforced: false,
},
},
{
description: "Feature Flag Disabled - OpenTRB Disabled",
lmt: &disabled,
enforceLMT: false,
expectDataScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
LMTEnforced: false,
},
},
}

Expand All @@ -268,18 +346,99 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
},
}

results, _, _, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{}, true, privacyConfig)
results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: true}, true, privacyConfig)
result := results["appnexus"]

assert.Nil(t, errs)

if test.expectDataScrub {
assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
} else {
assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
}

func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
testCases := []struct {
description string
gdpr string
gdprConsent string
gdprScrub bool
enforceGDPR bool
expectPrivacyLabels pbsmetrics.PrivacyLabels
}{
{
description: "Enforce - TCF Invalid",
gdpr: "1",
gdprConsent: "malformed",
gdprScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: "",
},
},
{
description: "Enforce - TCF 1",
gdpr: "1",
gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
gdprScrub: true,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: pbsmetrics.TCFVersionV1,
},
},
{
description: "Enforce - TCF 2",
gdpr: "1",
gdprConsent: "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA",
gdprScrub: true,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
GDPREnforced: true,
GDPRTCFVersion: pbsmetrics.TCFVersionV2,
},
},
{
description: "Not Enforce - TCF 1",
gdpr: "0",
gdprConsent: "BONV8oqONXwgmADACHENAO7pqzAAppY",
gdprScrub: false,
expectPrivacyLabels: pbsmetrics.PrivacyLabels{
GDPREnforced: false,
GDPRTCFVersion: "",
},
},
}

for _, test := range testCases {
req := newBidRequest(t)
req.User.Ext = json.RawMessage(`{"consent":"` + test.gdprConsent + `"}`)
req.Regs = &openrtb.Regs{
Ext: json.RawMessage(`{"gdpr":` + test.gdpr + `}`),
}

privacyConfig := config.Privacy{
GDPR: config.GDPR{
TCF2: config.TCF2{
Enabled: true,
},
},
}

results, _, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), req, &emptyUsersync{}, map[openrtb_ext.BidderName]*pbsmetrics.AdapterLabels{}, pbsmetrics.Labels{}, &permissionsMock{personalInfoAllowed: !test.gdprScrub}, true, privacyConfig)
result := results["appnexus"]

assert.Nil(t, errs)
if test.gdprScrub {
assert.Equal(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.Equal(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
} else {
assert.NotEqual(t, result.User.BuyerUID, "", test.description+":User.BuyerUID")
assert.NotEqual(t, result.Device.DIDMD5, "", test.description+":Device.DIDMD5")
}
assert.Equal(t, test.expectPrivacyLabels, privacyLabels, test.description+":PrivacyLabels")
}
}

Expand Down Expand Up @@ -352,6 +511,7 @@ func newBidRequest(t *testing.T) *openrtb.BidRequest {
User: &openrtb.User{
ID: "our-id",
BuyerUID: "their-id",
Yob: 1982,
Ext: json.RawMessage(`{"digitrust":{"id":"digi-id","keyv":1,"pref":1}}`),
},
Imp: []openrtb.Imp{{
Expand Down
2 changes: 1 addition & 1 deletion gdpr/impl_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ func TestAllowPersonalInfoTCF2(t *testing.T) {
},
}

// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consensts to purposes and vendors 2, 6, 8
// COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA : TCF2 with full consents to purposes and vendors 2, 6, 8
// PI needs all purposes to succeed
testDefs := []tcf2TestDef{
{
Expand Down
Loading

0 comments on commit 7bf6b04

Please sign in to comment.