Skip to content

Commit

Permalink
Add account CCPA enabled and per-request-type enabled flags (#1566)
Browse files Browse the repository at this point in the history
* Add account level request-type-specific and general CCPA enabled flags

* Remove mapstructure annotations on CCPA account structs and clean up CCPA tests

* Adjust account/host CCPA enabled flag logic to incorporate feedback on similar GDPR feature

* Add shared privacy policy account integration data structure

* Refactor EnabledForIntegrationType methods on account privacy objects

* Minor test refactor

* Simplify logic in EnabledForIntegrationType methods
  • Loading branch information
bsardo authored Nov 18, 2020
1 parent 17f5020 commit 1c31e06
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 104 deletions.
62 changes: 40 additions & 22 deletions config/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,45 +17,63 @@ type Account struct {
Disabled bool `mapstructure:"disabled" json:"disabled"`
CacheTTL DefaultTTLs `mapstructure:"cache_ttl" json:"cache_ttl"`
EventsEnabled bool `mapstructure:"events_enabled" json:"events_enabled"`
CCPA AccountCCPA `mapstructure:"ccpa" json:"ccpa"`
GDPR AccountGDPR `mapstructure:"gdpr" json:"gdpr"`
}

// AccountCCPA represents account-specific CCPA configuration
type AccountCCPA struct {
Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"`
IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
}

// EnabledForIntegrationType indicates whether CCPA is turned on at the account level for the specified integration type
// by using the integration type setting if defined or the general CCPA setting if defined; otherwise it returns nil
func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType) *bool {
if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil {
return integrationEnabled
}
return a.Enabled
}

// AccountGDPR represents account-specific GDPR configuration
type AccountGDPR struct {
Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"`
IntegrationEnabled AccountGDPRIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
Enabled *bool `mapstructure:"enabled" json:"enabled,omitempty"`
IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
}

// EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type
// by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil
func (a *AccountGDPR) EnabledForIntegrationType(integrationType IntegrationType) *bool {
var integrationEnabled *bool

switch integrationType {
case IntegrationTypeAMP:
integrationEnabled = a.IntegrationEnabled.AMP
case IntegrationTypeApp:
integrationEnabled = a.IntegrationEnabled.App
case IntegrationTypeVideo:
integrationEnabled = a.IntegrationEnabled.Video
case IntegrationTypeWeb:
integrationEnabled = a.IntegrationEnabled.Web
}

if integrationEnabled != nil {
if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil {
return integrationEnabled
}
if a.Enabled != nil {
return a.Enabled
}

return nil
return a.Enabled
}

// AccountGDPRIntegration indicates whether GDPR is enabled for each integration type
type AccountGDPRIntegration struct {
// AccountIntegration indicates whether a particular privacy policy (GDPR, CCPA) is enabled for each integration type
type AccountIntegration struct {
AMP *bool `mapstructure:"amp" json:"amp,omitempty"`
App *bool `mapstructure:"app" json:"app,omitempty"`
Video *bool `mapstructure:"video" json:"video,omitempty"`
Web *bool `mapstructure:"web" json:"web,omitempty"`
}

// GetByIntegrationType looks up the account integration enabled setting for the specified integration type
func (a *AccountIntegration) GetByIntegrationType(integrationType IntegrationType) *bool {
var integrationEnabled *bool

switch integrationType {
case IntegrationTypeAMP:
integrationEnabled = a.AMP
case IntegrationTypeApp:
integrationEnabled = a.App
case IntegrationTypeVideo:
integrationEnabled = a.Video
case IntegrationTypeWeb:
integrationEnabled = a.Web
}

return integrationEnabled
}
223 changes: 175 additions & 48 deletions config/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,64 +10,26 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
trueValue, falseValue := true, false

tests := []struct {
description string
giveIntegrationType IntegrationType
giveGDPREnabled *bool
giveAMPGDPREnabled *bool
giveAppGDPREnabled *bool
giveVideoGDPREnabled *bool
giveWebGDPREnabled *bool
wantEnabled *bool
description string
giveIntegrationType IntegrationType
giveGDPREnabled *bool
giveWebGDPREnabled *bool
wantEnabled *bool
}{
{
description: "GDPR AMP integration enabled, general GDPR disabled",
giveIntegrationType: IntegrationTypeAMP,
giveGDPREnabled: &falseValue,
giveAMPGDPREnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "GDPR App integration enabled, general GDPR disabled",
giveIntegrationType: IntegrationTypeApp,
giveGDPREnabled: &falseValue,
giveAppGDPREnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "GDPR Video integration enabled, general GDPR disabled",
giveIntegrationType: IntegrationTypeVideo,
giveGDPREnabled: &falseValue,
giveVideoGDPREnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "GDPR Web integration enabled, general GDPR disabled",
giveIntegrationType: IntegrationTypeWeb,
giveGDPREnabled: &falseValue,
giveWebGDPREnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "Web integration enabled, general GDPR unspecified",
giveIntegrationType: IntegrationTypeWeb,
giveGDPREnabled: nil,
giveWebGDPREnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "GDPR Web integration disabled, general GDPR enabled",
giveIntegrationType: IntegrationTypeWeb,
giveGDPREnabled: &trueValue,
giveWebGDPREnabled: &falseValue,
wantEnabled: &falseValue,
},
{
description: "GDPR Web integration disabled, general GDPR unspecified",
giveIntegrationType: IntegrationTypeWeb,
giveGDPREnabled: nil,
giveWebGDPREnabled: &falseValue,
wantEnabled: &falseValue,
},
{
description: "GDPR Web integration unspecified, general GDPR disabled",
giveIntegrationType: IntegrationTypeWeb,
Expand Down Expand Up @@ -95,11 +57,8 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
account := Account{
GDPR: AccountGDPR{
Enabled: tt.giveGDPREnabled,
IntegrationEnabled: AccountGDPRIntegration{
AMP: tt.giveAMPGDPREnabled,
App: tt.giveAppGDPREnabled,
Video: tt.giveVideoGDPREnabled,
Web: tt.giveWebGDPREnabled,
IntegrationEnabled: AccountIntegration{
Web: tt.giveWebGDPREnabled,
},
},
}
Expand All @@ -114,3 +73,171 @@ func TestAccountGDPREnabledForIntegrationType(t *testing.T) {
}
}
}

func TestAccountCCPAEnabledForIntegrationType(t *testing.T) {
trueValue, falseValue := true, false

tests := []struct {
description string
giveIntegrationType IntegrationType
giveCCPAEnabled *bool
giveWebCCPAEnabled *bool
wantEnabled *bool
}{
{
description: "CCPA Web integration enabled, general CCPA disabled",
giveIntegrationType: IntegrationTypeWeb,
giveCCPAEnabled: &falseValue,
giveWebCCPAEnabled: &trueValue,
wantEnabled: &trueValue,
},
{
description: "CCPA Web integration disabled, general CCPA enabled",
giveIntegrationType: IntegrationTypeWeb,
giveCCPAEnabled: &trueValue,
giveWebCCPAEnabled: &falseValue,
wantEnabled: &falseValue,
},
{
description: "CCPA Web integration unspecified, general CCPA disabled",
giveIntegrationType: IntegrationTypeWeb,
giveCCPAEnabled: &falseValue,
giveWebCCPAEnabled: nil,
wantEnabled: &falseValue,
},
{
description: "CCPA Web integration unspecified, general CCPA enabled",
giveIntegrationType: IntegrationTypeWeb,
giveCCPAEnabled: &trueValue,
giveWebCCPAEnabled: nil,
wantEnabled: &trueValue,
},
{
description: "CCPA Web integration unspecified, general CCPA unspecified",
giveIntegrationType: IntegrationTypeWeb,
giveCCPAEnabled: nil,
giveWebCCPAEnabled: nil,
wantEnabled: nil,
},
}

for _, tt := range tests {
account := Account{
CCPA: AccountCCPA{
Enabled: tt.giveCCPAEnabled,
IntegrationEnabled: AccountIntegration{
Web: tt.giveWebCCPAEnabled,
},
},
}

enabled := account.CCPA.EnabledForIntegrationType(tt.giveIntegrationType)

if tt.wantEnabled == nil {
assert.Nil(t, enabled, tt.description)
} else {
assert.NotNil(t, enabled, tt.description)
assert.Equal(t, *tt.wantEnabled, *enabled, tt.description)
}
}
}

func TestAccountIntegrationGetByIntegrationType(t *testing.T) {
trueValue, falseValue := true, false

tests := []struct {
description string
giveAMPEnabled *bool
giveAppEnabled *bool
giveVideoEnabled *bool
giveWebEnabled *bool
giveIntegrationType IntegrationType
wantEnabled *bool
}{
{
description: "AMP integration setting unspecified, returns nil",
giveIntegrationType: IntegrationTypeAMP,
wantEnabled: nil,
},
{
description: "AMP integration disabled, returns false",
giveAMPEnabled: &falseValue,
giveIntegrationType: IntegrationTypeAMP,
wantEnabled: &falseValue,
},
{
description: "AMP integration enabled, returns true",
giveAMPEnabled: &trueValue,
giveIntegrationType: IntegrationTypeAMP,
wantEnabled: &trueValue,
},
{
description: "App integration setting unspecified, returns nil",
giveIntegrationType: IntegrationTypeApp,
wantEnabled: nil,
},
{
description: "App integration disabled, returns false",
giveAppEnabled: &falseValue,
giveIntegrationType: IntegrationTypeApp,
wantEnabled: &falseValue,
},
{
description: "App integration enabled, returns true",
giveAppEnabled: &trueValue,
giveIntegrationType: IntegrationTypeApp,
wantEnabled: &trueValue,
},
{
description: "Video integration setting unspecified, returns nil",
giveIntegrationType: IntegrationTypeVideo,
wantEnabled: nil,
},
{
description: "Video integration disabled, returns false",
giveVideoEnabled: &falseValue,
giveIntegrationType: IntegrationTypeVideo,
wantEnabled: &falseValue,
},
{
description: "Video integration enabled, returns true",
giveVideoEnabled: &trueValue,
giveIntegrationType: IntegrationTypeVideo,
wantEnabled: &trueValue,
},
{
description: "Web integration setting unspecified, returns nil",
giveIntegrationType: IntegrationTypeWeb,
wantEnabled: nil,
},
{
description: "Web integration disabled, returns false",
giveWebEnabled: &falseValue,
giveIntegrationType: IntegrationTypeWeb,
wantEnabled: &falseValue,
},
{
description: "Web integration enabled, returns true",
giveWebEnabled: &trueValue,
giveIntegrationType: IntegrationTypeWeb,
wantEnabled: &trueValue,
},
}

for _, tt := range tests {
accountIntegration := AccountIntegration{
AMP: tt.giveAMPEnabled,
App: tt.giveAppEnabled,
Video: tt.giveVideoEnabled,
Web: tt.giveWebEnabled,
}

result := accountIntegration.GetByIntegrationType(tt.giveIntegrationType)
if tt.wantEnabled == nil {
assert.Nil(t, result, tt.description)
} else {
assert.NotNil(t, result, tt.description)
assert.Equal(t, *tt.wantEnabled, *result, tt.description)
}
}
}
13 changes: 10 additions & 3 deletions exchange/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ func cleanOpenRTBRequests(ctx context.Context,
consent := extractConsent(orig)
ampGDPRException := (labels.RType == pbsmetrics.ReqTypeAMP) && gDPR.AMPException()

ccpaEnforcer, err := extractCCPA(orig, privacyConfig, aliases)
ccpaEnforcer, err := extractCCPA(orig, privacyConfig, account, aliases, integrationTypeMap[labels.RType])
if err != nil {
errs = append(errs, err)
return
Expand Down Expand Up @@ -144,7 +144,14 @@ func gdprEnabled(account *config.Account, privacyConfig config.Privacy, integrat
return privacyConfig.GDPR.Enabled
}

func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases map[string]string) (privacy.PolicyEnforcer, error) {
func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.IntegrationType) bool {
if accountEnabled := account.CCPA.EnabledForIntegrationType(requestType); accountEnabled != nil {
return *accountEnabled
}
return privacyConfig.CCPA.Enforce
}

func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, account *config.Account, aliases map[string]string, requestType config.IntegrationType) (privacy.PolicyEnforcer, error) {
ccpaPolicy, err := ccpa.ReadFromRequest(orig)
if err != nil {
return privacy.NilPolicyEnforcer{}, err
Expand All @@ -157,7 +164,7 @@ func extractCCPA(orig *openrtb.BidRequest, privacyConfig config.Privacy, aliases
}

ccpaEnforcer := privacy.EnabledPolicyEnforcer{
Enabled: privacyConfig.CCPA.Enforce,
Enabled: ccpaEnabled(account, privacyConfig, requestType),
PolicyEnforcer: ccpaParsedPolicy,
}
return ccpaEnforcer, nil
Expand Down
Loading

0 comments on commit 1c31e06

Please sign in to comment.