Skip to content

Commit

Permalink
Refactor GPP functions (#2756)
Browse files Browse the repository at this point in the history
* First commit

* Remove white space

* added test case, cleaned IsSIDInList and IndexOfSID a little

* Brian's review

* Brian's second review

* StrToInt8Slice now throws error
  • Loading branch information
guscarreon committed May 18, 2023
1 parent 324d293 commit 38870ad
Show file tree
Hide file tree
Showing 10 changed files with 441 additions and 46 deletions.
2 changes: 1 addition & 1 deletion endpoints/cookie_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
if request.GDPR != nil {
gdprString = strconv.Itoa(*request.GDPR)
}
gdprSignal, err := gdpr.SignalParse(gdprString)
gdprSignal, err := gdpr.StrSignalParse(gdprString)
if err != nil {
return usersync.Request{}, privacy.Policies{}, err
}
Expand Down
15 changes: 6 additions & 9 deletions exchange/gdpr.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ import (
gppConstants "github.com/prebid/go-gpp/constants"
"github.com/prebid/prebid-server/gdpr"
"github.com/prebid/prebid-server/openrtb_ext"
gppPolicy "github.com/prebid/prebid-server/privacy/gpp"
)

// getGDPR will pull the gdpr flag from an openrtb request
func getGDPR(req *openrtb_ext.RequestWrapper) (gdpr.Signal, error) {
if req.Regs != nil && len(req.Regs.GPPSID) > 0 {
for _, id := range req.Regs.GPPSID {
if id == int8(gppConstants.SectionTCFEU2) {
return gdpr.SignalYes, nil
}
if gppPolicy.IsSIDInList(req.Regs.GPPSID, gppConstants.SectionTCFEU2) {
return gdpr.SignalYes, nil
}
return gdpr.SignalNo, nil
}
Expand All @@ -26,11 +25,9 @@ func getGDPR(req *openrtb_ext.RequestWrapper) (gdpr.Signal, error) {

// getConsent will pull the consent string from an openrtb request
func getConsent(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (consent string, err error) {
for i, id := range gpp.SectionTypes {
if id == gppConstants.SectionTCFEU2 {
consent = gpp.Sections[i].GetValue()
return
}
if i := gppPolicy.IndexOfSID(gpp, gppConstants.SectionTCFEU2); i >= 0 {
consent = gpp.Sections[i].GetValue()
return
}
ue, err := req.GetUserExt()
if ue == nil || ue.GetConsent() == nil || err != nil {
Expand Down
19 changes: 14 additions & 5 deletions gdpr/signal.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,24 @@ const (

var gdprSignalError = &errortypes.BadInput{Message: "GDPR signal should be integer 0 or 1"}

// SignalParse returns a parsed GDPR signal or a parse error.
func SignalParse(rawSignal string) (Signal, error) {
if rawSignal == "" {
// StrSignalParse returns a parsed GDPR signal or a parse error.
func StrSignalParse(signal string) (Signal, error) {
if signal == "" {
return SignalAmbiguous, nil
}

i, err := strconv.Atoi(rawSignal)
i, err := strconv.Atoi(signal)

if err != nil || (i != 0 && i != 1) {
if err != nil {
return SignalAmbiguous, gdprSignalError
}

return IntSignalParse(i)
}

// IntSignalParse checks parameter i is not out of bounds and returns a GDPR signal error
func IntSignalParse(i int) (Signal, error) {
if i != 0 && i != 1 {
return SignalAmbiguous, gdprSignalError
}

Expand Down
67 changes: 60 additions & 7 deletions gdpr/signal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,71 @@ func TestSignalParse(t *testing.T) {
wantSignal: SignalAmbiguous,
wantError: true,
},
{
description: "Out of bounds signal - raw signal is 5",
rawSignal: "5",
wantSignal: SignalAmbiguous,
wantError: true,
},
}

for _, test := range tests {
signal, err := SignalParse(test.rawSignal)
t.Run(test.description, func(t *testing.T) {
signal, err := StrSignalParse(test.rawSignal)

assert.Equal(t, test.wantSignal, signal, test.description)

assert.Equal(t, test.wantSignal, signal, test.description)
if test.wantError {
assert.NotNil(t, err, test.description)
} else {
assert.Nil(t, err, test.description)
}
})
}
}

func TestIntSignalParse(t *testing.T) {
type testOutput struct {
signal Signal
err error
}
testCases := []struct {
desc string
input int
expected testOutput
}{
{
desc: "input out of bounds, return SgnalAmbituous and gdprSignalError",
input: -1,
expected: testOutput{
signal: SignalAmbiguous,
err: gdprSignalError,
},
},
{
desc: "input in bounds equals signalNo, return signalNo and nil error",
input: 0,
expected: testOutput{
signal: SignalNo,
err: nil,
},
},
{
desc: "input in bounds equals signalYes, return signalYes and nil error",
input: 1,
expected: testOutput{
signal: SignalYes,
err: nil,
},
},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
outSignal, outErr := IntSignalParse(tc.input)

if test.wantError {
assert.NotNil(t, err, test.description)
} else {
assert.Nil(t, err, test.description)
}
assert.Equal(t, tc.expected.signal, outSignal, tc.desc)
assert.Equal(t, tc.expected.err, outErr, tc.desc)
})
}
}

Expand Down
57 changes: 37 additions & 20 deletions privacy/ccpa/policy.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package ccpa

import (
"errors"
"fmt"

gpplib "github.com/prebid/go-gpp"
gppConstants "github.com/prebid/go-gpp/constants"
"github.com/prebid/openrtb/v19/openrtb2"
"github.com/prebid/prebid-server/errortypes"
"github.com/prebid/prebid-server/openrtb_ext"
gppPolicy "github.com/prebid/prebid-server/privacy/gpp"
)

// Policy represents the CCPA regulatory information from an OpenRTB bid request.
Expand All @@ -18,33 +20,25 @@ type Policy struct {

// ReadFromRequestWrapper extracts the CCPA regulatory information from an OpenRTB bid request.
func ReadFromRequestWrapper(req *openrtb_ext.RequestWrapper, gpp gpplib.GppContainer) (Policy, error) {
var consent string
var noSaleBidders []string
var warn error = nil
var gppSIDs []int8
var requestUSPrivacy string
var warn error

if req == nil || req.BidRequest == nil {
return Policy{}, nil
}

if req.BidRequest.Regs != nil {
for _, s := range req.BidRequest.Regs.GPPSID {
if s == int8(gppConstants.SectionUSPV1) {
for i, id := range gpp.SectionTypes {
if id == gppConstants.SectionUSPV1 {
consent = gpp.Sections[i].GetValue()
}
}
}
}
if req.BidRequest.Regs.USPrivacy != "" {
if consent == "" {
consent = req.BidRequest.Regs.USPrivacy
} else if consent != req.BidRequest.Regs.USPrivacy {
warn = &errortypes.Warning{
Message: "regs.us_privacy consent does not match uspv1 in GPP, using regs.gpp",
WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
}
}
requestUSPrivacy = req.BidRequest.Regs.USPrivacy
gppSIDs = req.BidRequest.Regs.GPPSID
}

consent, err := SelectCCPAConsent(requestUSPrivacy, gpp, gppSIDs)
if err != nil {
warn = &errortypes.Warning{
Message: "regs.us_privacy consent does not match uspv1 in GPP, using regs.gpp",
WarningCode: errortypes.InvalidPrivacyConsentWarningCode}
}

if consent == "" {
Expand Down Expand Up @@ -100,6 +94,29 @@ func (p Policy) Write(req *openrtb_ext.RequestWrapper) error {
return nil
}

func SelectCCPAConsent(requestUSPrivacy string, gpp gpplib.GppContainer, gppSIDs []int8) (string, error) {
var consent string
var err error

if len(gpp.SectionTypes) > 0 {
if gppPolicy.IsSIDInList(gppSIDs, gppConstants.SectionUSPV1) {
if i := gppPolicy.IndexOfSID(gpp, gppConstants.SectionUSPV1); i >= 0 {
consent = gpp.Sections[i].GetValue()
}
}
}

if requestUSPrivacy != "" {
if consent == "" {
consent = requestUSPrivacy
} else if consent != requestUSPrivacy {
err = errors.New("request.us_privacy consent does not match uspv1")
}
}

return consent, err
}

func setPrebidNoSale(noSaleBidders []string, ext *openrtb_ext.RequestExt) {
if len(noSaleBidders) == 0 {
setPrebidNoSaleClear(ext)
Expand Down
90 changes: 86 additions & 4 deletions privacy/ccpa/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ccpa

import (
"encoding/json"
"errors"
"testing"

gpplib "github.com/prebid/go-gpp"
Expand Down Expand Up @@ -165,7 +166,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
},
giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}},
expectedPolicy: Policy{
Consent: "present",
Consent: "gppContainerConsent",
NoSaleBidders: []string{"a", "b"},
},
},
Expand All @@ -179,7 +180,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
},
giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}},
expectedPolicy: Policy{
Consent: "present",
Consent: "gppContainerConsent",
NoSaleBidders: []string{"a", "b"},
},
},
Expand All @@ -193,7 +194,7 @@ func TestReadFromRequestWrapper(t *testing.T) {
},
giveGPP: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{6}, Sections: []gpplib.Section{&upsv1Section}},
expectedPolicy: Policy{
Consent: "present",
Consent: "gppContainerConsent",
NoSaleBidders: []string{"a", "b"},
},
expectedError: true,
Expand Down Expand Up @@ -816,6 +817,87 @@ func TestBuildExtWrite(t *testing.T) {
}
}

func TestSelectCCPAConsent(t *testing.T) {
type testInput struct {
requestUSPrivacy string
gpp gpplib.GppContainer
gppSIDs []int8
}
testCases := []struct {
desc string
in testInput
expectedCCPA string
expectedErr error
}{
{
desc: "SectionUSPV1 in both GPP_SID and GPP container. Consent equal to request US_Privacy. Expect valid string and nil error",
in: testInput{
requestUSPrivacy: "gppContainerConsent",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{gppConstants.SectionUSPV1}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(6)},
},
expectedCCPA: "gppContainerConsent",
expectedErr: nil,
},
{
desc: "No SectionUSPV1 in GPP_SID array expect request US_Privacy",
in: testInput{
requestUSPrivacy: "requestConsent",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{gppConstants.SectionUSPV1}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(2), int8(4)},
},
expectedCCPA: "requestConsent",
expectedErr: nil,
},
{
desc: "No SectionUSPV1 in gpp.SectionTypes array expect request US_Privacy",
in: testInput{
requestUSPrivacy: "requestConsent",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(6)},
},
expectedCCPA: "requestConsent",
expectedErr: nil,
},
{
desc: "No SectionUSPV1 in GPP_SID array, blank request US_Privacy, expect blank consent",
in: testInput{
requestUSPrivacy: "",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{gppConstants.SectionUSPV1}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(2), int8(4)},
},
expectedCCPA: "",
expectedErr: nil,
},
{
desc: "No SectionUSPV1 in gpp.SectionTypes array, blank request US_Privacy, expect blank consent",
in: testInput{
requestUSPrivacy: "",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(6)},
},
expectedCCPA: "",
expectedErr: nil,
},
{
desc: "SectionUSPV1 in both GPP_SID and GPP container. Consent equal to request US_Privacy. Expect valid string and nil error",
in: testInput{
requestUSPrivacy: "requestConsent",
gpp: gpplib.GppContainer{Version: 1, SectionTypes: []gppConstants.SectionID{gppConstants.SectionUSPV1}, Sections: []gpplib.Section{upsv1Section}},
gppSIDs: []int8{int8(6)},
},
expectedCCPA: "gppContainerConsent",
expectedErr: errors.New("request.us_privacy consent does not match uspv1"),
},
}
for _, tc := range testCases {
out, outErr := SelectCCPAConsent(tc.in.requestUSPrivacy, tc.in.gpp, tc.in.gppSIDs)

assert.Equal(t, tc.expectedCCPA, out, tc.desc)
assert.Equal(t, tc.expectedErr, outErr, tc.desc)
}
}

func assertError(t *testing.T, expectError bool, err error, description string) {
t.Helper()
if expectError {
Expand All @@ -825,7 +907,7 @@ func assertError(t *testing.T, expectError bool, err error, description string)
}
}

var upsv1Section mockGPPSection = mockGPPSection{sectionID: 6, value: "present"}
var upsv1Section mockGPPSection = mockGPPSection{sectionID: 6, value: "gppContainerConsent"}
var tcf1Section mockGPPSection = mockGPPSection{sectionID: 2, value: "BOS2bx5OS2bx5ABABBAAABoAAAAAFA"}

type mockGPPSection struct {
Expand Down
Loading

0 comments on commit 38870ad

Please sign in to comment.