From 97089ab077e4170742c29b06727b12a2f7b65080 Mon Sep 17 00:00:00 2001 From: bsardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 14 Dec 2021 16:49:17 -0500 Subject: [PATCH 1/3] Add special feature support to vendor list parsers --- .gitignore | 3 +++ api/vendorlist.go | 3 +++ consentconstants/tcf2/features.go | 15 +++++++++++++++ vendorlist/eager-parsing.go | 8 +++++++- vendorlist/lazy-parsing.go | 8 +++++++- vendorlist2/eager-parsing.go | 29 ++++++++++++++++++++++++----- vendorlist2/lazy-parsing.go | 6 ++++++ vendorlist2/shared_test.go | 12 ++++++++---- 8 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 consentconstants/tcf2/features.go diff --git a/.gitignore b/.gitignore index 48b8bf9..0b565f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ vendor/ + +.idea/ +.vscode/ \ No newline at end of file diff --git a/api/vendorlist.go b/api/vendorlist.go index ffa6d6b..147130f 100644 --- a/api/vendorlist.go +++ b/api/vendorlist.go @@ -1,6 +1,7 @@ package api import "github.com/prebid/go-gdpr/consentconstants" +import tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" // VendorList is an interface used to fetch information about an IAB Global Vendor list. // For the latest version, see: https://vendorlist.consensu.org/vendorlist.json @@ -34,4 +35,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 tcf2ConsentConstants.SpecialFeature) (hasSpecialFeature bool) } diff --git a/consentconstants/tcf2/features.go b/consentconstants/tcf2/features.go new file mode 100644 index 0000000..de00ead --- /dev/null +++ b/consentconstants/tcf2/features.go @@ -0,0 +1,15 @@ +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 + +// TCF 2.0 Special Features: +const ( + // Use precise geolocation data to select and deliver an ad in the moment, without storing it. + Geolocation SpecialFeature = 1 + + // Identify a device by actively scanning device characteristics in order to select an ad in the moment. + DeviceScan SpecialFeature = 2 +) diff --git a/vendorlist/eager-parsing.go b/vendorlist/eager-parsing.go index 2a72aeb..bfc1521 100644 --- a/vendorlist/eager-parsing.go +++ b/vendorlist/eager-parsing.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseEagerly interprets and validates the Vendor List data up front, before returning it. @@ -106,11 +107,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 tcf2ConsentConstants.SpecialFeature) bool { + return false +} + type vendorListContract struct { Version uint16 `json:"vendorListVersion"` Vendors []vendorListVendorContract `json:"vendors"` diff --git a/vendorlist/lazy-parsing.go b/vendorlist/lazy-parsing.go index 4ba5e8b..ffc5797 100644 --- a/vendorlist/lazy-parsing.go +++ b/vendorlist/lazy-parsing.go @@ -6,6 +6,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseLazily returns a view of the data which re-calculates things on each function call. @@ -63,11 +64,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 tcf2ConsentConstants.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 diff --git a/vendorlist2/eager-parsing.go b/vendorlist2/eager-parsing.go index 88b0acf..86885cd 100644 --- a/vendorlist2/eager-parsing.go +++ b/vendorlist2/eager-parsing.go @@ -6,6 +6,7 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseEagerly interprets and validates the Vendor List data up front, before returning it. @@ -40,16 +41,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 { @@ -58,6 +60,15 @@ func mapify(input []uint8) map[consentconstants.Purpose]struct{} { return m } +func mapifySpecialFeature(input []uint8) map[tcf2ConsentConstants.SpecialFeature]struct{} { + m := make(map[tcf2ConsentConstants.SpecialFeature]struct{}, len(input)) + var s struct{} + for _, value := range input { + m[tcf2ConsentConstants.SpecialFeature(value)] = s + } + return m +} + type parsedVendorList struct { version uint16 vendors map[uint16]parsedVendor @@ -80,6 +91,7 @@ type parsedVendor struct { legitimateInterests map[consentconstants.Purpose]struct{} flexiblePurposes map[consentconstants.Purpose]struct{} specialPurposes map[consentconstants.Purpose]struct{} + specialFeatures map[tcf2ConsentConstants.SpecialFeature]struct{} } func (l parsedVendor) Purpose(purposeID consentconstants.Purpose) (hasPurpose bool) { @@ -120,6 +132,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 tcf2ConsentConstants.SpecialFeature) (hasSpecialFeature bool) { + _, hasSpecialFeature = l.specialFeatures[featureID] + return +} + type vendorListContract struct { Version uint16 `json:"vendorListVersion"` Vendors map[string]vendorListVendorContract `json:"vendors"` @@ -131,4 +149,5 @@ type vendorListVendorContract struct { LegitimateInterests []uint8 `json:"legIntPurposes"` FlexiblePurposes []uint8 `json:"flexiblePurposes"` SpecialPurposes []uint8 `json:"specialPurposes"` + SpecialFeatures []uint8 `json:"specialFeatures"` } diff --git a/vendorlist2/lazy-parsing.go b/vendorlist2/lazy-parsing.go index fe5e8ba..9353226 100644 --- a/vendorlist2/lazy-parsing.go +++ b/vendorlist2/lazy-parsing.go @@ -6,6 +6,7 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" + tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseLazily returns a view of the data which re-calculates things on each function call. @@ -70,6 +71,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 tcf2ConsentConstants.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 diff --git a/vendorlist2/shared_test.go b/vendorlist2/shared_test.go index 49f498b..068b2ff 100644 --- a/vendorlist2/shared_test.go +++ b/vendorlist2/shared_test.go @@ -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)) @@ -69,9 +73,9 @@ func vendorTester(parser func(data []byte) api.VendorList) func(*testing.T) { assertBoolsEqual(t, false, v.LegitimateInterest(3)) assertBoolsEqual(t, false, v.LegitimateInterestStrict(3)) - 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 } } @@ -91,7 +95,7 @@ const testData = ` "flexiblePurposes": [2, 9], "specialPurposes": [1, 2], "features": [1, 2], - "specialFeatures": [], + "specialFeatures": [1, 2], "policyUrl": "https://www.emerse.com/privacy-policy/" }, "80": { From 99a92d0f8754f78d04585a695345e8abdeeaac16 Mon Sep 17 00:00:00 2001 From: bsardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:42:17 -0500 Subject: [PATCH 2/3] Refactor to follow how consent constants purposes are defined --- api/vendorlist.go | 3 +-- consentconstants/features.go | 6 ++++++ consentconstants/tcf2/features.go | 9 +++------ vendorlist/eager-parsing.go | 3 +-- vendorlist/lazy-parsing.go | 3 +-- vendorlist2/eager-parsing.go | 11 +++++------ vendorlist2/lazy-parsing.go | 3 +-- 7 files changed, 18 insertions(+), 20 deletions(-) create mode 100644 consentconstants/features.go diff --git a/api/vendorlist.go b/api/vendorlist.go index 147130f..eaf2785 100644 --- a/api/vendorlist.go +++ b/api/vendorlist.go @@ -1,7 +1,6 @@ package api import "github.com/prebid/go-gdpr/consentconstants" -import tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" // VendorList is an interface used to fetch information about an IAB Global Vendor list. // For the latest version, see: https://vendorlist.consensu.org/vendorlist.json @@ -36,5 +35,5 @@ type Vendor interface { // 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 tcf2ConsentConstants.SpecialFeature) (hasSpecialFeature bool) + SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) } diff --git a/consentconstants/features.go b/consentconstants/features.go new file mode 100644 index 0000000..f7f300f --- /dev/null +++ b/consentconstants/features.go @@ -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 \ No newline at end of file diff --git a/consentconstants/tcf2/features.go b/consentconstants/tcf2/features.go index de00ead..deef4a0 100644 --- a/consentconstants/tcf2/features.go +++ b/consentconstants/tcf2/features.go @@ -1,15 +1,12 @@ 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 +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 SpecialFeature = 1 + Geolocation base.SpecialFeature = 1 // Identify a device by actively scanning device characteristics in order to select an ad in the moment. - DeviceScan SpecialFeature = 2 + DeviceScan base.SpecialFeature = 2 ) diff --git a/vendorlist/eager-parsing.go b/vendorlist/eager-parsing.go index bfc1521..b84bf95 100644 --- a/vendorlist/eager-parsing.go +++ b/vendorlist/eager-parsing.go @@ -6,7 +6,6 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseEagerly interprets and validates the Vendor List data up front, before returning it. @@ -113,7 +112,7 @@ func (l parsedVendor) SpecialPurpose(purposeID consentconstants.Purpose) bool { } // V1 vendor list does not support special features. -func (l parsedVendor) SpecialFeature(featureID tcf2ConsentConstants.SpecialFeature) bool { +func (l parsedVendor) SpecialFeature(featureID consentconstants.SpecialFeature) bool { return false } diff --git a/vendorlist/lazy-parsing.go b/vendorlist/lazy-parsing.go index ffc5797..bdf061c 100644 --- a/vendorlist/lazy-parsing.go +++ b/vendorlist/lazy-parsing.go @@ -6,7 +6,6 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseLazily returns a view of the data which re-calculates things on each function call. @@ -70,7 +69,7 @@ func (l lazyVendor) SpecialPurpose(purposeID consentconstants.Purpose) bool { } // V1 vendor list does not support special features. -func (l lazyVendor) SpecialFeature(featureID tcf2ConsentConstants.SpecialFeature) bool { +func (l lazyVendor) SpecialFeature(featureID consentconstants.SpecialFeature) bool { return false } diff --git a/vendorlist2/eager-parsing.go b/vendorlist2/eager-parsing.go index 86885cd..718ea98 100644 --- a/vendorlist2/eager-parsing.go +++ b/vendorlist2/eager-parsing.go @@ -6,7 +6,6 @@ import ( "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseEagerly interprets and validates the Vendor List data up front, before returning it. @@ -60,11 +59,11 @@ func mapifyPurpose(input []uint8) map[consentconstants.Purpose]struct{} { return m } -func mapifySpecialFeature(input []uint8) map[tcf2ConsentConstants.SpecialFeature]struct{} { - m := make(map[tcf2ConsentConstants.SpecialFeature]struct{}, len(input)) +func mapifySpecialFeature(input []uint8) map[consentconstants.SpecialFeature]struct{} { + m := make(map[consentconstants.SpecialFeature]struct{}, len(input)) var s struct{} for _, value := range input { - m[tcf2ConsentConstants.SpecialFeature(value)] = s + m[consentconstants.SpecialFeature(value)] = s } return m } @@ -91,7 +90,7 @@ type parsedVendor struct { legitimateInterests map[consentconstants.Purpose]struct{} flexiblePurposes map[consentconstants.Purpose]struct{} specialPurposes map[consentconstants.Purpose]struct{} - specialFeatures map[tcf2ConsentConstants.SpecialFeature]struct{} + specialFeatures map[consentconstants.SpecialFeature]struct{} } func (l parsedVendor) Purpose(purposeID consentconstants.Purpose) (hasPurpose bool) { @@ -133,7 +132,7 @@ func (l parsedVendor) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpe } // SpecialFeature returns true if this vendor claims a need for the given special feature -func (l parsedVendor) SpecialFeature(featureID tcf2ConsentConstants.SpecialFeature) (hasSpecialFeature bool) { +func (l parsedVendor) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) { _, hasSpecialFeature = l.specialFeatures[featureID] return } diff --git a/vendorlist2/lazy-parsing.go b/vendorlist2/lazy-parsing.go index 9353226..ec5dd22 100644 --- a/vendorlist2/lazy-parsing.go +++ b/vendorlist2/lazy-parsing.go @@ -6,7 +6,6 @@ import ( "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/api" "github.com/prebid/go-gdpr/consentconstants" - tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2" ) // ParseLazily returns a view of the data which re-calculates things on each function call. @@ -72,7 +71,7 @@ func (l lazyVendor) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpeci } // SpecialFeature returns true if this vendor claims a need for the given special feature -func (l lazyVendor) SpecialFeature(featureID tcf2ConsentConstants.SpecialFeature) (hasSpecialFeature bool) { +func (l lazyVendor) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) { return idExists(l, int(featureID), "specialFeatures") } From b60131f7b9afb0fdc10b82db2e04996d5c2046ff Mon Sep 17 00:00:00 2001 From: bsardo <1168933+bsardo@users.noreply.github.com> Date: Tue, 14 Dec 2021 18:50:13 -0500 Subject: [PATCH 3/3] Re-add deleted test assertions --- vendorlist2/shared_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vendorlist2/shared_test.go b/vendorlist2/shared_test.go index 068b2ff..1eb3f43 100644 --- a/vendorlist2/shared_test.go +++ b/vendorlist2/shared_test.go @@ -73,6 +73,10 @@ func vendorTester(parser func(data []byte) api.VendorList) func(*testing.T) { assertBoolsEqual(t, false, v.LegitimateInterest(3)) assertBoolsEqual(t, false, v.LegitimateInterestStrict(3)) + 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