From ee967b1130dfe7ccfb457a06069a6f9b679bb4a7 Mon Sep 17 00:00:00 2001 From: Gorka Guridi Date: Thu, 2 May 2024 14:29:57 +0100 Subject: [PATCH] Adding CMP vendor list loading mechanism & validation against the CMP ID coming inside the consent. (#3) * Adding CMP vendor list loading mechanism & validation against the CMP ID coming inside the consent. * Using go install to install the testing dependencies. * Fixing error message as it's different depending on the environment we run the tests. --- .circleci/config.yml | 25 ++++++---- .prettierrc | 7 +++ LICENSE | 2 +- README.md | 48 ++++++++++++++++-- benchmark_test.go | 74 ++++++++++++--------------- cmp/consent.go | 13 +++++ cmp/consent_test.go | 40 +++++++++++++++ cmp/loader.go | 84 +++++++++++++++++++++++++++++++ cmp/loader_test.go | 116 +++++++++++++++++++++++++++++++++++++++++++ cmp/suite_test.go | 13 +++++ consent_v1_test.go | 34 +++++++++---- consent_v2_test.go | 55 +++++++++++++------- go.mod | 28 +++++++---- go.sum | 107 ++++++++++++++++----------------------- main.go | 47 ++++++++++-------- parser_v1.go | 20 +++++++- parser_v2.go | 28 ++++++++--- suite_test.go | 5 +- 18 files changed, 558 insertions(+), 188 deletions(-) create mode 100644 .prettierrc create mode 100644 cmp/consent.go create mode 100644 cmp/consent_test.go create mode 100644 cmp/loader.go create mode 100644 cmp/loader_test.go create mode 100644 cmp/suite_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index 55b8c70..e44a7ab 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,9 @@ version: 2 jobs: unit-testing: - working_directory: ~/src/github.com/affectv/iab-tcf + working_directory: ~/src/github.com/hybridtheory/iab-tcf docker: - - image: golang:1.13-stretch + - image: golang:1.21-bullseye auth: username: $DOCKERHUB_USERNAME password: $DOCKERHUB_PASSWORD @@ -11,31 +11,34 @@ jobs: - checkout - restore_cache: keys: - - v1-go-dependencies-{{ checksum "go.mod" }}-{{ checksum "go.sum" }} + - v1-go-dependencies-{{ checksum "go.mod" }}-{{ checksum "go.sum" }} - run: name: Install test framework command: | - go get -u -v github.com/onsi/ginkgo/ginkgo - go get -u -v github.com/onsi/gomega + go install github.com/onsi/ginkgo/v2/ginkgo + go install github.com/onsi/gomega/... - run: name: Running Unit Tests command: | - ginkgo -cover -race -outputdir=./ ./... + ginkgo --race -cover --junit-report=junit.xml --output-dir=./test-reports ./... - run: name: Generate code coverage command: | - echo "mode: set" > coverage.out - cat *.coverprofile | grep -v mode: | sort -r | awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out - go tool cover -html=coverage.out -o coverage.html + go tool cover -html=./test-reports/coverprofile.out -o ./test-reports/coverage.html - store_artifacts: - path: coverage.html - destination: coverage + path: ./test-reports/ + destination: artifacts - store_test_results: path: test-reports - save_cache: paths: - "/go/pkg/mod" key: v1-go-dependencies-{{ checksum "go.mod" }}-{{ checksum "go.sum" }} + - run: + name: codacy coverage + command: | + export CODACY_PROJECT_NAME=iab-tcf + bash <(curl -Lks https://coverage.codacy.com/get.sh) report --force-coverage-parser go -r ./test-reports/coverprofile.out workflows: version: 2 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..52d855e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 120, + "tabWidth": 2, + "singleQuote": false, + "trailingComma": "all", + "arrowParens": "always" +} diff --git a/LICENSE b/LICENSE index 9baaec7..f577c94 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Affectv +Copyright (c) 2020 Azerion Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 54ac78b..af4fae8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -# iab-tcf [![CircleCI](https://circleci.com/gh/affectv/iab-tcf.svg?style=svg)](https://circleci.com/gh/affectv/iab-tcf) +# iab-tcf [![CircleCI](https://circleci.com/gh/hybridtheory/iab-tcf.svg?style=svg)](https://circleci.com/gh/hybridtheory/iab-tcf) -Go code to parse IAB v1/v2 consent based on [github.com/LiveRamp/iabconsent](github.com/LiveRamp/iabconsent) library. +Go code to parse IAB v1/v2 consent based on [github.com/LiveRamp/iabconsent](github.com/LiveRamp/iabconsent) +library. This package is just a wrapper on their amazing work to fill our needs. @@ -9,7 +10,7 @@ This package is just a wrapper on their amazing work to fill our needs. ### Install ```bash -go get github.com/affectv/iab-tcf +go get github.com/hybridtheory/iab-tcf ``` ### Example @@ -19,7 +20,7 @@ package main import ( "fmt" - iab "github.com/affectv/iab-tcf" + iab "github.com/hybridtheory/iab-tcf" ) func main() { @@ -28,9 +29,46 @@ func main() { } ``` +### CMP vendor list + +In order to validate if the CMP received belongs to a valid id there's a loader integrated +with the library. + +```golang +package main + +import ( + "fmt" + iab "github.com/hybridtheory/iab-tcf" + cmp "github.com/hybridtheory/iab-tcf/cmp" +) + +func main() { + cmp.NewLoader().LoadIDs() + consent, err := iab.NewConsent(encoded) + consent.IsCMPValid() // This will return true/false depending on the ids loaded. +} +``` + +If instead of the default vendor list [https://cmplist.consensu.org/v2/cmp-list.json](https://cmplist.consensu.org/v2/cmp-list.json) +we want to use our own, we can use an option: + +```golang +err := cmp.NewLoader(cmp.WithURL("https://example.com/cmp-list.json")).LoadIDs() +``` + +The format of the JSON must be the same. + +If we want to use our own list of valid CMPs we can simply set the variable: + +```golang +cmp.ValidCMPs = []int{1, 123} +``` + ## Testing -We use [Ginkgo](https://onsi.github.io/ginkgo/) and [Gomega](https://onsi.github.io/gomega/) for testing purposes. +We use [Ginkgo](https://onsi.github.io/ginkgo/) and [Gomega](https://onsi.github.io/gomega/) +for testing purposes. ```bash ginkgo ./... diff --git a/benchmark_test.go b/benchmark_test.go index b9134a1..f9424ae 100644 --- a/benchmark_test.go +++ b/benchmark_test.go @@ -1,68 +1,60 @@ package iab_tcf import ( + "time" + "github.com/montanaflynn/stats" - . "github.com/onsi/ginkgo" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gmeasure" ) type data struct { - message string consent string result string } var _ = Describe("performance", func() { - - const ( - times = 1000 - ) - var ( - testResults = map[string][]int64{} - testData = []data{ - data{ - message: "v1 consent", - consent: "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02", - result: "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011", - }, - data{ - message: "v2 consent", - consent: "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA", - result: "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101", - }, + experiment *gmeasure.Experiment + times = gmeasure.SamplingConfig{N: 1000} + testConsentV1 = data{ + consent: "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02", + result: "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011", + } + testConsentV2 = data{ + consent: "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA", + result: "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101", } ) - AfterSuite(func() { - for testName, results := range testResults { - p99, _ := stats.Percentile(stats.LoadRawData(results), 99) - Expect(p99).Should(BeNumerically("<", 2000), testName+" shouldn't take too long.") - } + BeforeEach(func() { + experiment = gmeasure.NewExperiment("consent speed") }) - var run = func(consent string, result string) func(b Benchmarker) { - return func(b Benchmarker) { - runtime := b.Time("runtime", func() { + Context("custom parser", func() { + var run = func(consent string, expected string) func(_ int) { + return func(_ int) { output, err := NewConsent(consent) Expect(err).NotTo(HaveOccurred()) - Expect(output.GetConsentBitstring()).To(Equal(result)) - }) - testName := CurrentGinkgoTestDescription().TestText - testResults[testName] = append(testResults[testName], runtime.Microseconds()) - b.RecordValue("spent in microseconds", float64(runtime.Microseconds())) + Expect(output.GetConsentBitstring()).To(Equal(expected)) + } } - } - - Context("custom parser", func() { - var runCustom = func(consent string, result string) func(b Benchmarker) { - return run(consent, result) + var assert = func() { + measurements := experiment.Get("runtime") + p99, _ := stats.Percentile(stats.LoadRawData(measurements.Durations), 99) + Expect(p99).Should(BeNumerically("<", 2000*time.Millisecond), "it shouldn't take too long.") } - for _, data := range testData { - testResults[data.message] = []int64{} - Measure(data.message, runCustom(data.consent, data.result), times) - } + It("is fast with v1", func() { + experiment.SampleDuration("runtime", run(testConsentV1.consent, testConsentV1.result), times) + assert() + }) + + It("is fast with v2", func() { + experiment.SampleDuration("runtime", run(testConsentV2.consent, testConsentV2.result), times) + assert() + }) }) }) diff --git a/cmp/consent.go b/cmp/consent.go new file mode 100644 index 0000000..def61b5 --- /dev/null +++ b/cmp/consent.go @@ -0,0 +1,13 @@ +package cmp + +type Consent struct{} + +// ValidCMPs returns the list of valid CMPs loaded. +func (c *Consent) ValidCMPs() []int { + return ValidCMPs +} + +// IsCMPListLoaded returns if the list of valid CMPs was loaded or not. +func (c *Consent) IsCMPListLoaded() bool { + return c.ValidCMPs() != nil +} diff --git a/cmp/consent_test.go b/cmp/consent_test.go new file mode 100644 index 0000000..a67175e --- /dev/null +++ b/cmp/consent_test.go @@ -0,0 +1,40 @@ +package cmp_test + +import ( + "github.com/hybridtheory/iab-tcf/cmp" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Consent", func() { + var ( + testValidCMPs = []int{1, 2, 3} + consent *cmp.Consent + ) + + BeforeEach(func() { + consent = &cmp.Consent{} + }) + + It("returns valid cmps", func() { + cmp.ValidCMPs = testValidCMPs + Expect(consent.ValidCMPs()).To(Equal(testValidCMPs)) + }) + + Context("is list loaded", func() { + It("returns false if it is not loaded", func() { + cmp.ValidCMPs = nil + Expect(consent.IsCMPListLoaded()).To(BeFalse()) + }) + + It("returns true if it is loaded but empty", func() { + cmp.ValidCMPs = []int{} + Expect(consent.IsCMPListLoaded()).To(BeTrue()) + }) + + It("returns true if it is properly loaded", func() { + cmp.ValidCMPs = testValidCMPs + Expect(consent.IsCMPListLoaded()).To(BeTrue()) + }) + }) +}) diff --git a/cmp/loader.go b/cmp/loader.go new file mode 100644 index 0000000..bdcb4d8 --- /dev/null +++ b/cmp/loader.go @@ -0,0 +1,84 @@ +package cmp + +import ( + "encoding/json" + "net/http" + + "golang.org/x/exp/maps" +) + +const ( + DefaultCMPVendorList = "https://cmplist.consensu.org/v2/cmp-list.json" +) + +var ( + ValidCMPs []int +) + +// Option is the type that allows us to configure the Loader dynamically. +type Option func(loader *Loader) + +// Loader is the type that contains the logic to load and parse a CMP JSON list. +type Loader struct { + URL string +} + +// CMP contains the structure of the CMP info that comes inside the JSON +type CMP struct { + ID int + Name string + IsCommercial bool + Environments []string +} + +// WithURL allows to configure a different URL for the CMP JSON list. +func WithURL(url string) Option { + return func(cmp *Loader) { + cmp.URL = url + } +} + +// NewLoader returns a CMP vendor list loader instance. +func NewLoader(options ...Option) *Loader { + loader := &Loader{ + URL: DefaultCMPVendorList, + } + for _, option := range options { + option(loader) + } + return loader +} + +// Unmarshal parses the JSON vendor list into a struct so we can use them. +func (loader *Loader) Unmarshal(response *http.Response) ([]CMP, error) { + type Response struct { + CMPS map[string]CMP `json:"cmps"` + } + data := Response{} + if err := json.NewDecoder(response.Body).Decode(&data); err != nil { + return []CMP{}, err + } + return maps.Values(data.CMPS), nil +} + +// Load is used to load the vendor list into a list of CMP information. +func (loader *Loader) Load() ([]CMP, error) { + response, err := http.Get(loader.URL) + if err == nil { + return loader.Unmarshal(response) + } + return []CMP{}, err +} + +// LoadIDs loads the list of vendor CMP ids globally so we can reuse it +// with subsequent calls. +func (loader *Loader) LoadIDs() error { + cmps, err := loader.Load() + if err == nil { + ValidCMPs = []int{} + for _, cmp := range cmps { + ValidCMPs = append(ValidCMPs, cmp.ID) + } + } + return err +} diff --git a/cmp/loader_test.go b/cmp/loader_test.go new file mode 100644 index 0000000..f53e57f --- /dev/null +++ b/cmp/loader_test.go @@ -0,0 +1,116 @@ +package cmp_test + +import ( + "github.com/hybridtheory/iab-tcf/cmp" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" +) + +var _ = Describe("Loader", func() { + + var ( + loader *cmp.Loader + cmps []cmp.CMP + err error + ) + + BeforeEach(func() { + loader = cmp.NewLoader() + }) + + Describe("configuration", func() { + Context("with url", func() { + const ( + testURL = "https://unknown-url" + ) + + BeforeEach(func() { + loader = cmp.NewLoader(cmp.WithURL(testURL)) + }) + + It("is used to retrieve the JSON", func() { + _, err = loader.Load() + Expect(err).Should(MatchError(MatchRegexp("lookup unknown-url"))) + }) + }) + }) + + Describe("load", func() { + BeforeEach(func() { + cmps, err = loader.Load() + }) + + DescribeTable("available vendors", + func(cmpID int) { + Expect(cmps).To(ContainElement(MatchFields(IgnoreExtras, Fields{"ID": Equal(cmpID)}))) + }, + Entry("Microsoft Corporation", 198), + Entry("Google LLC", 300), + Entry("eBay Inc", 125), + ) + + DescribeTable("unavailable vendors", + func(cmpID int) { + Expect(cmps).ToNot(ContainElement(MatchFields(IgnoreExtras, Fields{"ID": Equal(cmpID)}))) + }, + Entry("unknown #1", 4), + Entry("unknown #2", 8), + ) + + Context("errors", func() { + It("unavailable endpoint", func() { + cmps, err = cmp.NewLoader(cmp.WithURL("https://unknown")).Load() + Expect(cmps).To(HaveLen(0)) + Expect(err).To(HaveOccurred()) + }) + + It("not a json", func() { + cmps, err = cmp.NewLoader(cmp.WithURL("http://github.com/")).Load() + Expect(cmps).To(HaveLen(0)) + Expect(err).To(HaveOccurred()) + }) + }) + }) + + Describe("load ids", func() { + BeforeEach(func() { + err = loader.LoadIDs() + }) + + DescribeTable("available vendors", + func(expected int) { + Expect(cmp.ValidCMPs).To(ContainElement(expected)) + }, + Entry("Microsoft Corporation", 198), + Entry("Google LLC", 300), + Entry("eBay Inc", 125), + ) + + DescribeTable("unavailable vendors", + func(unexpected int) { + Expect(cmp.ValidCMPs).ToNot(ContainElement(unexpected)) + }, + Entry("unknown #1", 4), + Entry("unknown #2", 8), + ) + + Context("errors", func() { + BeforeEach(func() { + cmp.ValidCMPs = nil + }) + + It("unavailable endpoint", func() { + err = cmp.NewLoader(cmp.WithURL("https://unknown")).LoadIDs() + Expect(cmps).To(HaveLen(0)) + Expect(err).To(HaveOccurred()) + }) + + It("not a json", func() { + err = cmp.NewLoader(cmp.WithURL("http://github.com/")).LoadIDs() + Expect(cmp.ValidCMPs).To(HaveLen(0)) + Expect(err).To(HaveOccurred()) + }) + }) + }) +}) diff --git a/cmp/suite_test.go b/cmp/suite_test.go new file mode 100644 index 0000000..63e847f --- /dev/null +++ b/cmp/suite_test.go @@ -0,0 +1,13 @@ +package cmp_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestHandlers(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Consent suite: CMP") +} diff --git a/consent_v1_test.go b/consent_v1_test.go index b626fce..18eeaae 100644 --- a/consent_v1_test.go +++ b/consent_v1_test.go @@ -1,33 +1,49 @@ -package iab_tcf +package iab_tcf_test import ( - . "github.com/onsi/ginkgo" + iab_tcf "github.com/hybridtheory/iab-tcf" + "github.com/hybridtheory/iab-tcf/cmp" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + . "github.com/onsi/gomega" - . "github.com/onsi/ginkgo/extensions/table" ) var _ = Describe("Consent TCF 1.0", func() { const ( - testGdprConsent = "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" + testGdprConsent = "BOlLbqtOlLbqtAVABADECg-AAAApp7v______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-3zd4u_1vf99yfm1-7etr3tp_87ues2_Xur__79__3z3_9phP78k89r7337Ew-v02" testBitstringConsentPurpose = "111110000000000000000000" - testBitstringConsent = "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011" + testBitstringConsent = "111101110111111111111111111111111111111111111111111110111111111111111111111111111111111111111110110111011001111111100111010111111111110111111111101111111111111111111011111011101111011110011111111111111110110111111111110010111111111101111111111111011111111111111111110111011111111111011011111001101110111100010111011111111010110111101111111110111110111001001111110011011010111111011101101111010110110101111011110110110100111111111110011101110111001111010110011011011111101011110111010101111111111111111101111110111111111111111011111001111011111111111110110100110000100111111101111110010010011110011110110101111101111011111011111101100010011000011111010111111010011011" ) var ( - consent Consent - err error + consent iab_tcf.Consent + err error ) BeforeEach(func() { - consent, err = NewConsent(testGdprConsent) - Expect(err).NotTo(HaveOccurred()) + consent, err = iab_tcf.NewConsent(testGdprConsent) + Expect(err).NotTo(HaveOccurred()) }) It("detects the version as V1", func() { Expect(consent.Version()).To(Equal(1)) }) + It("detects the cmp id as 21", func() { + Expect(consent.CMPID()).To(Equal(21)) + }) + + DescribeTable("cmp validity", + func(validCMPs []int, expected gomega.OmegaMatcher) { + cmp.ValidCMPs = validCMPs + Expect(consent.IsCMPValid()).To(expected) + }, + Entry("valid", []int{21}, BeTrue()), + Entry("invalid", []int{22}, BeFalse()), + ) + DescribeTable("purpose consent", func(purposeID int, expected bool) { Expect(consent.HasConsentedPurpose(purposeID)).To(Equal(expected)) diff --git a/consent_v2_test.go b/consent_v2_test.go index 60af447..ee6b1fa 100644 --- a/consent_v2_test.go +++ b/consent_v2_test.go @@ -1,34 +1,41 @@ -package iab_tcf +package iab_tcf_test import ( - . "github.com/onsi/ginkgo" + iab_tcf "github.com/hybridtheory/iab-tcf" + "github.com/hybridtheory/iab-tcf/cmp" + . "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + . "github.com/onsi/gomega" - . "github.com/onsi/ginkgo/extensions/table" ) var _ = Describe("Consent TCF 2.0", func() { const ( - testGdprConsent = "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA" - testBitstringConsent = "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101" - testBitstringConsentPurpose = "111111111100000000000000" + testGdprConsent = "COxR03kOxR1CqBcABCENAgCMAP_AAH_AAAqIF3EXySoGY2thI2YVFxBEIYwfJxyigMgChgQIsSwNQIeFLBoGLiAAHBGYJAQAGBAEEACBAQIkHGBMCQAAgAgBiRCMQEGMCzNIBIBAggEbY0FACCVmHkHSmZCY7064O__QLuIJEFQMAkSBAIACLECIQwAQDiAAAYAlAAABAhIaAAgIWBQEeAAAACAwAAgAAABBAAACAAQAAICIAAABAAAgAiAQAAAAGgIQAACBABACRIAAAEANCAAgiCEAQg4EAo4AAA" + testBitstringConsent = "010001011111001001001010100000011001100011011010110110000100100011011001100001010100010111000100000100010000100001100011000001111100100111000111001010001010000000110010000000001010000110000001000000100010110001001011000000110101000000100001111000010100101100000110100000011000101110001000000000000000011100000100011001100000100100000001000000000000011000000100000000010000010000000000001000000100000001000000100010010000011100011000000100110000001001000000000000000010000000000010000000000110001001000100001000110001000000010000011000110000001011001100110100100000000100100000000100000010000010000000010001101101100011010000010100000000001000001001010110011000011110010000011101001010011001100100001001100011101111010011101011100000111011111111111101" + testBitstringConsentPurpose = "111111111100000000000000" testBitstringInterestConsent = "010000010010001000001010100000011000000001001000100100000010000000010000000000000100010110001000000100010000100001100000000000100000000111000100000000000000000000110000000001001010000000000000000000000010000001000010010000110100000000000001000000010000101100000010100000001000111100000000000000000000000000000100000001100000000000000001000000000000000000000000000010000010000000000000000000000100000000000000100000000000000000010000000100010000000000000000000000000010000000000000000001000000000001000100000000100000000000000000000000000000001101000000010000100000000000000000000100000010000000000010000000000100100010010000000000000000000000001000000000011010000100000000000001000001000100000100001000000000100001000001110000001000000001010001110000" ) var ( - consent Consent - err error + consent iab_tcf.Consent + err error ) BeforeEach(func() { - consent, err = NewConsent(testGdprConsent) - Expect(err).NotTo(HaveOccurred()) + consent, err = iab_tcf.NewConsent(testGdprConsent) + Expect(err).NotTo(HaveOccurred()) }) It("detects the version as V2", func() { Expect(consent.Version()).To(Equal(2)) }) + It("detects the CMP ID as 21", func() { + Expect(consent.CMPID()).To(Equal(92)) + }) + DescribeTable("purposes consented", func(purposeID int, expected bool) { Expect(consent.HasConsentedPurpose(purposeID)).To(Equal(expected)) @@ -119,30 +126,42 @@ var _ = Describe("Consent TCF 2.0", func() { }) }) - var _ = Describe("Consent TCF 2.0 generated with https://iabtcf.com/#/encode", func() { const ( - testGdprConsent = "COytyllOytyllCrAAAENAiCMAFVAACqAAAAAF3QAgAFABkAAoioAAA.IF5EX2S5OI2tho2YdF7BEYYwfJxyigMgShgQIsS8NwIeFbBoGPmAAHBG4JAQAGBAkkACBAQIsHGBcCQABgIgRiRCMQEGMjzNKBJBAggkbI0FACCVmnkHS3ZCY70-6u__bA" - testBitstringConsent = "000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - testBitstringConsentPurpose = "010101010100000000000000" + testGdprConsent = "COytyllOytyllCrAAAENAiCMAFVAACqAAAAAF3QAgAFABkAAoioAAA.IF5EX2S5OI2tho2YdF7BEYYwfJxyigMgShgQIsS8NwIeFbBoGPmAAHBG4JAQAGBAkkACBAQIsHGBcCQABgIgRiRCMQEGMjzNKBJBAggkbI0FACCVmnkHS3ZCY70-6u__bA" + testBitstringConsent = "000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + testBitstringConsentPurpose = "010101010100000000000000" testBitstringInterestConsent = "0100010101" ) var ( - consent Consent - err error + consent iab_tcf.Consent + err error ) BeforeEach(func() { - consent, err = NewConsent(testGdprConsent) - Expect(err).NotTo(HaveOccurred()) + consent, err = iab_tcf.NewConsent(testGdprConsent) + Expect(err).NotTo(HaveOccurred()) }) It("detects the version as V2", func() { Expect(consent.Version()).To(Equal(2)) }) + It("detects the cmp id as 171", func() { + Expect(consent.CMPID()).To(Equal(171)) + }) + + DescribeTable("cmp validity", + func(validCMPs []int, expected gomega.OmegaMatcher) { + cmp.ValidCMPs = validCMPs + Expect(consent.IsCMPValid()).To(expected) + }, + Entry("valid", []int{171}, BeTrue()), + Entry("invalid", []int{172}, BeFalse()), + ) + DescribeTable("purposes consented", func(purposeID int, expected bool) { Expect(consent.HasConsentedPurpose(purposeID)).To(Equal(expected)) diff --git a/go.mod b/go.mod index d706cdc..d65089b 100644 --- a/go.mod +++ b/go.mod @@ -1,19 +1,27 @@ module github.com/hybridtheory/iab-tcf -go 1.14 +go 1.21 require ( - github.com/LiveRamp/iabconsent v0.2.1-0.20200330205436-de5fda813949 - github.com/go-check/check v0.0.0-20200227125254-8fa46927fb4f // indirect - github.com/golang/protobuf v1.4.0 // indirect + github.com/LiveRamp/iabconsent v0.5.3 github.com/montanaflynn/stats v0.6.3 + github.com/onsi/ginkgo/v2 v2.17.1 + github.com/onsi/gomega v1.33.0 + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f +) + +require ( + github.com/go-check/check v0.0.0-20200227125254-8fa46927fb4f // indirect + github.com/go-logr/logr v1.4.1 // indirect + github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/onsi/ginkgo v1.12.0 - github.com/onsi/gomega v1.9.0 github.com/pkg/errors v0.9.1 // indirect github.com/rupertchen/go-bits v0.2.0 // indirect - golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect - golang.org/x/sys v0.0.0-20200428200454-593003d681fa // indirect - golang.org/x/text v0.3.2 // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect + golang.org/x/net v0.24.0 // indirect + golang.org/x/sys v0.19.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.20.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ea5f3da..47ecae0 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,24 @@ -github.com/LiveRamp/iabconsent v0.2.0 h1:BSpuPTAjoqJ+D4xQ31WJh/ZFV8wYc2cAFF4QZT/dH7w= -github.com/LiveRamp/iabconsent v0.2.0/go.mod h1:DEYH+APsHV6I6KeViTEP3YRqePx7dE9ww52vCfV/osA= -github.com/LiveRamp/iabconsent v0.2.1-0.20200330205436-de5fda813949 h1:JxFGF5vB8BhBQqAcpJWkEaq1qfwGypuTK8vgL1/uZ8k= -github.com/LiveRamp/iabconsent v0.2.1-0.20200330205436-de5fda813949/go.mod h1:8ltEmb7vFGjtELdwolazqM4xIyH86eFdKtwLNlb05KU= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/LiveRamp/iabconsent v0.5.3 h1:QCU2IXGwRxha7D2jgTIeHLAnJ3H9IkuVzeQnJy/RcIo= +github.com/LiveRamp/iabconsent v0.5.3/go.mod h1:U1M4fHcbR0/PRP/Fu0qBnsm8NFYEqjHiYgrM70bxqp4= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-check/check v0.0.0-20161208181325-20d25e280405/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-check/check v0.0.0-20200227125254-8fa46927fb4f h1:tbYkvK0CuMDlW7nrz4u/f5WBxOcuBfHks7OGQjFyrPE= github.com/go-check/check v0.0.0-20200227125254-8fa46927fb4f/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= @@ -28,54 +27,36 @@ github.com/montanaflynn/stats v0.6.3 h1:F8446DrvIF5V5smZfZ8K9nrmmix0AFgevPdLruGO github.com/montanaflynn/stats v0.6.3/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= +github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE= +github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rupertchen/go-bits v0.2.0 h1:B5+B70H4vWgwMppvo3wiYtwgN1j9m2nD9DJnnMqGcbQ= github.com/rupertchen/go-bits v0.2.0/go.mod h1:V1n1fOC+mPsmLRcRQ5Esgi7CMdsPNeWNz4nVGm+DMJc= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 h1:Jcxah/M+oLZ/R4/z5RzfPzGbPXnVDPkEDtf2JnuxN+U= -golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e h1:N7DeIrjYszNmSW409R3frPPwglRwMkXSBzwVbkOjLLA= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200428200454-593003d681fa h1:yMbJOvnfYkO1dSAviTu/ZguZWLBTXx4xE3LYrxUCCiA= -golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 9146114..e289fa1 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,11 @@ package iab_tcf import ( - "github.com/LiveRamp/iabconsent" - "encoding/base64" - "strings" + "encoding/base64" "errors" + "strings" + + "github.com/LiveRamp/iabconsent" ) var booleanFormatter = map[bool]string{ @@ -17,20 +18,22 @@ var booleanFormatter = map[bool]string{ type Consent interface { // Version returns the version of this consent string. Version() int + // CMPID returns the CMP ID of this consent string. + CMPID() int // HasConsentedPurpose returns the consent value for a Purpose established on the legal basis of consent. // The Purposes are numerically identified and published in the Global Vendor List. HasConsentedPurpose(purposeID int) bool // GetConsentPurposeBitstring returns a string of 1 & 0 each of them representing the consent // given for a specific purposeID. - // The first number is for the purposeID 1, the second number for the purposeID 2, + // The first number is for the purposeID 1, the second number for the purposeID 2, // and so on. GetConsentPurposeBitstring() string - // HasConsentedLegitimateInterestForPurpose returns the Purpose’s transparency requirements - // are met for each Purpose on the legal basis of legitimate interest and the user has not - // exercised their “Right to Object” to that Purpose. + // HasConsentedLegitimateInterestForPurpose returns the Purpose’s transparency requirements + // are met for each Purpose on the legal basis of legitimate interest and the user has not + // exercised their “Right to Object” to that Purpose. HasConsentedLegitimateInterestForPurpose(purposeID int) bool // HasUserConsented returns true if the user has given consent to the vendorID passed - // as parameter. + // as parameter. HasUserConsented(vendorID int) bool // HasUserLegitimateInterest returns true if the CMP has established transparency for a vendor's // legitimate interest disclosures. If a user exercises their “Right To Object” to a vendor’s @@ -38,37 +41,41 @@ type Consent interface { HasUserLegitimateInterest(vendorID int) bool // GetConsentBitstring returns a string of 1 & 0 each of them representing the consent // given for a specific vendorID. - // The first number is for the vendorID 1, the second number for the vendorID 2, - // and so on. + // The first number is for the vendorID 1, the second number for the vendorID 2, + // and so on. GetConsentBitstring() string - // GetInterestsBitstring returns a string of 1 & 0 each of them representing the + // GetInterestsBitstring returns a string of 1 & 0 each of them representing the // return of `HasUserLegitimateInterest` method for the vendorID in that position of the string. - // The first number is for the vendorID 1, the second number for the vendorID 2, + // The first number is for the vendorID 1, the second number for the vendorID 2, // and so on. GetInterestsBitstring() string // GetPublisherRestrictions returns a list of restrictions per publisher, if it relates. - GetPublisherRestrictions() ([]*iabconsent.PubRestrictionEntry) + GetPublisherRestrictions() []*iabconsent.PubRestrictionEntry + // IsCMPListLoaded returns if the list of valid CMPs was properly loaded or not. + IsCMPListLoaded() bool + // IsCMPValid validates the consent string CMP ID agains the list of valid ones downloaded from IAB. + IsCMPValid() bool } // DecodeConsent receives a GDPR IAB consent string and decodes the // CORE segment only, returning it. It also returns an error if something // happened and we couldn't decode it. func DecodeConsent(consent string) ([]byte, error) { - segments := strings.Split(consent, ".") + segments := strings.Split(consent, ".") decoded, err := base64.RawURLEncoding.DecodeString(segments[0]) if err != nil { return nil, err - } - return decoded, nil + } + return decoded, nil } // GetVersion extracts the version from the consent string, moving // the data pointer so we don't have to reparse it. func GetVersion(r *iabconsent.ConsentReader) iabconsent.TCFVersion { - if version, err := r.ReadInt(6); err == nil { - return iabconsent.TCFVersion(version) - } - return iabconsent.InvalidTCFVersion + if version, err := r.ReadInt(6); err == nil { + return iabconsent.TCFVersion(version) + } + return iabconsent.InvalidTCFVersion } // NewConsent returns a Consent instance with all the necessary information diff --git a/parser_v1.go b/parser_v1.go index e72a9cc..f3556fc 100644 --- a/parser_v1.go +++ b/parser_v1.go @@ -1,12 +1,16 @@ package iab_tcf import ( + "slices" + "github.com/LiveRamp/iabconsent" + "github.com/hybridtheory/iab-tcf/cmp" ) // ConsentV1 is an implementation of the Consent interface used to retrieve // consent information given a TCF 1.0 format. type ConsentV1 struct { + *cmp.Consent ParsedConsent *iabconsent.ParsedConsent } @@ -27,6 +31,16 @@ func (c *ConsentV1) Version() int { return int(iabconsent.V1) } +// CMPID returns the CMP ID of this consent string. +func (c *ConsentV1) CMPID() int { + return c.ParsedConsent.CMPID +} + +// IsCMPValid validates the consent string CMP ID agains the list of valid ones downloaded from IAB. +func (c *ConsentV1) IsCMPValid() bool { + return slices.Contains(c.ValidCMPs(), c.CMPID()) +} + // HasConsentedPurpose returns always true because consent TFC 1.0 doesn't // come with this information. func (c *ConsentV1) HasConsentedPurpose(purposeID int) bool { @@ -79,7 +93,7 @@ func (c *ConsentV1) GetInterestsBitstring() string { // GetPublisherRestrictions returns an empty list because consent TFC 1.0 doesn't // implement user legitimate interests. -func (c *ConsentV1) GetPublisherRestrictions() ([]*iabconsent.PubRestrictionEntry) { +func (c *ConsentV1) GetPublisherRestrictions() []*iabconsent.PubRestrictionEntry { return make([]*iabconsent.PubRestrictionEntry, 0, 0) } @@ -88,7 +102,9 @@ func (c *ConsentV1) GetPublisherRestrictions() ([]*iabconsent.PubRestrictionEntr func ParseV1(r *iabconsent.ConsentReader) (*iabconsent.ParsedConsent, error) { var p = &iabconsent.ParsedConsent{} p.Version = int(iabconsent.V1) - r.ReadString(17) + r.ReadString(12) + p.CMPID, _ = r.ReadInt(12) + r.ReadString(3) p.ConsentLanguage, _ = r.ReadString(2) p.VendorListVersion, _ = r.ReadInt(12) p.PurposesAllowed, _ = r.ReadBitField(24) diff --git a/parser_v2.go b/parser_v2.go index 114b29e..ddfc2bb 100644 --- a/parser_v2.go +++ b/parser_v2.go @@ -1,12 +1,16 @@ package iab_tcf import ( + "slices" + "github.com/LiveRamp/iabconsent" + "github.com/hybridtheory/iab-tcf/cmp" ) // ConsentV2 is an implementation of the Consent interface used to retrieve // consent information given a TCF 1.0 format. type ConsentV2 struct { + cmp.Consent ParsedConsent *iabconsent.V2ParsedConsent } @@ -27,6 +31,16 @@ func (c *ConsentV2) Version() int { return int(iabconsent.V2) } +// CMPID returns the CMP ID of this consent string. +func (c *ConsentV2) CMPID() int { + return c.ParsedConsent.CMPID +} + +// IsCMPValid validates the consent string CMP ID agains the list of valid ones downloaded from IAB. +func (c *ConsentV2) IsCMPValid() bool { + return slices.Contains(c.ValidCMPs(), c.CMPID()) +} + // HasConsentedPurpose returns the consent value for a Purpose established on the legal basis of consent. // The Purposes are numerically identified and published in the Global Vendor List. func (c *ConsentV2) HasConsentedPurpose(purposeID int) bool { @@ -43,15 +57,15 @@ func (c *ConsentV2) GetConsentPurposeBitstring() string { return bitString } -// HasConsentedLegitimateInterestForPurpose returns the Purpose’s transparency requirements -// are met for each Purpose on the legal basis of legitimate interest and the user has not -// exercised their “Right to Object” to that Purpose. +// HasConsentedLegitimateInterestForPurpose returns the Purpose’s transparency requirements +// are met for each Purpose on the legal basis of legitimate interest and the user has not +// exercised their “Right to Object” to that Purpose. func (c *ConsentV2) HasConsentedLegitimateInterestForPurpose(purposeID int) bool { return c.ParsedConsent.PurposesLITransparency[purposeID] } // HasUserConsented returns true if the user has given consent to the vendorID passed -// as parameter. +// as parameter. func (c *ConsentV2) HasUserConsented(vendorID int) bool { if c.ParsedConsent.IsConsentRangeEncoding { for _, re := range c.ParsedConsent.ConsentedVendorsRange { @@ -100,7 +114,7 @@ func (c *ConsentV2) GetInterestsBitstring() string { } // GetPublisherRestrictions returns a list of restrictions per publisher, if it relates. -func (c *ConsentV2) GetPublisherRestrictions() ([]*iabconsent.PubRestrictionEntry) { +func (c *ConsentV2) GetPublisherRestrictions() []*iabconsent.PubRestrictionEntry { return c.ParsedConsent.PubRestrictionEntries } @@ -109,7 +123,9 @@ func (c *ConsentV2) GetPublisherRestrictions() ([]*iabconsent.PubRestrictionEntr func ParseV2(r *iabconsent.ConsentReader) (*iabconsent.V2ParsedConsent, error) { var p = &iabconsent.V2ParsedConsent{} p.Version = int(iabconsent.V2) - r.ReadString(17) + r.ReadString(12) + p.CMPID, _ = r.ReadInt(12) + r.ReadString(3) p.ConsentLanguage, _ = r.ReadString(2) p.VendorListVersion, _ = r.ReadInt(12) p.TCFPolicyVersion, _ = r.ReadInt(6) diff --git a/suite_test.go b/suite_test.go index 136ae4f..af53f6b 100644 --- a/suite_test.go +++ b/suite_test.go @@ -1,9 +1,10 @@ package iab_tcf_test import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) func TestHandlers(t *testing.T) {