Skip to content

Commit

Permalink
[PX-2203] Parse Whole GPP Strings and Parsing functionality for MSPA …
Browse files Browse the repository at this point in the history
…US National + GPC Subsection (LiveRamp#24)

* add new mspa struct,  usnational parse + test fixtures + tests.

* total gpp header and string parsing, and tests.

* gpp subsection parsing + gpc specific subsection
  • Loading branch information
kylehalverson authored Feb 14, 2023
1 parent 6fd0b25 commit 91d8694
Show file tree
Hide file tree
Showing 7 changed files with 986 additions and 1 deletion.
148 changes: 148 additions & 0 deletions gpp_parsed_consent.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,53 @@ package iabconsent
import (
"encoding/base64"
"fmt"
"strings"

"github.com/pkg/errors"
)

// GppHeader is the first section of a GPP Consent String.
// See ParseGppHeader for in-depth format.
type GppHeader struct {
Type int
Version int
Sections []int
}

// GppParsedConsent is an empty interface since GPP will need to handle more consent structs
// than just the Multi-state Privacy Agreement structs.
type GppParsedConsent interface {
}

// GppSection contains the specific Section ID (important to match up correct parsing).
// and pre-parsed Section Value, including all subsections.
type GppSection struct {
sectionId int
sectionValue string
}

type GppSectionParser interface {
ParseConsent() (GppParsedConsent, error)
GetSectionId() int
}

// GetSectionId returns the Section ID for a given GppSection.
func (g *GppSection) GetSectionId() int {
return g.sectionId
}

type GppSubSection struct {
// Global Privacy Control (GPC) is signaled and set.
Gpc bool
}

type GppSubSectionTypes int

const (
SubSectCore GppSubSectionTypes = iota
SubSectGpc
)

// ParseGppHeader parses the first (and required) part of any GPP Consent String.
// It is used to read the Type, Version, and which sections are contained in the following string(s).
// Format is:
Expand Down Expand Up @@ -42,3 +79,114 @@ func ParseGppHeader(s string) (*GppHeader, error) {
g.Sections, _ = r.ReadFibonacciRange()
return g, r.Err
}

// MapGppSectionToParser takes a base64 Raw URL Encoded string which represents a GPP v1 string
// of the format {gpp header}~{section 1}[.{sub-section}][~{section n}]
// and returns each pair of section value and parsing function that should be used.
// The pairs are returned to allow more control over how parsing functions are applied.
func MapGppSectionToParser(s string) ([]GppSectionParser, error) {
var gppHeader *GppHeader
var err error
// ~ separated fields. with the format {gpp header}~{section 1}[.{sub-section}][~{section n}]
var segments = strings.Split(s, "~")
if len(segments) < 2 {
return nil, errors.New("not enough gpp segments")
}

gppHeader, err = ParseGppHeader(segments[0])
if err != nil {
return nil, errors.Wrap(err, "read gpp header")
} else if len(segments[1:]) != len(gppHeader.Sections) {
// Return early if sections in header do not match sections passed.
return nil, errors.New("mismatch number of sections")
}
// Go through each section and add parsing function and section value to returned value.
var gppSections = make([]GppSectionParser, 0)
for i := 1; i < len(segments); i++ {
var gppSection GppSectionParser
switch sid := gppHeader.Sections[i-1]; sid {
case 7:
gppSection = NewMspaNationl(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.
}
if gppSection != nil {
gppSections = append(gppSections, gppSection)
}
}
return gppSections, nil
}

// ParseGppConsent takes a base64 Raw URL Encoded string which represents a GPP v1 string and
// returns a map of Section ID to ParsedConsents with consent parsed via a consecutive parsing.
func ParseGppConsent(s string) (map[int]GppParsedConsent, error) {
var gppSections []GppSectionParser
var err error
gppSections, err = MapGppSectionToParser(s)
if err != nil {
return nil, err
}
var gppConsents = make(map[int]GppParsedConsent, len(gppSections))
// Consecutively, go through each section and try to parse.
for _, gpp := range gppSections {
var consent GppParsedConsent
var consentErr error
consent, consentErr = gpp.ParseConsent()
if consentErr != nil {
// If an error, quietly do not add teh consent value to map.
} else {
gppConsents[gpp.GetSectionId()] = consent
}
}
return gppConsents, nil
}

// ParseGppSubSections parses the subsections that may be appended to GPP sections after a `.`
// Currently, GPC is the only subsection, so we only have a single Subsection parsing function.
// In the future, Section IDs may need their own SubSection parser.
func ParseGppSubSections(subSections []string) (*GppSubSection, error) {
var gppSub = new(GppSubSection)
// There could be >1 subsection, but we will only return a single GppSubSection result.
for _, s := range subSections {
// Actual base64 encoded data, so no need to add extra `0`s.
var b, err = base64.RawURLEncoding.DecodeString(s)
if err != nil {
return nil, errors.Wrap(err, "parse gpp subsection string")
}
var r = NewConsentReader(b)

var subType int
subType, err = r.ReadInt(2)
if err != nil {
return nil, errors.Wrap(err, "parse gpp subsection type")
}
// Check for specific SubSection Type, and then parse subsection correctly.
switch GppSubSectionTypes(subType) {
case SubSectGpc:
var gppValue bool
gppValue, err = ParseGpcSubsection(r)
if err != nil {
return nil, errors.Wrap(err, "parse gpp subsection gpc bool")
}
// Only override if not set to true already, as we want the most restrictive value
// if > 1 GPC subsection.
if gppSub.Gpc != true {
gppSub.Gpc = gppValue
}
}
}
return gppSub, nil
}

// ParseGpcSubsection reads the next bit as a bool, and returns the result of value.
// Info about GPC subsection here: https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/606b99efc16b649c5c1f8f1d2eb0d0d3258c4a2d/Sections/US-National/IAB%20Privacy%E2%80%99s%20National%20Privacy%20Technical%20Specification.md#gpc-sub-section
func ParseGpcSubsection(r *ConsentReader) (bool, error) {
var gppValue bool
var err error
gppValue, err = r.ReadBool()
if err != nil {
return false, errors.Wrap(err, "parse gpp subsection gpc bool")
}
return gppValue, err
}
160 changes: 160 additions & 0 deletions gpp_parsed_consent_fixture_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package iabconsent_test

import (
"github.com/LiveRamp/iabconsent"
)

// Test fixtures can be created here: https://iabgpp.com/

var gppParsedConsentFixtures = map[string]map[int]*iabconsent.MspaParsedConsent{
// Valid GPP w/ US National MSPA, No Subsection.
"DBABL~BVVqAAEABAA": {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,
},
},
// Valid GPP w/ US National MSPA, Subsection of GPC False.
"DBABL~BVVqAAEABAA.QA": {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,
},
},
// Valid GPP w/ US National MSPA, Subsection of GPC True.
"DBABL~BVVqAAEABAA.YA": {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: true,
},
},
// 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": {},
// Valid GPP w/ US National MSPA and US Privacy, but skip US Privacy until supported.
"DBABzw~1YNN~BVVqAAEABAA.QA": {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,
},
},
}
Loading

0 comments on commit 91d8694

Please sign in to comment.