Skip to content

Commit

Permalink
GDPR: Basic enforcement (#2374)
Browse files Browse the repository at this point in the history
  • Loading branch information
bsardo authored Nov 9, 2022
1 parent eb84f30 commit 98b7bb4
Show file tree
Hide file tree
Showing 20 changed files with 2,588 additions and 530 deletions.
18 changes: 16 additions & 2 deletions account/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r
return account, nil
}

// TCF2Enforcements maps enforcement algo string values to their integer representation and is
// used to limit string compares
var TCF2Enforcements = map[string]config.TCF2EnforcementAlgo{
config.TCF2EnforceAlgoBasic: config.TCF2BasicEnforcement,
config.TCF2EnforceAlgoFull: config.TCF2FullEnforcement,
}

// setDerivedConfig modifies an account object by setting fields derived from other fields set in the account configuration
func setDerivedConfig(account *config.Account) {
account.GDPR.PurposeConfigs = map[consentconstants.Purpose]*config.AccountGDPRPurpose{
Expand All @@ -107,9 +114,16 @@ func setDerivedConfig(account *config.Account) {
10: &account.GDPR.Purpose10,
}

// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders
// located in the VendorExceptions field of the GDPR.PurposeX struct
for _, pc := range account.GDPR.PurposeConfigs {
// To minimize the number of string compares per request, we set the integer representation
// of the enforcement algorithm on each purpose config
pc.EnforceAlgoID = config.TCF2UndefinedEnforcement
if algo, exists := TCF2Enforcements[pc.EnforceAlgo]; exists {
pc.EnforceAlgoID = algo
}

// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders
// located in the VendorExceptions field of the GDPR.PurposeX struct
if pc.VendorExceptions == nil {
continue
}
Expand Down
15 changes: 15 additions & 0 deletions account/account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ func TestSetDerivedConfig(t *testing.T) {
purpose1VendorExceptions []openrtb_ext.BidderName
feature1VendorExceptions []openrtb_ext.BidderName
basicEnforcementVendors []string
enforceAlgo string
wantEnforceAlgoID config.TCF2EnforcementAlgo
}{
{
description: "Nil purpose 1 vendor exceptions",
Expand Down Expand Up @@ -158,13 +160,24 @@ func TestSetDerivedConfig(t *testing.T) {
description: "Multiple basic enforcement vendors",
basicEnforcementVendors: []string{"appnexus", "rubicon"},
},
{
description: "Basic Enforcement algorithm",
enforceAlgo: config.TCF2EnforceAlgoBasic,
wantEnforceAlgoID: config.TCF2BasicEnforcement,
},
{
description: "Full Enforcement algorithm",
enforceAlgo: config.TCF2EnforceAlgoFull,
wantEnforceAlgoID: config.TCF2FullEnforcement,
},
}

for _, tt := range tests {
account := config.Account{
GDPR: config.AccountGDPR{
Purpose1: config.AccountGDPRPurpose{
VendorExceptions: tt.purpose1VendorExceptions,
EnforceAlgo: tt.enforceAlgo,
},
SpecialFeature1: config.AccountGDPRSpecialFeature{
VendorExceptions: tt.feature1VendorExceptions,
Expand Down Expand Up @@ -193,6 +206,8 @@ func TestSetDerivedConfig(t *testing.T) {
assert.ElementsMatch(t, purpose1ExceptionMapKeys, tt.purpose1VendorExceptions, tt.description)
assert.ElementsMatch(t, feature1ExceptionMapKeys, tt.feature1VendorExceptions, tt.description)
assert.ElementsMatch(t, basicEnforcementMapKeys, tt.basicEnforcementVendors, tt.description)

assert.Equal(t, account.GDPR.Purpose1.EnforceAlgoID, tt.wantEnforceAlgoID, tt.description)
}
}

Expand Down
47 changes: 23 additions & 24 deletions config/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,6 @@ type AccountGDPR struct {
SpecialFeature1 AccountGDPRSpecialFeature `mapstructure:"special_feature1" json:"special_feature1"`
}

// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor which indicates whether
// weak vendor enforcement applies to that bidder.
func (a *AccountGDPR) BasicEnforcementVendor(bidder openrtb_ext.BidderName) (value, exists bool) {
if a.BasicEnforcementVendorsMap == nil {
return false, false
}
_, found := a.BasicEnforcementVendorsMap[string(bidder)]

return found, true
}

// EnabledForChannelType indicates whether GDPR is turned on at the account level for the specified channel type
// by using the channel type setting if defined or the general GDPR setting if defined; otherwise it returns nil.
func (a *AccountGDPR) EnabledForChannelType(channelType ChannelType) *bool {
Expand Down Expand Up @@ -135,6 +124,17 @@ func (a *AccountGDPR) PurposeEnforced(purpose consentconstants.Purpose) (value,
return *a.PurposeConfigs[purpose].EnforcePurpose, true
}

// PurposeEnforcementAlgo checks the purpose enforcement algo for a given purpose by first
// looking at the account settings, and if not set there, defaulting to the host configuration.
func (a *AccountGDPR) PurposeEnforcementAlgo(purpose consentconstants.Purpose) (value TCF2EnforcementAlgo, exists bool) {
c, exists := a.PurposeConfigs[purpose]

if exists && (c.EnforceAlgoID == TCF2BasicEnforcement || c.EnforceAlgoID == TCF2FullEnforcement) {
return c.EnforceAlgoID, true
}
return TCF2UndefinedEnforcement, false
}

// PurposeEnforcingVendors gets the account level enforce vendors setting for a given purpose returning the value and
// whether or not it is set. If not set, a default value of true is returned matching host default behavior.
func (a *AccountGDPR) PurposeEnforcingVendors(purpose consentconstants.Purpose) (value, exists bool) {
Expand All @@ -147,17 +147,14 @@ func (a *AccountGDPR) PurposeEnforcingVendors(purpose consentconstants.Purpose)
return *a.PurposeConfigs[purpose].EnforceVendors, true
}

// PurposeVendorException checks if the given bidder is a vendor exception for a given purpose.
func (a *AccountGDPR) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (value, exists bool) {
if a.PurposeConfigs[purpose] == nil {
return false, false
}
if a.PurposeConfigs[purpose].VendorExceptionMap == nil {
return false, false
}
_, found := a.PurposeConfigs[purpose].VendorExceptionMap[bidder]
// PurposeVendorExceptions returns the vendor exception map for a given purpose.
func (a *AccountGDPR) PurposeVendorExceptions(purpose consentconstants.Purpose) (value map[openrtb_ext.BidderName]struct{}, exists bool) {
c, exists := a.PurposeConfigs[purpose]

return found, true
if exists && c.VendorExceptionMap != nil {
return c.VendorExceptionMap, true
}
return nil, false
}

// PurposeOneTreatmentEnabled gets the account level purpose one treatment enabled setting returning the value and
Expand All @@ -180,9 +177,11 @@ func (a *AccountGDPR) PurposeOneTreatmentAccessAllowed() (value, exists bool) {

// AccountGDPRPurpose represents account-specific GDPR purpose configuration
type AccountGDPRPurpose struct {
EnforceAlgo string `mapstructure:"enforce_algo" json:"enforce_algo,omitempty"`
EnforcePurpose *bool `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"`
EnforceVendors *bool `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"`
EnforceAlgo string `mapstructure:"enforce_algo" json:"enforce_algo,omitempty"`
// Integer representation of enforcement algo for performance improvement on compares
EnforceAlgoID TCF2EnforcementAlgo
EnforcePurpose *bool `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"`
EnforceVendors *bool `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"`
// Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed
VendorExceptions []openrtb_ext.BidderName `mapstructure:"vendor_exceptions" json:"vendor_exceptions"`
VendorExceptionMap map[openrtb_ext.BidderName]struct{}
Expand Down
195 changes: 93 additions & 102 deletions config/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,74 @@ func TestPurposeEnforced(t *testing.T) {
}
}

func TestPurposeEnforcementAlgo(t *testing.T) {
tests := []struct {
description string
givePurposeConfigNil bool
givePurpose1Algo TCF2EnforcementAlgo
givePurpose2Algo TCF2EnforcementAlgo
givePurpose consentconstants.Purpose
wantAlgo TCF2EnforcementAlgo
wantAlgoSet bool
}{
{
description: "Purpose config is nil",
givePurposeConfigNil: true,
givePurpose: 1,
wantAlgo: TCF2UndefinedEnforcement,
wantAlgoSet: false,
},
{
description: "Purpose 1 enforcement algo is undefined",
givePurpose1Algo: TCF2UndefinedEnforcement,
givePurpose: 1,
wantAlgo: TCF2UndefinedEnforcement,
wantAlgoSet: false,
},
{
description: "Purpose 1 enforcement algo set to basic",
givePurpose1Algo: TCF2BasicEnforcement,
givePurpose: 1,
wantAlgo: TCF2BasicEnforcement,
wantAlgoSet: true,
},
{
description: "Purpose 1 enforcement algo set to full",
givePurpose1Algo: TCF2FullEnforcement,
givePurpose: 1,
wantAlgo: TCF2FullEnforcement,
wantAlgoSet: true,
},
{
description: "Purpose 2 Enforcement algo set to basic",
givePurpose2Algo: TCF2BasicEnforcement,
givePurpose: 2,
wantAlgo: TCF2BasicEnforcement,
wantAlgoSet: true,
},
}

for _, tt := range tests {
accountGDPR := AccountGDPR{}

if !tt.givePurposeConfigNil {
accountGDPR.PurposeConfigs = map[consentconstants.Purpose]*AccountGDPRPurpose{
1: {
EnforceAlgoID: tt.givePurpose1Algo,
},
2: {
EnforceAlgoID: tt.givePurpose2Algo,
},
}
}

value, present := accountGDPR.PurposeEnforcementAlgo(tt.givePurpose)

assert.Equal(t, tt.wantAlgo, value, tt.description)
assert.Equal(t, tt.wantAlgoSet, present, tt.description)
}
}

func TestPurposeEnforcingVendors(t *testing.T) {
tests := []struct {
description string
Expand Down Expand Up @@ -433,73 +501,52 @@ func TestPurposeEnforcingVendors(t *testing.T) {
}
}

func TestPurposeVendorException(t *testing.T) {
func TestPurposeVendorExceptions(t *testing.T) {
tests := []struct {
description string
givePurposeConfigNil bool
givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{}
givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{}
givePurpose consentconstants.Purpose
giveBidder openrtb_ext.BidderName
wantIsVendorException bool
wantVendorExceptionSet bool
wantExceptionMap map[openrtb_ext.BidderName]struct{}
wantExceptionMapSet bool
}{
{
description: "Purpose config is nil",
givePurposeConfigNil: true,
givePurpose: 1,
giveBidder: "appnexus",
wantIsVendorException: false,
wantVendorExceptionSet: false,
description: "Purpose config is nil",
givePurposeConfigNil: true,
givePurpose: 1,
// wantExceptionMap: map[openrtb_ext.BidderName]struct{}{},
wantExceptionMap: nil,
wantExceptionMapSet: false,
},
{
description: "Nil - exception map not defined for purpose",
givePurpose: 1,
giveBidder: "appnexus",
wantIsVendorException: false,
wantVendorExceptionSet: false,
description: "Nil - exception map not defined for purpose",
givePurpose: 1,
// wantExceptionMap: map[openrtb_ext.BidderName]struct{}{},
wantExceptionMap: nil,
wantExceptionMapSet: false,
},
{
description: "Empty - exception map empty for purpose",
givePurpose: 1,
givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{},
giveBidder: "appnexus",
wantIsVendorException: false,
wantVendorExceptionSet: true,
wantExceptionMap: map[openrtb_ext.BidderName]struct{}{},
wantExceptionMapSet: true,
},
{
description: "One - bidder found in purpose exception map containing one entry",
givePurpose: 1,
givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}},
giveBidder: "appnexus",
wantIsVendorException: true,
wantVendorExceptionSet: true,
},
{
description: "Many - bidder found in purpose exception map containing multiple entries",
description: "Nonempty - exception map with multiple entries for purpose",
givePurpose: 1,
givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
giveBidder: "appnexus",
wantIsVendorException: true,
wantVendorExceptionSet: true,
wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
wantExceptionMapSet: true,
},
{
description: "Many - bidder not found in purpose exception map containing multiple entries",
givePurpose: 1,
givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
giveBidder: "openx",
wantIsVendorException: false,
wantVendorExceptionSet: true,
},
{
description: "Many - bidder found in different purpose exception map containing multiple entries",
description: "Nonempty - exception map with multiple entries for different purpose",
givePurpose: 2,
givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
giveBidder: "openx",
wantIsVendorException: true,
wantVendorExceptionSet: true,
wantExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
wantExceptionMapSet: true,
},
}

Expand All @@ -517,10 +564,10 @@ func TestPurposeVendorException(t *testing.T) {
}
}

value, present := accountGDPR.PurposeVendorException(tt.givePurpose, tt.giveBidder)
value, present := accountGDPR.PurposeVendorExceptions(tt.givePurpose)

assert.Equal(t, tt.wantIsVendorException, value, tt.description)
assert.Equal(t, tt.wantVendorExceptionSet, present, tt.description)
assert.Equal(t, tt.wantExceptionMap, value, tt.description)
assert.Equal(t, tt.wantExceptionMapSet, present, tt.description)
}
}

Expand Down Expand Up @@ -704,59 +751,3 @@ func TestPurposeOneTreatmentAccessAllowed(t *testing.T) {
assert.Equal(t, tt.wantAllowedSet, present, tt.description)
}
}

func TestBasicEnforcementVendor(t *testing.T) {
tests := []struct {
description string
giveBasicVendorMap map[string]struct{}
giveBidder openrtb_ext.BidderName
wantBasicVendorSet bool
wantIsBasicVendor bool
}{
{
description: "Nil - basic vendor map not defined",
giveBidder: "appnexus",
wantBasicVendorSet: false,
wantIsBasicVendor: false,
},
{
description: "Empty - basic vendor map empty",
giveBasicVendorMap: map[string]struct{}{},
giveBidder: "appnexus",
wantBasicVendorSet: true,
wantIsBasicVendor: false,
},
{
description: "One - bidder found in basic vendor map containing one entry",
giveBasicVendorMap: map[string]struct{}{"appnexus": {}},
giveBidder: "appnexus",
wantBasicVendorSet: true,
wantIsBasicVendor: true,
},
{
description: "Many - bidder found in basic vendor map containing multiple entries",
giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
giveBidder: "appnexus",
wantBasicVendorSet: true,
wantIsBasicVendor: true,
},
{
description: "Many - bidder not found in basic vendor map containing multiple entries",
giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
giveBidder: "openx",
wantBasicVendorSet: true,
wantIsBasicVendor: false,
},
}

for _, tt := range tests {
accountGDPR := AccountGDPR{
BasicEnforcementVendorsMap: tt.giveBasicVendorMap,
}

value, present := accountGDPR.BasicEnforcementVendor(tt.giveBidder)

assert.Equal(t, tt.wantIsBasicVendor, value, tt.description)
assert.Equal(t, tt.wantBasicVendorSet, present, tt.description)
}
}
Loading

0 comments on commit 98b7bb4

Please sign in to comment.