-
Notifications
You must be signed in to change notification settings - Fork 760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
GDPR: pass geo based on special feature 1 opt-in #2122
Changes from all commits
5e51ffa
676d685
7387ee7
2ff502c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -287,7 +287,7 @@ type TCF2 struct { | |
Purpose8 TCF2Purpose `mapstructure:"purpose8"` | ||
Purpose9 TCF2Purpose `mapstructure:"purpose9"` | ||
Purpose10 TCF2Purpose `mapstructure:"purpose10"` | ||
SpecialPurpose1 TCF2Purpose `mapstructure:"special_purpose1"` | ||
SpecialFeature1 TCF2SpecialFeature `mapstructure:"special_feature1"` | ||
PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"` | ||
} | ||
|
||
|
@@ -301,6 +301,13 @@ type TCF2Purpose struct { | |
VendorExceptionMap map[openrtb_ext.BidderName]struct{} | ||
} | ||
|
||
type TCF2SpecialFeature struct { | ||
Enforce bool `mapstructure:"enforce"` | ||
// Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed | ||
VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions"` | ||
VendorExceptionMap map[openrtb_ext.BidderName]struct{} | ||
} | ||
|
||
type TCF2PurposeOneTreatment struct { | ||
Enabled bool `mapstructure:"enabled"` | ||
AccessAllowed bool `mapstructure:"access_allowed"` | ||
|
@@ -556,7 +563,6 @@ func New(v *viper.Viper) (*Configuration, error) { | |
&c.GDPR.TCF2.Purpose8, | ||
&c.GDPR.TCF2.Purpose9, | ||
&c.GDPR.TCF2.Purpose10, | ||
&c.GDPR.TCF2.SpecialPurpose1, | ||
} | ||
for c := 0; c < len(purposeConfigs); c++ { | ||
purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) | ||
|
@@ -567,6 +573,14 @@ func New(v *viper.Viper) (*Configuration, error) { | |
} | ||
} | ||
|
||
// To look for a special feature's vendor exceptions in O(1) time, we fill this hash table with bidders located in the | ||
// VendorExceptions field of the GDPR.TCF2.SpecialFeature1 struct defined in this file | ||
c.GDPR.TCF2.SpecialFeature1.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{}) | ||
for v := 0; v < len(c.GDPR.TCF2.SpecialFeature1.VendorExceptions); v++ { | ||
bidderName := c.GDPR.TCF2.SpecialFeature1.VendorExceptions[v] | ||
c.GDPR.TCF2.SpecialFeature1.VendorExceptionMap[bidderName] = struct{}{} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please verify this code has full test coverage for cases of none, one, and many (2). It's been a problem for us in the past. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Done |
||
|
||
// To look for a request's app_id in O(1) time, we fill this hash table located in the | ||
// the BlacklistedApps field of the Configuration struct defined in this file | ||
c.BlacklistedAppMap = make(map[string]bool) | ||
|
@@ -950,8 +964,6 @@ func SetupViper(v *viper.Viper, filename string) { | |
v.SetDefault("gdpr.tcf2.purpose8.vendor_exceptions", []openrtb_ext.BidderName{}) | ||
v.SetDefault("gdpr.tcf2.purpose9.vendor_exceptions", []openrtb_ext.BidderName{}) | ||
v.SetDefault("gdpr.tcf2.purpose10.vendor_exceptions", []openrtb_ext.BidderName{}) | ||
v.SetDefault("gdpr.tcf2.special_purpose1.enabled", true) | ||
v.SetDefault("gdpr.tcf2.special_purpose1.vendor_exceptions", []openrtb_ext.BidderName{}) | ||
v.SetDefault("gdpr.amp_exception", false) | ||
v.SetDefault("gdpr.eea_countries", []string{"ALA", "AUT", "BEL", "BGR", "HRV", "CYP", "CZE", "DNK", "EST", | ||
"FIN", "FRA", "GUF", "DEU", "GIB", "GRC", "GLP", "GGY", "HUN", "ISL", "IRL", "IMN", "ITA", "JEY", "LVA", | ||
|
@@ -1008,6 +1020,7 @@ func SetupViper(v *viper.Viper, filename string) { | |
// Migrate config settings to maintain compatibility with old configs | ||
migrateConfig(v) | ||
migrateConfigPurposeOneTreatment(v) | ||
migrateConfigSpecialFeature1(v) | ||
migrateConfigTCF2PurposeEnabledFlags(v) | ||
|
||
// These defaults must be set after the migrate functions because those functions look for the presence of these | ||
|
@@ -1035,6 +1048,8 @@ func SetupViper(v *viper.Viper, filename string) { | |
v.SetDefault("gdpr.tcf2.purpose10.enforce_purpose", TCF2FullEnforcement) | ||
v.SetDefault("gdpr.tcf2.purpose_one_treatment.enabled", true) | ||
v.SetDefault("gdpr.tcf2.purpose_one_treatment.access_allowed", true) | ||
v.SetDefault("gdpr.tcf2.special_feature1.enforce", true) | ||
v.SetDefault("gdpr.tcf2.special_feature1.vendor_exceptions", []openrtb_ext.BidderName{}) | ||
} | ||
|
||
func migrateConfig(v *viper.Viper) { | ||
|
@@ -1062,6 +1077,19 @@ func migrateConfigPurposeOneTreatment(v *viper.Viper) { | |
} | ||
} | ||
|
||
func migrateConfigSpecialFeature1(v *viper.Viper) { | ||
if oldConfig, ok := v.Get("gdpr.tcf2.special_purpose1").(map[string]interface{}); ok { | ||
if v.IsSet("gdpr.tcf2.special_feature1") { | ||
glog.Warning("using gdpr.tcf2.special_feature1 and ignoring deprecated gdpr.tcf2.special_purpose1") | ||
} else { | ||
glog.Warning("gdpr.tcf2.special_purpose1.enabled is deprecated and should be changed to gdpr.tcf2.special_feature1.enforce") | ||
glog.Warning("gdpr.tcf2.special_purpose1.vendor_exceptions is deprecated and should be changed to gdpr.tcf2.special_feature1.vendor_exceptions") | ||
v.Set("gdpr.tcf2.special_feature1.enforce", oldConfig["enabled"]) | ||
v.Set("gdpr.tcf2.special_feature1.vendor_exceptions", oldConfig["vendor_exceptions"]) | ||
} | ||
} | ||
} | ||
|
||
func migrateConfigTCF2PurposeEnabledFlags(v *viper.Viper) { | ||
for i := 1; i <= 10; i++ { | ||
oldField := fmt.Sprintf("gdpr.tcf2.purpose%d.enabled", i) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -217,8 +217,8 @@ func TestDefaults(t *testing.T) { | |
VendorExceptions: []openrtb_ext.BidderName{}, | ||
VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, | ||
}, | ||
SpecialPurpose1: TCF2Purpose{ | ||
Enabled: true, | ||
SpecialFeature1: TCF2SpecialFeature{ | ||
Enforce: true, | ||
VendorExceptions: []openrtb_ext.BidderName{}, | ||
VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{}, | ||
}, | ||
|
@@ -272,7 +272,7 @@ gdpr: | |
purpose10: | ||
enforce_vendors: false | ||
vendor_exceptions: ["foo10"] | ||
special_purpose1: | ||
special_feature1: | ||
vendor_exceptions: ["fooSP1"] | ||
ccpa: | ||
enforce: true | ||
|
@@ -540,8 +540,8 @@ func TestFullConfig(t *testing.T) { | |
VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("foo10")}, | ||
VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("foo10"): {}}, | ||
}, | ||
SpecialPurpose1: TCF2Purpose{ | ||
Enabled: true, // true by default | ||
SpecialFeature1: TCF2SpecialFeature{ | ||
Enforce: true, // true by default | ||
VendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("fooSP1")}, | ||
VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("fooSP1"): {}}, | ||
}, | ||
|
@@ -765,6 +765,91 @@ func TestMigrateConfigPurposeOneTreatment(t *testing.T) { | |
} | ||
} | ||
|
||
func TestMigrateConfigSpecialFeature1(t *testing.T) { | ||
oldSpecialFeature1Config := []byte(` | ||
gdpr: | ||
tcf2: | ||
special_purpose1: | ||
enabled: true | ||
vendor_exceptions: ["appnexus"] | ||
`) | ||
newSpecialFeature1Config := []byte(` | ||
gdpr: | ||
tcf2: | ||
special_feature1: | ||
enforce: true | ||
vendor_exceptions: ["appnexus"] | ||
`) | ||
oldAndNewSpecialFeature1Config := []byte(` | ||
gdpr: | ||
tcf2: | ||
special_purpose1: | ||
enabled: false | ||
vendor_exceptions: ["appnexus"] | ||
special_feature1: | ||
enforce: true | ||
vendor_exceptions: ["rubicon"] | ||
`) | ||
|
||
tests := []struct { | ||
description string | ||
config []byte | ||
wantSpecialFeature1Enforce bool | ||
wantSpecialFeature1VendorExceptions []string | ||
}{ | ||
{ | ||
description: "New config and old config not set", | ||
config: []byte{}, | ||
}, | ||
{ | ||
description: "New config not set, old config set", | ||
config: oldSpecialFeature1Config, | ||
wantSpecialFeature1Enforce: true, | ||
wantSpecialFeature1VendorExceptions: []string{"appnexus"}, | ||
}, | ||
{ | ||
description: "New config set, old config not set", | ||
config: newSpecialFeature1Config, | ||
wantSpecialFeature1Enforce: true, | ||
wantSpecialFeature1VendorExceptions: []string{"appnexus"}, | ||
}, | ||
{ | ||
description: "New config and old config set", | ||
config: oldAndNewSpecialFeature1Config, | ||
wantSpecialFeature1Enforce: true, | ||
wantSpecialFeature1VendorExceptions: []string{"rubicon"}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
v := viper.New() | ||
v.SetConfigType("yaml") | ||
v.ReadConfig(bytes.NewBuffer(tt.config)) | ||
|
||
migrateConfigSpecialFeature1(v) | ||
|
||
if len(tt.config) > 0 { | ||
assert.Equal(t, tt.wantSpecialFeature1Enforce, v.Get("gdpr.tcf2.special_feature1.enforce").(bool), tt.description) | ||
assert.Equal(t, tt.wantSpecialFeature1VendorExceptions, v.GetStringSlice("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) | ||
} else { | ||
assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.enforce"), tt.description) | ||
assert.Nil(t, v.Get("gdpr.tcf2.special_feature1.vendor_exceptions"), tt.description) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I find this to be a very comprehensive test case but: can we make one last assertion in the resulting
Or even better, a
Another option is to probably add an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @guscarreon and I spoke offline about this. My line of thinking was that the coverage was there across three unit tests but it was hard to see: I'm hesitant to make use of another function in the package to perform assertions in a pure unit test so instead of calling |
||
|
||
var c Configuration | ||
err := v.Unmarshal(&c) | ||
assert.NoError(t, err, tt.description) | ||
assert.Equal(t, tt.wantSpecialFeature1Enforce, c.GDPR.TCF2.SpecialFeature1.Enforce, tt.description) | ||
|
||
// convert expected vendor exceptions to type BidderName | ||
expectedVendorExceptions := make([]openrtb_ext.BidderName, 0, 0) | ||
for _, ve := range tt.wantSpecialFeature1VendorExceptions { | ||
expectedVendorExceptions = append(expectedVendorExceptions, openrtb_ext.BidderName(ve)) | ||
} | ||
assert.ElementsMatch(t, expectedVendorExceptions, c.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description) | ||
} | ||
} | ||
|
||
func TestMigrateConfigTCF2PurposeEnabledFlags(t *testing.T) { | ||
trueStr := "true" | ||
falseStr := "false" | ||
|
@@ -1291,3 +1376,58 @@ func doTimeoutTest(t *testing.T, expected int, requested int, max uint64, def ui | |
limited := cfg.LimitAuctionTimeout(time.Duration(requested) * time.Millisecond) | ||
assert.Equal(t, limited, expectedDuration, "Expected %dms timeout, got %dms", expectedDuration, limited/time.Millisecond) | ||
} | ||
|
||
func TestSpecialFeature1VendorExceptionMap(t *testing.T) { | ||
baseConfig := []byte(` | ||
gdpr: | ||
default_value: 0 | ||
tcf2: | ||
special_feature1: | ||
vendor_exceptions: `) | ||
|
||
tests := []struct { | ||
description string | ||
configVendorExceptions []byte | ||
wantVendorExceptions []openrtb_ext.BidderName | ||
wantVendorExceptionsMap map[openrtb_ext.BidderName]struct{} | ||
}{ | ||
{ | ||
description: "nil vendor exceptions", | ||
configVendorExceptions: []byte(`null`), | ||
wantVendorExceptions: []openrtb_ext.BidderName{}, | ||
wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{}, | ||
}, | ||
{ | ||
description: "no vendor exceptions", | ||
configVendorExceptions: []byte(`[]`), | ||
wantVendorExceptions: []openrtb_ext.BidderName{}, | ||
wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{}, | ||
}, | ||
{ | ||
description: "one vendor exception", | ||
configVendorExceptions: []byte(`["vendor1"]`), | ||
wantVendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("vendor1")}, | ||
wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("vendor1"): {}}, | ||
}, | ||
{ | ||
description: "many exceptions", | ||
configVendorExceptions: []byte(`["vendor1","vendor2"]`), | ||
wantVendorExceptions: []openrtb_ext.BidderName{openrtb_ext.BidderName("vendor1"), openrtb_ext.BidderName("vendor2")}, | ||
wantVendorExceptionsMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderName("vendor1"): {}, openrtb_ext.BidderName("vendor2"): {}}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
config := append(baseConfig, tt.configVendorExceptions...) | ||
|
||
v := viper.New() | ||
SetupViper(v, "") | ||
v.SetConfigType("yaml") | ||
v.ReadConfig(bytes.NewBuffer(config)) | ||
cfg, err := New(v) | ||
assert.NoError(t, err, "Setting up config error", tt.description) | ||
|
||
assert.Equal(t, tt.wantVendorExceptions, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptions, tt.description) | ||
assert.Equal(t, tt.wantVendorExceptionsMap, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptionMap, tt.description) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. We were using the wrong terminology?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not just a matter of the wrong terminology. We are looking at the wrong field in the GVL. The go-gdpr package provides a
vendor.SpecialPurpose
method that checks if a special purpose is defined in the GVLspecialPurposes
field for a vendor. We are currently calling this method but that isn't correct. Instead we should be looking at GVLspecialFeatures
for a vendor. I have a go-gdpr PR out that is currently under review that will add a new method that allows us to access the correct field for a given vendor.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'a concerning. What's the difference between a special purpose and a special feature? The TCF folks could've done better in the naming there.