Skip to content
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

Add special feature support to vendor list parsers #34

Merged
merged 3 commits into from
Jan 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
vendor/

.idea/
.vscode/
2 changes: 2 additions & 0 deletions api/vendorlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ type Vendor interface {
LegitimateInterestStrict(purposeID consentconstants.Purpose) (hasLegitimateInterest bool)
// SpecialPurpose returns true if this vendor claims a need for the given special purpose
SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool)
// SpecialFeature returns true if this vendor claims a need for the given special feature
SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool)
}
6 changes: 6 additions & 0 deletions consentconstants/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package consentconstants

// SpecialFeature is one of the IAB GDPR special features. These appear in:
// 1. `root.specialFeatures[i]` of the vendor list: https://vendorlist.consensu.org/vendorlist.json
// 2. SpecialFeatureOptIns of the Consent string: https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/Consent%20string%20and%20vendor%20list%20formats%20v1.1%20Final.md#vendor-consent-string-format-
type SpecialFeature uint8
12 changes: 12 additions & 0 deletions consentconstants/tcf2/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package consentconstants

import base "github.com/prebid/go-gdpr/consentconstants"

// TCF 2.0 Special Features:
const (
// Use precise geolocation data to select and deliver an ad in the moment, without storing it.
Geolocation base.SpecialFeature = 1

// Identify a device by actively scanning device characteristics in order to select an ad in the moment.
DeviceScan base.SpecialFeature = 2
)
7 changes: 6 additions & 1 deletion vendorlist/eager-parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,16 @@ func (l parsedVendor) LegitimateInterestStrict(purposeID consentconstants.Purpos
return
}

// V1 vedndor list does not support special purposes.
// V1 vendor list does not support special purposes.
func (l parsedVendor) SpecialPurpose(purposeID consentconstants.Purpose) bool {
return false
}

// V1 vendor list does not support special features.
func (l parsedVendor) SpecialFeature(featureID consentconstants.SpecialFeature) bool {
return false
}

type vendorListContract struct {
Version uint16 `json:"vendorListVersion"`
Vendors []vendorListVendorContract `json:"vendors"`
Expand Down
7 changes: 6 additions & 1 deletion vendorlist/lazy-parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,16 @@ func (l lazyVendor) LegitimateInterestStrict(purposeID consentconstants.Purpose)
return idExists(l, int(purposeID), "legIntPurposeIds")
}

// V1 vedndor list does not support special purposes.
// V1 vendor list does not support special purposes.
func (l lazyVendor) SpecialPurpose(purposeID consentconstants.Purpose) bool {
return false
}

// V1 vendor list does not support special features.
func (l lazyVendor) SpecialFeature(featureID consentconstants.SpecialFeature) bool {
return false
}

// Returns false unless "id" exists in an array located at "data.key".
func idExists(data []byte, id int, key string) bool {
hasID := false
Expand Down
28 changes: 23 additions & 5 deletions vendorlist2/eager-parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,17 @@ func ParseEagerly(data []byte) (api.VendorList, error) {

func parseVendor(contract vendorListVendorContract) parsedVendor {
parsed := parsedVendor{
purposes: mapify(contract.Purposes),
legitimateInterests: mapify(contract.LegitimateInterests),
flexiblePurposes: mapify(contract.FlexiblePurposes),
specialPurposes: mapify(contract.SpecialPurposes),
purposes: mapifyPurpose(contract.Purposes),
legitimateInterests: mapifyPurpose(contract.LegitimateInterests),
flexiblePurposes: mapifyPurpose(contract.FlexiblePurposes),
specialPurposes: mapifyPurpose(contract.SpecialPurposes),
specialFeatures: mapifySpecialFeature(contract.SpecialFeatures),
}

return parsed
}

func mapify(input []uint8) map[consentconstants.Purpose]struct{} {
func mapifyPurpose(input []uint8) map[consentconstants.Purpose]struct{} {
m := make(map[consentconstants.Purpose]struct{}, len(input))
var s struct{}
for _, value := range input {
Expand All @@ -58,6 +59,15 @@ func mapify(input []uint8) map[consentconstants.Purpose]struct{} {
return m
}

func mapifySpecialFeature(input []uint8) map[consentconstants.SpecialFeature]struct{} {
m := make(map[consentconstants.SpecialFeature]struct{}, len(input))
var s struct{}
for _, value := range input {
m[consentconstants.SpecialFeature(value)] = s
}
return m
}

type parsedVendorList struct {
version uint16
vendors map[uint16]parsedVendor
Expand All @@ -80,6 +90,7 @@ type parsedVendor struct {
legitimateInterests map[consentconstants.Purpose]struct{}
flexiblePurposes map[consentconstants.Purpose]struct{}
specialPurposes map[consentconstants.Purpose]struct{}
specialFeatures map[consentconstants.SpecialFeature]struct{}
}

func (l parsedVendor) Purpose(purposeID consentconstants.Purpose) (hasPurpose bool) {
Expand Down Expand Up @@ -120,6 +131,12 @@ func (l parsedVendor) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpe
return
}

// SpecialFeature returns true if this vendor claims a need for the given special feature
func (l parsedVendor) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) {
_, hasSpecialFeature = l.specialFeatures[featureID]
return
}

type vendorListContract struct {
Version uint16 `json:"vendorListVersion"`
Vendors map[string]vendorListVendorContract `json:"vendors"`
Expand All @@ -131,4 +148,5 @@ type vendorListVendorContract struct {
LegitimateInterests []uint8 `json:"legIntPurposes"`
FlexiblePurposes []uint8 `json:"flexiblePurposes"`
SpecialPurposes []uint8 `json:"specialPurposes"`
SpecialFeatures []uint8 `json:"specialFeatures"`
}
5 changes: 5 additions & 0 deletions vendorlist2/lazy-parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (l lazyVendor) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpeci
return idExists(l, int(purposeID), "specialPurposes")
}

// SpecialFeature returns true if this vendor claims a need for the given special feature
func (l lazyVendor) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) {
return idExists(l, int(featureID), "specialFeatures")
}

// Returns false unless "id" exists in an array located at "data.key".
func idExists(data []byte, id int, key string) bool {
hasID := false
Expand Down
10 changes: 9 additions & 1 deletion vendorlist2/shared_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ func vendorTester(parser func(data []byte) api.VendorList) func(*testing.T) {
assertBoolsEqual(t, true, v.SpecialPurpose(2))
assertBoolsEqual(t, false, v.SpecialPurpose(3)) // Does not exist yet

assertBoolsEqual(t, true, v.SpecialFeature(1))
assertBoolsEqual(t, true, v.SpecialFeature(2))
assertBoolsEqual(t, false, v.SpecialFeature(3)) // Does not exist yet

v = list.Vendor(80)
assertBoolsEqual(t, true, v.Purpose(1))
assertBoolsEqual(t, true, v.PurposeStrict(1))
Expand All @@ -72,6 +76,10 @@ func vendorTester(parser func(data []byte) api.VendorList) func(*testing.T) {
assertBoolsEqual(t, false, v.SpecialPurpose(1))
assertBoolsEqual(t, false, v.SpecialPurpose(2))
assertBoolsEqual(t, false, v.SpecialPurpose(3)) // Does not exist yet

assertBoolsEqual(t, false, v.SpecialFeature(1))
assertBoolsEqual(t, false, v.SpecialFeature(2))
assertBoolsEqual(t, false, v.SpecialFeature(3)) // Does not exist yet
}

}
Expand All @@ -91,7 +99,7 @@ const testData = `
"flexiblePurposes": [2, 9],
"specialPurposes": [1, 2],
"features": [1, 2],
"specialFeatures": [],
"specialFeatures": [1, 2],
"policyUrl": "https://www.emerse.com/privacy-policy/"
},
"80": {
Expand Down