diff --git a/gpp_parsed_consent.go b/gpp_parsed_consent.go index 7e810b9..0d741a9 100644 --- a/gpp_parsed_consent.go +++ b/gpp_parsed_consent.go @@ -106,7 +106,9 @@ func MapGppSectionToParser(s string) ([]GppSectionParser, error) { var gppSection GppSectionParser switch sid := gppHeader.Sections[i-1]; sid { case 7: - gppSection = NewMspaNationl(segments[i]) + gppSection = NewMspaNational(segments[i]) + case 9: + gppSection = NewMspaVA(segments[i]) default: // Skip if no matching struct, as Section ID is not supported yet. // Any newly supported Section IDs should be added as cases here. @@ -134,7 +136,7 @@ func ParseGppConsent(s string) (map[int]GppParsedConsent, error) { var consentErr error consent, consentErr = gpp.ParseConsent() if consentErr != nil { - // If an error, quietly do not add teh consent value to map. + // If an error, quietly do not add the consent value to map. } else { gppConsents[gpp.GetSectionId()] = consent } diff --git a/gpp_parsed_consent_fixture_test.go b/gpp_parsed_consent_fixture_test.go index f875781..1e4caf6 100644 --- a/gpp_parsed_consent_fixture_test.go +++ b/gpp_parsed_consent_fixture_test.go @@ -118,6 +118,95 @@ var gppParsedConsentFixtures = map[string]map[int]*iabconsent.MspaParsedConsent{ Gpc: true, }, }, + // Valid GPP w/ US Virgina MSPA, Subsection of GPC False. + "DBABRg~BVoYYYA": {9: { + Version: 1, + SharingNotice: iabconsent.NoticeProvided, + SaleOptOutNotice: iabconsent.NoticeProvided, + TargetedAdvertisingOptOutNotice: iabconsent.NoticeProvided, + SaleOptOut: iabconsent.NotOptedOut, + TargetedAdvertisingOptOut: iabconsent.NotOptedOut, + SensitiveDataProcessing: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.NoConsent, + 2: iabconsent.Consent, + 3: iabconsent.ConsentNotApplicable, + 4: iabconsent.NoConsent, + 5: iabconsent.Consent, + 6: iabconsent.ConsentNotApplicable, + 7: iabconsent.NoConsent, + }, + KnownChildSensitiveDataConsents: map[int]iabconsent.MspaConsent{ + 0: iabconsent.Consent, + }, + MspaCoveredTransaction: iabconsent.MspaNotApplicable, + MspaOptOutOptionMode: iabconsent.MspaNotApplicable, + MspaServiceProviderMode: iabconsent.MspaNotApplicable, + Gpc: false, + }, + }, + // Valid GPP w/ US US National and Virgina MSPA, Subsection of GPC False. + "DBACLMA~BVVqAAEABAA~BVoYYYA": {7: { + Version: 1, + SharingNotice: iabconsent.NoticeProvided, + SaleOptOutNotice: iabconsent.NoticeProvided, + SharingOptOutNotice: iabconsent.NoticeProvided, + TargetedAdvertisingOptOutNotice: iabconsent.NoticeProvided, + SensitiveDataProcessingOptOutNotice: iabconsent.NoticeProvided, + SensitiveDataLimitUseNotice: iabconsent.NoticeProvided, + SaleOptOut: iabconsent.NotOptedOut, + SharingOptOut: iabconsent.NotOptedOut, + TargetedAdvertisingOptOut: iabconsent.NotOptedOut, + SensitiveDataProcessing: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.ConsentNotApplicable, + 2: iabconsent.ConsentNotApplicable, + 3: iabconsent.ConsentNotApplicable, + 4: iabconsent.ConsentNotApplicable, + 5: iabconsent.ConsentNotApplicable, + 6: iabconsent.ConsentNotApplicable, + 7: iabconsent.NoConsent, + 8: iabconsent.ConsentNotApplicable, + 9: iabconsent.ConsentNotApplicable, + 10: iabconsent.ConsentNotApplicable, + 11: iabconsent.ConsentNotApplicable, + }, + KnownChildSensitiveDataConsents: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.ConsentNotApplicable, + }, + PersonalDataConsents: iabconsent.NoConsent, + MspaCoveredTransaction: iabconsent.MspaNotApplicable, + MspaOptOutOptionMode: iabconsent.MspaNotApplicable, + MspaServiceProviderMode: iabconsent.MspaNotApplicable, + Gpc: false, + }, + 9: { + Version: 1, + SharingNotice: iabconsent.NoticeProvided, + SaleOptOutNotice: iabconsent.NoticeProvided, + TargetedAdvertisingOptOutNotice: iabconsent.NoticeProvided, + SaleOptOut: iabconsent.NotOptedOut, + TargetedAdvertisingOptOut: iabconsent.NotOptedOut, + SensitiveDataProcessing: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.NoConsent, + 2: iabconsent.Consent, + 3: iabconsent.ConsentNotApplicable, + 4: iabconsent.NoConsent, + 5: iabconsent.Consent, + 6: iabconsent.ConsentNotApplicable, + 7: iabconsent.NoConsent, + }, + KnownChildSensitiveDataConsents: map[int]iabconsent.MspaConsent{ + 0: iabconsent.Consent, + }, + MspaCoveredTransaction: iabconsent.MspaNotApplicable, + MspaOptOutOptionMode: iabconsent.MspaNotApplicable, + MspaServiceProviderMode: iabconsent.MspaNotApplicable, + Gpc: false, + }, + }, // Valid GPP string w/ sections for EU TCF V2 and US Privacy // Since both are not supported, Consent fixture should be blank. "DBACNY~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN": {}, diff --git a/mspa_parsed_consent_fixture_test.go b/mspa_parsed_consent_fixture_test.go index 6ca348e..990f126 100644 --- a/mspa_parsed_consent_fixture_test.go +++ b/mspa_parsed_consent_fixture_test.go @@ -115,3 +115,58 @@ var usNationalConsentFixtures = map[string]*iabconsent.MspaParsedConsent{ Gpc: false, }, } + +var usVAConsentFixtures = map[string]*iabconsent.MspaParsedConsent{ + // With subsection of GPC True. + "BVoYYYA.YA": { + Version: 1, + SharingNotice: iabconsent.NoticeProvided, + SaleOptOutNotice: iabconsent.NoticeProvided, + TargetedAdvertisingOptOutNotice: iabconsent.NoticeProvided, + SaleOptOut: iabconsent.NotOptedOut, + TargetedAdvertisingOptOut: iabconsent.NotOptedOut, + SensitiveDataProcessing: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.NoConsent, + 2: iabconsent.Consent, + 3: iabconsent.ConsentNotApplicable, + 4: iabconsent.NoConsent, + 5: iabconsent.Consent, + 6: iabconsent.ConsentNotApplicable, + 7: iabconsent.NoConsent, + }, + KnownChildSensitiveDataConsents: map[int]iabconsent.MspaConsent{ + 0: iabconsent.Consent, + }, + MspaCoveredTransaction: iabconsent.MspaNotApplicable, + MspaOptOutOptionMode: iabconsent.MspaNotApplicable, + MspaServiceProviderMode: iabconsent.MspaNotApplicable, + Gpc: true, + }, + // Without subsection. + "BVoYYYA": { + Version: 1, + SharingNotice: iabconsent.NoticeProvided, + SaleOptOutNotice: iabconsent.NoticeProvided, + TargetedAdvertisingOptOutNotice: iabconsent.NoticeProvided, + SaleOptOut: iabconsent.NotOptedOut, + TargetedAdvertisingOptOut: iabconsent.NotOptedOut, + SensitiveDataProcessing: map[int]iabconsent.MspaConsent{ + 0: iabconsent.ConsentNotApplicable, + 1: iabconsent.NoConsent, + 2: iabconsent.Consent, + 3: iabconsent.ConsentNotApplicable, + 4: iabconsent.NoConsent, + 5: iabconsent.Consent, + 6: iabconsent.ConsentNotApplicable, + 7: iabconsent.NoConsent, + }, + KnownChildSensitiveDataConsents: map[int]iabconsent.MspaConsent{ + 0: iabconsent.Consent, + }, + MspaCoveredTransaction: iabconsent.MspaNotApplicable, + MspaOptOutOptionMode: iabconsent.MspaNotApplicable, + MspaServiceProviderMode: iabconsent.MspaNotApplicable, + Gpc: false, + }, +} diff --git a/mspa_parsed_consent_test.go b/mspa_parsed_consent_test.go index 94178eb..5d1a139 100644 --- a/mspa_parsed_consent_test.go +++ b/mspa_parsed_consent_test.go @@ -108,7 +108,7 @@ func (s *MspaSuite) TestParseUsNational(c *check.C) { for k, v := range usNationalConsentFixtures { c.Log(k) - var gppSection = iabconsent.NewMspaNationl(k) + var gppSection = iabconsent.NewMspaNational(k) var p, err = gppSection.ParseConsent() c.Check(err, check.IsNil) @@ -136,7 +136,47 @@ func (s *MspaSuite) TestParseUsNationalError(c *check.C) { for _, t := range tcs { c.Log(t.desc) - var gppSection = iabconsent.NewMspaNationl(t.usNatString) + var gppSection = iabconsent.NewMspaNational(t.usNatString) + var p, err = gppSection.ParseConsent() + + c.Check(p, check.IsNil) + c.Check(err, check.ErrorMatches, t.expected.Error()) + } +} + +func (s *MspaSuite) TestParseUsVA(c *check.C) { + for k, v := range usVAConsentFixtures { + c.Log(k) + + var gppSection = iabconsent.NewMspaVA(k) + var p, err = gppSection.ParseConsent() + + c.Check(err, check.IsNil) + c.Check(p, check.DeepEquals, v) + } +} + +func (s *MspaSuite) TestParseUsVAError(c *check.C) { + var tcs = []struct { + desc string + usVAString string + expected error + }{ + { + desc: "Wrong Version.", + usVAString: "DVoYYYA", + expected: errors.New("non-v1 string passed."), + }, + { + desc: "Bad Decoding.", + usVAString: "$%&*(", + expected: errors.New("parse usva consent string: illegal base64 data at input byte 0"), + }, + } + for _, t := range tcs { + c.Log(t.desc) + + var gppSection = iabconsent.NewMspaVA(t.usVAString) var p, err = gppSection.ParseConsent() c.Check(p, check.IsNil) diff --git a/mspa_sections.go b/mspa_sections.go index 38ab234..7634955 100644 --- a/mspa_sections.go +++ b/mspa_sections.go @@ -11,10 +11,18 @@ type MspaUsNational struct { GppSection } -func NewMspaNationl(section string) *MspaUsNational { +type MspaUsVA struct { + GppSection +} + +func NewMspaNational(section string) *MspaUsNational { return &MspaUsNational{GppSection{sectionId: 7, sectionValue: section}} } +func NewMspaVA(section string) *MspaUsVA { + return &MspaUsVA{GppSection{sectionId: 9, sectionValue: section}} +} + func (m *MspaUsNational) ParseConsent() (GppParsedConsent, error) { var segments = strings.Split(m.sectionValue, ".") @@ -61,3 +69,47 @@ func (m *MspaUsNational) ParseConsent() (GppParsedConsent, error) { return p, r.Err } + +func (m *MspaUsVA) ParseConsent() (GppParsedConsent, error) { + var segments = strings.Split(m.sectionValue, ".") + + var b, err = base64.RawURLEncoding.DecodeString(segments[0]) + if err != nil { + return nil, errors.Wrap(err, "parse usva consent string") + } + + var r = NewConsentReader(b) + + // This block of code directly describes the format of the payload. + // The spec for the consent string can be found here: + // https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/tree/main/Sections/US-States/VA + var p = &MspaParsedConsent{} + p.Version, _ = r.ReadInt(6) + + if p.Version != 1 { + return nil, errors.New("non-v1 string passed.") + } + + p.SharingNotice, _ = r.ReadMspaNotice() + p.SaleOptOutNotice, _ = r.ReadMspaNotice() + p.TargetedAdvertisingOptOutNotice, _ = r.ReadMspaNotice() + p.SaleOptOut, _ = r.ReadMspaOptOut() + p.TargetedAdvertisingOptOut, _ = r.ReadMspaOptOut() + // This has a shorter length than UsNational. + p.SensitiveDataProcessing, _ = r.ReadMspaBitfieldConsent(8) + // While an array in UsNational, we can just use an array of 1 for a single value. + p.KnownChildSensitiveDataConsents, _ = r.ReadMspaBitfieldConsent(1) + // 0 is not a valid value according to the docs for MspaCoveredTransaction. Instead of erroring, + // return the value of the string, and let downstream processing handle if the value is 0. + p.MspaCoveredTransaction, _ = r.ReadMspaNaYesNo() + p.MspaOptOutOptionMode, _ = r.ReadMspaNaYesNo() + p.MspaServiceProviderMode, _ = r.ReadMspaNaYesNo() + + if len(segments) > 1 { + var gppSubsectionConsent *GppSubSection + gppSubsectionConsent, _ = ParseGppSubSections(segments[1:]) + p.Gpc = gppSubsectionConsent.Gpc + } + + return p, r.Err +}