From 90278ba3aca038e31403c267c07654aa34b84f56 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Tue, 13 Jun 2023 23:36:37 -0700 Subject: [PATCH 01/14] Initial activities framework --- config/activity.go | 3 +- endpoints/openrtb2/auction.go | 8 + exchange/exchange.go | 2 + exchange/utils.go | 14 ++ privacy/activities/transmittids.go | 39 +++++ privacy/activity.go | 3 + privacy/enforcer.go | 259 ++++++++++++++++++++++++++--- privacy/enforcer_test.go | 17 -- privacy/policyenforcer.go | 45 +++++ privacy/policyenforcer_test.go | 18 ++ privacy/scrubber.go | 5 + usersync/chooser.go | 7 + 12 files changed, 375 insertions(+), 45 deletions(-) create mode 100644 privacy/activities/transmittids.go create mode 100644 privacy/policyenforcer.go create mode 100644 privacy/policyenforcer_test.go diff --git a/config/activity.go b/config/activity.go index 987cbe84a2d..9a0ad767dae 100644 --- a/config/activity.go +++ b/config/activity.go @@ -8,16 +8,17 @@ type AllowActivities struct { TransmitUserFPD Activity `mapstructure:"transmitUfpd" json:"transmitUfpd"` TransmitPreciseGeo Activity `mapstructure:"transmitPreciseGeo" json:"transmitPreciseGeo"` TransmitUniqueRequestIds Activity `mapstructure:"transmitUniqueRequestIds" json:"transmitUniqueRequestIds"` + TransmitTIds Activity `mapstructure:"transmitTid" json:"transmitTid"` } type Activity struct { Default *bool `mapstructure:"default" json:"default"` Rules []ActivityRule `mapstructure:"rules" json:"rules"` - Allow bool `mapstructure:"allow" json:"allow"` } type ActivityRule struct { Condition ActivityCondition `mapstructure:"condition" json:"condition"` + Allow bool `mapstructure:"allow" json:"allow"` } type ActivityCondition struct { diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 216bff6eaf5..8ea2f190eba 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/privacy" "io" "io/ioutil" "net/http" @@ -190,6 +191,12 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + activities, activitiesErr := privacy.NewActivityControl(deps.cfg.AccountDefaults.Privacy, account.Privacy) + if activitiesErr != nil { + errL = append(errL, activitiesErr) + writeError(errL, w, &labels) + return + } ctx := context.Background() @@ -236,6 +243,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http PubID: labels.PubID, HookExecutor: hookExecutor, TCF2Config: tcf2Config, + Activitities: activities, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) ao.RequestWrapper = req diff --git a/exchange/exchange.go b/exchange/exchange.go index 4251c3e2f18..fcd48482d8e 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/privacy" "math/rand" "net/url" "runtime/debug" @@ -195,6 +196,7 @@ type AuctionRequest struct { GlobalPrivacyControlHeader string ImpExtInfoMap map[string]ImpExtInfo TCF2Config gdpr.TCF2ConfigReader + Activitities privacy.ActivityControl // LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. diff --git a/exchange/utils.go b/exchange/utils.go index d43a4182451..aec00ff2bf8 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/privacy/activities" "math/rand" "github.com/buger/jsonparser" @@ -184,6 +185,19 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) } + + //!!! activity + transmitTIdsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityTransmitTIds, *req, + privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) + if transmitTIdsActivityAllowed == privacy.EnforceDeny { + //!!! Effect if not Permitted + // remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. + activityErr := activities.RemoveTIds(bidderRequest.BidRequest) + if activityErr != nil { + errs = append(errs, activityErr) + } + } + } return diff --git a/privacy/activities/transmittids.go b/privacy/activities/transmittids.go new file mode 100644 index 00000000000..7257c91eb4d --- /dev/null +++ b/privacy/activities/transmittids.go @@ -0,0 +1,39 @@ +package activities + +import ( + "encoding/json" + "github.com/prebid/openrtb/v19/openrtb2" +) + +func RemoveTIds(bidReq *openrtb2.BidRequest) error { + + //!!!only for testing + bidReq.ID = "modified!!!" + + if bidReq.Source != nil { + bidReq.Source.TID = "" + } + + for i, imp := range bidReq.Imp { + if len(imp.Ext) > 0 { + var err error + //!!!better way to search and remove? + var impExt map[string]interface{} + err = json.Unmarshal(imp.Ext, &impExt) + if err != nil { + return err + } + + if _, present := impExt["tid"]; present { + delete(impExt, "tid") + newExt, err := json.Marshal(impExt) + if err != nil { + return err + } + bidReq.Imp[i].Ext = newExt + } + + } + } + return nil +} diff --git a/privacy/activity.go b/privacy/activity.go index 7cdef17590c..cac17a486fc 100644 --- a/privacy/activity.go +++ b/privacy/activity.go @@ -11,6 +11,7 @@ const ( ActivityTransmitUserFPD ActivityTransmitPreciseGeo ActivityTransmitUniqueRequestIds + ActivityTransmitTIds ) func (a Activity) String() string { @@ -29,6 +30,8 @@ func (a Activity) String() string { return "transmitPreciseGeo" case ActivityTransmitUniqueRequestIds: return "transmitUniqueRequestIds" + case ActivityTransmitTIds: + return "transmitTid" } return "" diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 0d5ecad5309..16369280b8e 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -1,43 +1,248 @@ package privacy -// PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy. -type PolicyEnforcer interface { - // CanEnforce returns true when policy information is specifically provided by the publisher. - CanEnforce() bool +import ( + "fmt" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/openrtb_ext" + "strings" +) - // ShouldEnforce returns true when the OpenRTB request should have personally identifiable - // information (PII) removed or anonymized per the policy. - ShouldEnforce(bidder string) bool +type EnforceResult int // or maybe ActivityResult? + +const ( + EnforceAbstain EnforceResult = iota + EnforceAllow + EnforceDeny +) + +type ActivityControl struct { + plans map[Activity]EnforcementPlan } -// NilPolicyEnforcer implements the PolicyEnforcer interface but will always return false. -type NilPolicyEnforcer struct{} +func NewActivityControl(hostConf config.AccountPrivacy, accConf config.AccountPrivacy) (ActivityControl, error) { + //!!how to merge host config with acc configs? + ac := ActivityControl{plans: nil} + var err error + + plans := make(map[Activity]EnforcementPlan) + + plans[ActivitySyncUser], err = buildEnforcementPlan(hostConf.AllowActivities.SyncUser) + if err != nil { + return ac, err + } + plans[ActivityFetchBids], err = buildEnforcementPlan(hostConf.AllowActivities.FetchBids) + if err != nil { + return ac, err + } + plans[ActivityEnrichUserFPD], err = buildEnforcementPlan(hostConf.AllowActivities.EnrichUserFPD) + if err != nil { + return ac, err + } + plans[ActivityReportAnalytics], err = buildEnforcementPlan(hostConf.AllowActivities.ReportAnalytics) + if err != nil { + return ac, err + } + plans[ActivityTransmitUserFPD], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitUserFPD) + if err != nil { + return ac, err + } + plans[ActivityTransmitPreciseGeo], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitPreciseGeo) + if err != nil { + return ac, err + } + plans[ActivityTransmitUniqueRequestIds], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitUniqueRequestIds) + if err != nil { + return ac, err + } + plans[ActivityTransmitTIds], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitTIds) + if err != nil { + return ac, err + } + + ac.plans = plans -// CanEnforce is hardcoded to always return false. -func (NilPolicyEnforcer) CanEnforce() bool { - return false + return ac, nil } -// ShouldEnforce is hardcoded to always return false. -func (NilPolicyEnforcer) ShouldEnforce(bidder string) bool { - return false +func buildEnforcementPlan(activity config.Activity) (EnforcementPlan, error) { + ef := EnforcementPlan{} + rules, err := activityRulesToEnforcementRules(activity.Rules) + if err != nil { + return ef, err + } + ef.defaultResult = activityDefaultToDefaultResult(activity.Default) + ef.rules = rules + return ef, nil } -// EnabledPolicyEnforcer decorates a PolicyEnforcer with an enabled flag. -type EnabledPolicyEnforcer struct { - Enabled bool - PolicyEnforcer PolicyEnforcer +func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]EnforcementRule, error) { + enfRules := make([]EnforcementRule, 0) + for _, r := range rules { + cmpName, err := conditionToRuleComponentName(r.Condition.ComponentName) + if err != nil { + return nil, err + } + er := ComponentEnforcementRule{ + allowed: r.Allow, + componentName: cmpName, + componentType: r.Condition.ComponentType, + } + enfRules = append(enfRules, er) + } + return enfRules, nil } -// CanEnforce returns true when the PolicyEnforcer can enforce. -func (p EnabledPolicyEnforcer) CanEnforce() bool { - return p.PolicyEnforcer.CanEnforce() +func conditionToRuleComponentName(conditions []string) ([]ScopedName, error) { + sn := make([]ScopedName, 0) + for _, condition := range conditions { + scope, err := NewScopedName(condition) + if err != nil { + return sn, err + } + sn = append(sn, scope) + } + return sn, nil } -// ShouldEnforce returns true when the enforcer is enabled the PolicyEnforcer allows enforcement. -func (p EnabledPolicyEnforcer) ShouldEnforce(bidder string) bool { - if p.Enabled { - return p.PolicyEnforcer.ShouldEnforce(bidder) +func activityDefaultToDefaultResult(activityDefault *bool) EnforceResult { + if activityDefault == nil { + return EnforceAbstain + } else if *activityDefault { + return EnforceAllow } - return false + return EnforceDeny +} + +func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { + plan, planDefined := e.plans[activity] + + if !planDefined { + return EnforceAbstain + } + + return plan.Allow(request, target) +} + +// allow this to be created from acitivty config, which veronika will get from the account config root object +// maybe call this ActivityPlan? +type EnforcementPlan struct { + defaultResult EnforceResult + rules []EnforcementRule +} + +func (p EnforcementPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { + for _, rule := range p.rules { + result := rule.Allow(request, target) // exit on first non-abstain response + if result == EnforceAllow || result == EnforceDeny { + return result + } + } + return p.defaultResult +} + +// maybe call this ActivityRule? +type EnforcementRule interface { + Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult +} + +type ComponentEnforcementRule struct { + componentName []ScopedName + componentType []string + // include gppSectionId from 3.5 + // include geo from 3.5 + allowed bool // behavior if rule matches. can be either true=allow or false=deny. result is abstain if the rule doesn't match +} + +func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { + // all string comparisons in this section are case sensitive + // doc: https://docs.google.com/document/d/1dRxFUFmhh2jGanzGZvfkK_6jtHPpHXWD7Qsi6KEugeE/edit + // the doc details the boolean operations. + // - "or" within each field (componentName, componentType + // - "and" between the rules present. empty fields are ignored (refer to doc for details) + + // componentName + // check for matching scoped name. a wildcard is allowed for the name in which any target with the same scope is matched + + // componentType + // can either act as a scope wildcard or meta targeting. can be scope "bidder", "analytics", maybe others. + // may also be "rtd" meta. you need to pass that through somehow, perhaps as targetMeta? targetMeta can be a slice. should be small enough that search speed isn't a concern. + + // gppSectionId + // check if id is present in the gppsid slice. no parsing of gpp should happen here. + + // geo + // simple filter on the req.user section + + scopeFound := false + for _, scope := range r.componentName { + if strings.EqualFold(scope.Scope, target.Scope) && strings.EqualFold(scope.Name, target.Name) { + scopeFound = true + break + } + } + + typeFound := false + for _, componentType := range r.componentType { + if strings.EqualFold(componentType, target.Scope) { + typeFound = true + break + } + } + + matchFound := scopeFound || typeFound + + if matchFound && r.allowed { + return EnforceAllow + } + return EnforceDeny +} + +// the default scope should be hardcoded as bidder +// ex: "bidder.appnexus", "bidder.*", "appnexus", "analytics.pubmatic" +// TODO: add parsing helpers +type ScopedName struct { + Scope string + Name string +} + +const ( + ScopeTypeBidder = "bidder" + ScopeTypeAnalytics = "analytics" + ScopeTypeRTD = "rtd" // real time data + ScopeTypeUserId = "userid" + ScopeTypeGeneral = "general" +) + +func NewScopedName(condition string) (ScopedName, error) { + + var scope, name string + split := strings.Split(condition, ".") + if len(split) == 2 { + s := strings.ToLower(split[0]) + if s == ScopeTypeBidder || s == ScopeTypeAnalytics || s == ScopeTypeUserId { + scope = s + } else if strings.Contains(s, ScopeTypeRTD) { + scope = ScopeTypeRTD + } else { + scope = ScopeTypeGeneral + } + name = split[1] + } else if len(split) == 1 { + scope = ScopeTypeBidder + name = split[0] + } else { + return ScopedName{}, fmt.Errorf("unable to parse condition: %s", condition) + } + + return ScopedName{ + Scope: scope, + Name: name, + }, nil +} + +// ex: "USA.VA", "USA". see all comments in https://github.com/prebid/prebid-server/issues/2622 +// TODO: add parsing helpers +type Geo struct { + Country string + Region string } diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go index b0c4032c714..ecefad59b06 100644 --- a/privacy/enforcer_test.go +++ b/privacy/enforcer_test.go @@ -1,18 +1 @@ package privacy - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestNilEnforcerCanEnforce(t *testing.T) { - nilEnforcer := &NilPolicyEnforcer{} - assert.False(t, nilEnforcer.CanEnforce()) -} - -func TestNilEnforcerShouldEnforce(t *testing.T) { - nilEnforcer := &NilPolicyEnforcer{} - assert.False(t, nilEnforcer.ShouldEnforce("")) - assert.False(t, nilEnforcer.ShouldEnforce("anyBidder")) -} diff --git a/privacy/policyenforcer.go b/privacy/policyenforcer.go new file mode 100644 index 00000000000..e70c0d3d190 --- /dev/null +++ b/privacy/policyenforcer.go @@ -0,0 +1,45 @@ +package privacy + +// NOTE: Reanme this package. Will eventually replace in its entirety with Activites. + +// PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy. +type PolicyEnforcer interface { + // CanEnforce returns true when policy information is specifically provided by the publisher. + CanEnforce() bool + + // ShouldEnforce returns true when the OpenRTB request should have personally identifiable + // information (PII) removed or anonymized per the policy. + ShouldEnforce(bidder string) bool +} + +// NilPolicyEnforcer implements the PolicyEnforcer interface but will always return false. +type NilPolicyEnforcer struct{} + +// CanEnforce is hardcoded to always return false. +func (NilPolicyEnforcer) CanEnforce() bool { + return false +} + +// ShouldEnforce is hardcoded to always return false. +func (NilPolicyEnforcer) ShouldEnforce(bidder string) bool { + return false +} + +// EnabledPolicyEnforcer decorates a PolicyEnforcer with an enabled flag. +type EnabledPolicyEnforcer struct { + Enabled bool + PolicyEnforcer PolicyEnforcer +} + +// CanEnforce returns true when the PolicyEnforcer can enforce. +func (p EnabledPolicyEnforcer) CanEnforce() bool { + return p.PolicyEnforcer.CanEnforce() +} + +// ShouldEnforce returns true when the enforcer is enabled the PolicyEnforcer allows enforcement. +func (p EnabledPolicyEnforcer) ShouldEnforce(bidder string) bool { + if p.Enabled { + return p.PolicyEnforcer.ShouldEnforce(bidder) + } + return false +} diff --git a/privacy/policyenforcer_test.go b/privacy/policyenforcer_test.go new file mode 100644 index 00000000000..b0c4032c714 --- /dev/null +++ b/privacy/policyenforcer_test.go @@ -0,0 +1,18 @@ +package privacy + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNilEnforcerCanEnforce(t *testing.T) { + nilEnforcer := &NilPolicyEnforcer{} + assert.False(t, nilEnforcer.CanEnforce()) +} + +func TestNilEnforcerShouldEnforce(t *testing.T) { + nilEnforcer := &NilPolicyEnforcer{} + assert.False(t, nilEnforcer.ShouldEnforce("")) + assert.False(t, nilEnforcer.ShouldEnforce("anyBidder")) +} diff --git a/privacy/scrubber.go b/privacy/scrubber.go index 428193bcda1..a746424cd56 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -146,6 +146,11 @@ func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo S return &userCopy } +/* +func (scrubber) ScrubTransactionIDs(r reqes) { + //!!!remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. +}*/ + func scrubIPV4Lowest8(ip string) string { i := strings.LastIndex(ip, ".") if i == -1 { diff --git a/usersync/chooser.go b/usersync/chooser.go index 97fa1471b7e..227833116ae 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -150,6 +150,13 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} if cookie.HasLiveSync(syncer.Key()) { return nil, BidderEvaluation{Status: StatusAlreadySynced, Bidder: bidder, SyncerKey: syncer.Key()} } + // integrate with syncUser activity here + // will need to provide a RequestWrapper. since this is one of the few activities without a natural request. + //!!! compose a fake one. you won't have geo, but can provide gppsid. + /*if !privacy.ActivityAllows(bidder) { + return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} + } + */ if !privacy.GDPRAllowsBidderSync(bidder) { return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} From fce25c02dc41d5b189697763246a6820db46216e Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Wed, 14 Jun 2023 15:03:09 -0700 Subject: [PATCH 02/14] Fix for condition evaluation --- exchange/utils.go | 2 +- privacy/activities/transmittids.go | 3 ++- privacy/enforcer.go | 14 +++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index aec00ff2bf8..7009963010a 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -189,7 +189,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, //!!! activity transmitTIdsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityTransmitTIds, *req, privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) - if transmitTIdsActivityAllowed == privacy.EnforceDeny { + if transmitTIdsActivityAllowed == privacy.EnforceAllow { //!!! Effect if not Permitted // remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. activityErr := activities.RemoveTIds(bidderRequest.BidRequest) diff --git a/privacy/activities/transmittids.go b/privacy/activities/transmittids.go index 7257c91eb4d..1b19f6cb341 100644 --- a/privacy/activities/transmittids.go +++ b/privacy/activities/transmittids.go @@ -8,12 +8,13 @@ import ( func RemoveTIds(bidReq *openrtb2.BidRequest) error { //!!!only for testing - bidReq.ID = "modified!!!" + bidReq.ID = "" if bidReq.Source != nil { bidReq.Source.TID = "" } + //bidders don't receive imp.ext data except of bidder parameters for i, imp := range bidReq.Imp { if len(imp.Ext) > 0 { var err error diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 16369280b8e..52062949a2e 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -108,9 +108,9 @@ func activityDefaultToDefaultResult(activityDefault *bool) EnforceResult { if activityDefault == nil { return EnforceAbstain } else if *activityDefault { - return EnforceAllow + return EnforceDeny } - return EnforceDeny + return EnforceAllow } func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { @@ -191,10 +191,14 @@ func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, targ matchFound := scopeFound || typeFound - if matchFound && r.allowed { - return EnforceAllow + if matchFound { + if r.allowed { + return EnforceDeny + } else { + return EnforceAllow + } } - return EnforceDeny + return EnforceAbstain } // the default scope should be hardcoded as bidder From d2c5c9d1d0bb3b0e36a0311befd5b0de5c94040c Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Thu, 15 Jun 2023 18:12:23 -0700 Subject: [PATCH 03/14] Rules evaluation logic fixes --- exchange/utils.go | 2 +- privacy/enforcer.go | 62 +++++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 26 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index 7009963010a..77b88d3b390 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -189,7 +189,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, //!!! activity transmitTIdsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityTransmitTIds, *req, privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) - if transmitTIdsActivityAllowed == privacy.EnforceAllow { + if transmitTIdsActivityAllowed == privacy.ActivityDeny || transmitTIdsActivityAllowed == privacy.ActivityAbstain { //!!! Effect if not Permitted // remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. activityErr := activities.RemoveTIds(bidderRequest.BidRequest) diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 52062949a2e..82f4cbda2cc 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -7,12 +7,12 @@ import ( "strings" ) -type EnforceResult int // or maybe ActivityResult? +type ActivityResult int const ( - EnforceAbstain EnforceResult = iota - EnforceAllow - EnforceDeny + ActivityAbstain ActivityResult = iota + ActivityAllow + ActivityDeny ) type ActivityControl struct { @@ -104,20 +104,22 @@ func conditionToRuleComponentName(conditions []string) ([]ScopedName, error) { return sn, nil } -func activityDefaultToDefaultResult(activityDefault *bool) EnforceResult { +func activityDefaultToDefaultResult(activityDefault *bool) ActivityResult { + if activityDefault == nil { - return EnforceAbstain + // if default is unspecified, the hardcoded default-default is true. + return ActivityAllow } else if *activityDefault { - return EnforceDeny + return ActivityAllow } - return EnforceAllow + return ActivityDeny } -func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { +func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { plan, planDefined := e.plans[activity] if !planDefined { - return EnforceAbstain + return ActivityAbstain } return plan.Allow(request, target) @@ -126,23 +128,29 @@ func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWra // allow this to be created from acitivty config, which veronika will get from the account config root object // maybe call this ActivityPlan? type EnforcementPlan struct { - defaultResult EnforceResult + defaultResult ActivityResult rules []EnforcementRule } -func (p EnforcementPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { +func (p EnforcementPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { + // "and" between the rules present + // ??? result is abstain if the rule doesn't match for _, rule := range p.rules { - result := rule.Allow(request, target) // exit on first non-abstain response - if result == EnforceAllow || result == EnforceDeny { + result := rule.Allow(request, target) + if result == ActivityDeny { return result } + if result == ActivityAbstain { + return p.defaultResult + } } - return p.defaultResult + + return ActivityAllow } // maybe call this ActivityRule? type EnforcementRule interface { - Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult + Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult } type ComponentEnforcementRule struct { @@ -153,7 +161,7 @@ type ComponentEnforcementRule struct { allowed bool // behavior if rule matches. can be either true=allow or false=deny. result is abstain if the rule doesn't match } -func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, target ScopedName) EnforceResult { +func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { // all string comparisons in this section are case sensitive // doc: https://docs.google.com/document/d/1dRxFUFmhh2jGanzGZvfkK_6jtHPpHXWD7Qsi6KEugeE/edit // the doc details the boolean operations. @@ -173,14 +181,15 @@ func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, targ // geo // simple filter on the req.user section - scopeFound := false + //!!! what is the behavior when only one componentName or componentType is present? + componentNameFound := false for _, scope := range r.componentName { - if strings.EqualFold(scope.Scope, target.Scope) && strings.EqualFold(scope.Name, target.Name) { - scopeFound = true + if strings.EqualFold(scope.Scope, target.Scope) && + (strings.EqualFold(scope.Name, target.Name) || scope.Name == "*") { + componentNameFound = true break } } - typeFound := false for _, componentType := range r.componentType { if strings.EqualFold(componentType, target.Scope) { @@ -189,16 +198,19 @@ func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, targ } } - matchFound := scopeFound || typeFound + matchFound := componentNameFound && typeFound + + // behavior if rule matches: can be either true=allow or false=deny. result is abstain if the rule doesn't match if matchFound { if r.allowed { - return EnforceDeny + return ActivityAllow } else { - return EnforceAllow + return ActivityDeny } } - return EnforceAbstain + + return ActivityAbstain } // the default scope should be hardcoded as bidder From 63c4d780f4f63a73051e2dbbb96b389ae3eef870 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Mon, 19 Jun 2023 15:55:42 -0700 Subject: [PATCH 04/14] No merge for host and account configs needed at the activities level --- endpoints/openrtb2/auction.go | 2 +- privacy/enforcer.go | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 8ea2f190eba..6c5f571cf95 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -191,7 +191,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) - activities, activitiesErr := privacy.NewActivityControl(deps.cfg.AccountDefaults.Privacy, account.Privacy) + activities, activitiesErr := privacy.NewActivityControl(account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) writeError(errL, w, &labels) diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 82f4cbda2cc..a8863e82d95 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -19,42 +19,41 @@ type ActivityControl struct { plans map[Activity]EnforcementPlan } -func NewActivityControl(hostConf config.AccountPrivacy, accConf config.AccountPrivacy) (ActivityControl, error) { - //!!how to merge host config with acc configs? +func NewActivityControl(privacyConf config.AccountPrivacy) (ActivityControl, error) { ac := ActivityControl{plans: nil} var err error plans := make(map[Activity]EnforcementPlan) - plans[ActivitySyncUser], err = buildEnforcementPlan(hostConf.AllowActivities.SyncUser) + plans[ActivitySyncUser], err = buildEnforcementPlan(privacyConf.AllowActivities.SyncUser) if err != nil { return ac, err } - plans[ActivityFetchBids], err = buildEnforcementPlan(hostConf.AllowActivities.FetchBids) + plans[ActivityFetchBids], err = buildEnforcementPlan(privacyConf.AllowActivities.FetchBids) if err != nil { return ac, err } - plans[ActivityEnrichUserFPD], err = buildEnforcementPlan(hostConf.AllowActivities.EnrichUserFPD) + plans[ActivityEnrichUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.EnrichUserFPD) if err != nil { return ac, err } - plans[ActivityReportAnalytics], err = buildEnforcementPlan(hostConf.AllowActivities.ReportAnalytics) + plans[ActivityReportAnalytics], err = buildEnforcementPlan(privacyConf.AllowActivities.ReportAnalytics) if err != nil { return ac, err } - plans[ActivityTransmitUserFPD], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitUserFPD) + plans[ActivityTransmitUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUserFPD) if err != nil { return ac, err } - plans[ActivityTransmitPreciseGeo], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitPreciseGeo) + plans[ActivityTransmitPreciseGeo], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitPreciseGeo) if err != nil { return ac, err } - plans[ActivityTransmitUniqueRequestIds], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitUniqueRequestIds) + plans[ActivityTransmitUniqueRequestIds], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUniqueRequestIds) if err != nil { return ac, err } - plans[ActivityTransmitTIds], err = buildEnforcementPlan(hostConf.AllowActivities.TransmitTIds) + plans[ActivityTransmitTIds], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitTIds) if err != nil { return ac, err } From b60143d03d1ef5b77faa043971da9d7115b26691 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Mon, 19 Jun 2023 16:08:40 -0700 Subject: [PATCH 05/14] Added activities support ror am endpoint --- endpoints/openrtb2/amp_auction.go | 9 +++++++++ endpoints/openrtb2/auction.go | 1 + 2 files changed, 10 insertions(+) diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 0947c6f4f0b..100861a1036 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/prebid/prebid-server/privacy" "net/http" "net/url" "strings" @@ -222,6 +223,13 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + activities, activitiesErr := privacy.NewActivityControl(account.Privacy) + if activitiesErr != nil { + errL = append(errL, activitiesErr) + writeError(errL, w, &labels) + return + } + secGPC := r.Header.Get("Sec-GPC") auctionRequest := &exchange.AuctionRequest{ @@ -239,6 +247,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h HookExecutor: hookExecutor, QueryParams: r.URL.Query(), TCF2Config: tcf2Config, + Activitities: activities, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 6c5f571cf95..42c0c93d9b9 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -191,6 +191,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http } tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR) + activities, activitiesErr := privacy.NewActivityControl(account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) From d6af286de7d5eff302c59a082cfb767a5f773abd Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Mon, 19 Jun 2023 16:24:47 -0700 Subject: [PATCH 06/14] Added fetchBids activity support --- exchange/utils.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/exchange/utils.go b/exchange/utils.go index 77b88d3b390..a1674fd10ca 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -176,6 +176,15 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) } + // fetchBids activity + fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, *req, + privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) + if fetchBidsActivityAllowed != privacy.ActivityAllow { + // skip the call to a bidder if fetchBids activity is not allowed + // do not add this bidder to allowedBidderRequests + continue + } + if bidRequestAllowed { privacyEnforcement.Apply(bidderRequest.BidRequest) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) @@ -186,7 +195,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) } - //!!! activity + // transmitTid activity transmitTIdsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityTransmitTIds, *req, privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) if transmitTIdsActivityAllowed == privacy.ActivityDeny || transmitTIdsActivityAllowed == privacy.ActivityAbstain { From d5f13d7134e6a483a100d47da6cc5ff0db7c4290 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Wed, 21 Jun 2023 18:39:29 -0700 Subject: [PATCH 07/14] Code clean up --- config/account.go | 2 +- exchange/utils.go | 32 ++++++------------ privacy/enforcer.go | 79 +++++++++++++++------------------------------ 3 files changed, 36 insertions(+), 77 deletions(-) diff --git a/config/account.go b/config/account.go index 393194bc3f6..d37d89e149e 100644 --- a/config/account.go +++ b/config/account.go @@ -40,7 +40,7 @@ type Account struct { Validations Validations `mapstructure:"validations" json:"validations"` DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"` BidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments `mapstructure:"bidadjustments" json:"bidadjustments"` - Privacy AccountPrivacy `mapstructure:"privacy" json:"privacy"` + Privacy *AccountPrivacy `mapstructure:"privacy" json:"privacy"` } // CookieSync represents the account-level defaults for the cookie sync endpoint. diff --git a/exchange/utils.go b/exchange/utils.go index a1674fd10ca..8ef4f20fae3 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "github.com/prebid/prebid-server/privacy/activities" "math/rand" "github.com/buger/jsonparser" @@ -151,6 +150,15 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, for _, bidderRequest := range allBidderRequests { bidRequestAllowed := true + // fetchBids activity + fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, *req, + privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) + if fetchBidsActivityAllowed == privacy.ActivityDeny { + // skip the call to a bidder if fetchBids activity is not allowed + // do not add this bidder to allowedBidderRequests + continue + } + // CCPA privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) @@ -176,15 +184,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, applyFPD(auctionReq.FirstPartyData[bidderRequest.BidderName], bidderRequest.BidRequest) } - // fetchBids activity - fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, *req, - privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) - if fetchBidsActivityAllowed != privacy.ActivityAllow { - // skip the call to a bidder if fetchBids activity is not allowed - // do not add this bidder to allowedBidderRequests - continue - } - if bidRequestAllowed { privacyEnforcement.Apply(bidderRequest.BidRequest) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) @@ -194,19 +193,6 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, setLegacyGDPRFromGPP(bidderRequest.BidRequest, gpp) setLegacyUSPFromGPP(bidderRequest.BidRequest, gpp) } - - // transmitTid activity - transmitTIdsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityTransmitTIds, *req, - privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) - if transmitTIdsActivityAllowed == privacy.ActivityDeny || transmitTIdsActivityAllowed == privacy.ActivityAbstain { - //!!! Effect if not Permitted - // remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. - activityErr := activities.RemoveTIds(bidderRequest.BidRequest) - if activityErr != nil { - errs = append(errs, activityErr) - } - } - } return diff --git a/privacy/enforcer.go b/privacy/enforcer.go index a8863e82d95..6fc7d6310a6 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -16,14 +16,18 @@ const ( ) type ActivityControl struct { - plans map[Activity]EnforcementPlan + plans map[Activity]ActivityPlan } -func NewActivityControl(privacyConf config.AccountPrivacy) (ActivityControl, error) { +func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, error) { ac := ActivityControl{plans: nil} var err error - plans := make(map[Activity]EnforcementPlan) + if privacyConf == nil { + return ac, err + } + + plans := make(map[Activity]ActivityPlan) plans[ActivitySyncUser], err = buildEnforcementPlan(privacyConf.AllowActivities.SyncUser) if err != nil { @@ -63,8 +67,8 @@ func NewActivityControl(privacyConf config.AccountPrivacy) (ActivityControl, err return ac, nil } -func buildEnforcementPlan(activity config.Activity) (EnforcementPlan, error) { - ef := EnforcementPlan{} +func buildEnforcementPlan(activity config.Activity) (ActivityPlan, error) { + ef := ActivityPlan{} rules, err := activityRulesToEnforcementRules(activity.Rules) if err != nil { return ef, err @@ -74,8 +78,8 @@ func buildEnforcementPlan(activity config.Activity) (EnforcementPlan, error) { return ef, nil } -func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]EnforcementRule, error) { - enfRules := make([]EnforcementRule, 0) +func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]ActivityRule, error) { + enfRules := make([]ActivityRule, 0) for _, r := range rules { cmpName, err := conditionToRuleComponentName(r.Condition.ComponentName) if err != nil { @@ -104,7 +108,6 @@ func conditionToRuleComponentName(conditions []string) ([]ScopedName, error) { } func activityDefaultToDefaultResult(activityDefault *bool) ActivityResult { - if activityDefault == nil { // if default is unspecified, the hardcoded default-default is true. return ActivityAllow @@ -124,31 +127,22 @@ func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWra return plan.Allow(request, target) } -// allow this to be created from acitivty config, which veronika will get from the account config root object -// maybe call this ActivityPlan? -type EnforcementPlan struct { +type ActivityPlan struct { defaultResult ActivityResult - rules []EnforcementRule + rules []ActivityRule } -func (p EnforcementPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { - // "and" between the rules present - // ??? result is abstain if the rule doesn't match +func (p ActivityPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { for _, rule := range p.rules { result := rule.Allow(request, target) - if result == ActivityDeny { + if result == ActivityDeny || result == ActivityAllow { return result } - if result == ActivityAbstain { - return p.defaultResult - } } - - return ActivityAllow + return p.defaultResult } -// maybe call this ActivityRule? -type EnforcementRule interface { +type ActivityRule interface { Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult } @@ -157,31 +151,14 @@ type ComponentEnforcementRule struct { componentType []string // include gppSectionId from 3.5 // include geo from 3.5 - allowed bool // behavior if rule matches. can be either true=allow or false=deny. result is abstain if the rule doesn't match + allowed bool } func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { - // all string comparisons in this section are case sensitive - // doc: https://docs.google.com/document/d/1dRxFUFmhh2jGanzGZvfkK_6jtHPpHXWD7Qsi6KEugeE/edit - // the doc details the boolean operations. - // - "or" within each field (componentName, componentType - // - "and" between the rules present. empty fields are ignored (refer to doc for details) - - // componentName - // check for matching scoped name. a wildcard is allowed for the name in which any target with the same scope is matched - - // componentType - // can either act as a scope wildcard or meta targeting. can be scope "bidder", "analytics", maybe others. - // may also be "rtd" meta. you need to pass that through somehow, perhaps as targetMeta? targetMeta can be a slice. should be small enough that search speed isn't a concern. - - // gppSectionId - // check if id is present in the gppsid slice. no parsing of gpp should happen here. - - // geo - // simple filter on the req.user section - - //!!! what is the behavior when only one componentName or componentType is present? componentNameFound := false + if len(r.componentName) == 0 { + componentNameFound = true + } for _, scope := range r.componentName { if strings.EqualFold(scope.Scope, target.Scope) && (strings.EqualFold(scope.Name, target.Name) || scope.Name == "*") { @@ -189,18 +166,19 @@ func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, targ break } } + typeFound := false + if len(r.componentType) == 0 { + typeFound = true + } for _, componentType := range r.componentType { if strings.EqualFold(componentType, target.Scope) { typeFound = true break } } - - matchFound := componentNameFound && typeFound - // behavior if rule matches: can be either true=allow or false=deny. result is abstain if the rule doesn't match - + matchFound := componentNameFound && typeFound if matchFound { if r.allowed { return ActivityAllow @@ -208,13 +186,9 @@ func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, targ return ActivityDeny } } - return ActivityAbstain } -// the default scope should be hardcoded as bidder -// ex: "bidder.appnexus", "bidder.*", "appnexus", "analytics.pubmatic" -// TODO: add parsing helpers type ScopedName struct { Scope string Name string @@ -256,7 +230,6 @@ func NewScopedName(condition string) (ScopedName, error) { } // ex: "USA.VA", "USA". see all comments in https://github.com/prebid/prebid-server/issues/2622 -// TODO: add parsing helpers type Geo struct { Country string Region string From c0457b8680ca4ed1559d8d16075e5689219c3059 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Thu, 22 Jun 2023 16:45:06 -0700 Subject: [PATCH 08/14] Unit tests part I --- exchange/utils.go | 2 +- privacy/enforcer.go | 21 +- privacy/enforcer_test.go | 415 +++++++++++++++++++++++++++++++++++++++ privacy/scrubber.go | 5 - usersync/chooser.go | 7 - 5 files changed, 429 insertions(+), 21 deletions(-) diff --git a/exchange/utils.go b/exchange/utils.go index 8ef4f20fae3..e3ea6514b69 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -151,7 +151,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, bidRequestAllowed := true // fetchBids activity - fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, *req, + fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) if fetchBidsActivityAllowed == privacy.ActivityDeny { // skip the call to a bidder if fetchBids activity is not allowed diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 6fc7d6310a6..8bef427b25a 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -3,7 +3,6 @@ package privacy import ( "fmt" "github.com/prebid/prebid-server/config" - "github.com/prebid/prebid-server/openrtb_ext" "strings" ) @@ -117,14 +116,14 @@ func activityDefaultToDefaultResult(activityDefault *bool) ActivityResult { return ActivityDeny } -func (e ActivityControl) Allow(activity Activity, request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { +func (e ActivityControl) Allow(activity Activity, target ScopedName) ActivityResult { plan, planDefined := e.plans[activity] if !planDefined { return ActivityAbstain } - return plan.Allow(request, target) + return plan.Allow(target) } type ActivityPlan struct { @@ -132,9 +131,9 @@ type ActivityPlan struct { rules []ActivityRule } -func (p ActivityPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { +func (p ActivityPlan) Allow(target ScopedName) ActivityResult { for _, rule := range p.rules { - result := rule.Allow(request, target) + result := rule.Allow(target) if result == ActivityDeny || result == ActivityAllow { return result } @@ -143,7 +142,7 @@ func (p ActivityPlan) Allow(request openrtb_ext.RequestWrapper, target ScopedNam } type ActivityRule interface { - Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult + Allow(target ScopedName) ActivityResult } type ComponentEnforcementRule struct { @@ -154,7 +153,11 @@ type ComponentEnforcementRule struct { allowed bool } -func (r ComponentEnforcementRule) Allow(request openrtb_ext.RequestWrapper, target ScopedName) ActivityResult { +func (r ComponentEnforcementRule) Allow(target ScopedName) ActivityResult { + if len(r.componentName) == 0 && len(r.componentType) == 0 { + return ActivityAbstain + } + componentNameFound := false if len(r.componentName) == 0 { componentNameFound = true @@ -203,7 +206,9 @@ const ( ) func NewScopedName(condition string) (ScopedName, error) { - + if condition == "" { + return ScopedName{}, fmt.Errorf("unable to parse empty condition") + } var scope, name string split := strings.Split(condition, ".") if len(split) == 2 { diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go index ecefad59b06..7a3ad3439b1 100644 --- a/privacy/enforcer_test.go +++ b/privacy/enforcer_test.go @@ -1 +1,416 @@ package privacy + +import ( + "errors" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/util/ptrutil" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewActivityControl(t *testing.T) { + + testCases := []struct { + name string + privacyConf *config.AccountPrivacy + activityControl ActivityControl + err error + }{ + { + name: "privacy config is nil", + privacyConf: nil, + activityControl: ActivityControl{plans: nil}, + err: nil, + }, + { + name: "privacy config is specified and correct", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + SyncUser: getDefaultActivityConfig(), + FetchBids: getDefaultActivityConfig(), + EnrichUserFPD: getDefaultActivityConfig(), + ReportAnalytics: getDefaultActivityConfig(), + TransmitUserFPD: getDefaultActivityConfig(), + TransmitPreciseGeo: getDefaultActivityConfig(), + TransmitUniqueRequestIds: getDefaultActivityConfig(), + TransmitTIds: getDefaultActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivitySyncUser: getDefaultActivityPlan(), + ActivityFetchBids: getDefaultActivityPlan(), + ActivityEnrichUserFPD: getDefaultActivityPlan(), + ActivityReportAnalytics: getDefaultActivityPlan(), + ActivityTransmitUserFPD: getDefaultActivityPlan(), + ActivityTransmitPreciseGeo: getDefaultActivityPlan(), + ActivityTransmitUniqueRequestIds: getDefaultActivityPlan(), + ActivityTransmitTIds: getDefaultActivityPlan(), + }}, + err: nil, + }, + { + name: "privacy config is specified and SyncUser is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + SyncUser: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and FetchBids is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + FetchBids: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and EnrichUserFPD is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + EnrichUserFPD: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and ReportAnalytics is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + ReportAnalytics: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and TransmitUserFPD is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + TransmitUserFPD: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and TransmitPreciseGeo is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + TransmitPreciseGeo: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and TransmitUniqueRequestIds is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + TransmitUniqueRequestIds: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "privacy config is specified and TransmitTIds is incorrect", + privacyConf: &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + TransmitTIds: getIncorrectActivityConfig(), + }, + }, + activityControl: ActivityControl{plans: nil}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualAC, actualErr := NewActivityControl(test.privacyConf) + if test.err == nil { + assert.Equal(t, test.activityControl, actualAC, "incorrect activity control") + assert.NoError(t, actualErr, "error should be nil") + } else { + assert.EqualError(t, actualErr, test.err.Error(), "error is incorrect") + } + }) + } +} + +func TestActivityDefaultToDefaultResult(t *testing.T) { + + testCases := []struct { + name string + activityDefault *bool + expectedResult ActivityResult + }{ + { + name: "activityDefault is nil", + activityDefault: nil, + expectedResult: ActivityAllow, + }, + { + name: "activityDefault is nil", + activityDefault: ptrutil.ToPtr(true), + expectedResult: ActivityAllow, + }, + { + name: "activityDefault is nil", + activityDefault: ptrutil.ToPtr(false), + expectedResult: ActivityDeny, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := activityDefaultToDefaultResult(test.activityDefault) + assert.Equal(t, actualResult, test.expectedResult, "result is incorrect") + + }) + } +} + +func TestAllowActivityControl(t *testing.T) { + + testCases := []struct { + name string + activityControl ActivityControl + activity Activity + target ScopedName + activityResult ActivityResult + }{ + { + name: "plans is nil", + activityControl: ActivityControl{plans: nil}, + activity: ActivityFetchBids, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityAbstain, + }, + { + name: "activity not defined", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivitySyncUser: getDefaultActivityPlan()}}, + activity: ActivityFetchBids, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityAbstain, + }, + { + name: "activity defined but not allowed", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivityFetchBids: getDefaultActivityPlan()}}, + activity: ActivityFetchBids, + target: ScopedName{Scope: "bidder", Name: "bidderB"}, + activityResult: ActivityAllow, + }, + { + name: "activity defined and allowed", + activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ + ActivityFetchBids: getDefaultActivityPlan()}}, + activity: ActivityFetchBids, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := test.activityControl.Allow(test.activity, test.target) + assert.Equal(t, test.activityResult, actualResult, "incorrect allow activity result") + + }) + } +} + +func TestAllowComponentEnforcementRule(t *testing.T) { + + testCases := []struct { + name string + componentRule ComponentEnforcementRule + target ScopedName + activityResult ActivityResult + }{ + { + name: "activity is allowed", + componentRule: ComponentEnforcementRule{ + allowed: true, + componentName: []ScopedName{ + {Scope: "bidder", Name: "bidderA"}, + }, + componentType: []string{"bidder"}, + }, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + { + name: "activity is not allowed", + componentRule: ComponentEnforcementRule{ + allowed: false, + componentName: []ScopedName{ + {Scope: "bidder", Name: "bidderA"}, + }, + componentType: []string{"bidder"}, + }, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityDeny, + }, + { + name: "activity is not found", + componentRule: ComponentEnforcementRule{ + allowed: true, + componentName: []ScopedName{ + {Scope: "bidder", Name: "bidderA"}, + }, + componentType: []string{"bidder"}, + }, + target: ScopedName{Scope: "bidder", Name: "bidderB"}, + activityResult: ActivityAbstain, + }, + { + name: "activity is not allowed, componentName only", + componentRule: ComponentEnforcementRule{ + allowed: true, + componentName: []ScopedName{ + {Scope: "bidder", Name: "bidderA"}, + }, + }, + target: ScopedName{Scope: "bidder", Name: "bidderA"}, + activityResult: ActivityAllow, + }, + { + name: "activity is allowed, componentType only", + componentRule: ComponentEnforcementRule{ + allowed: true, + componentType: []string{"bidder"}, + }, + target: ScopedName{Scope: "bidder", Name: "bidderB"}, + activityResult: ActivityAllow, + }, + { + name: "activity is allowed, no componentType and no componentName", + componentRule: ComponentEnforcementRule{ + allowed: true, + }, + target: ScopedName{Scope: "bidder", Name: "bidderB"}, + activityResult: ActivityAbstain, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualResult := test.componentRule.Allow(test.target) + assert.Equal(t, test.activityResult, actualResult, "incorrect allow activity result") + + }) + } +} + +func TestNewScopedName(t *testing.T) { + + testCases := []struct { + name string + condition string + expectedScopeName ScopedName + err error + }{ + { + name: "condition is empty", + condition: "", + expectedScopeName: ScopedName{}, + err: errors.New("unable to parse empty condition"), + }, + { + name: "condition is incorrect", + condition: "bidder.bidderA.bidderB", + expectedScopeName: ScopedName{}, + err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), + }, + { + name: "condition is correct with separator", + condition: "bidder.bidderA", + expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"}, + err: nil, + }, + { + name: "condition is bidder name", + condition: "bidderA", + expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"}, + err: nil, + }, + { + name: "condition is bidder name", + condition: "rtd.test", + expectedScopeName: ScopedName{Scope: "rtd", Name: "test"}, + err: nil, + }, + { + name: "condition is bidder name", + condition: "test.test", + expectedScopeName: ScopedName{Scope: "general", Name: "test"}, + err: nil, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + actualSN, actualErr := NewScopedName(test.condition) + if test.err == nil { + assert.Equal(t, test.expectedScopeName, actualSN, "incorrect activity control") + assert.NoError(t, actualErr, "error should be nil") + } else { + assert.EqualError(t, actualErr, test.err.Error(), "error is incorrect") + } + }) + } +} + +// constants +func getDefaultActivityConfig() config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: true, + Condition: config.ActivityCondition{ + ComponentName: []string{"bidderA"}, + ComponentType: []string{"bidder"}, + }, + }, + }, + } +} + +func getDefaultActivityPlan() ActivityPlan { + return ActivityPlan{ + defaultResult: ActivityAllow, + rules: []ActivityRule{ + ComponentEnforcementRule{ + allowed: true, + componentName: []ScopedName{ + {Scope: "bidder", Name: "bidderA"}, + }, + componentType: []string{"bidder"}, + }, + }, + } +} + +func getIncorrectActivityConfig() config.Activity { + return config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: true, + Condition: config.ActivityCondition{ + ComponentName: []string{"bidder.bidderA.bidderB"}, + ComponentType: []string{"bidder"}, + }, + }, + }, + } +} diff --git a/privacy/scrubber.go b/privacy/scrubber.go index a746424cd56..428193bcda1 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -146,11 +146,6 @@ func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo S return &userCopy } -/* -func (scrubber) ScrubTransactionIDs(r reqes) { - //!!!remove source.tid and imp.ext.tid. This can be specific to certain bidders or global to all bidders. -}*/ - func scrubIPV4Lowest8(ip string) string { i := strings.LastIndex(ip, ".") if i == -1 { diff --git a/usersync/chooser.go b/usersync/chooser.go index 227833116ae..97fa1471b7e 100644 --- a/usersync/chooser.go +++ b/usersync/chooser.go @@ -150,13 +150,6 @@ func (c standardChooser) evaluate(bidder string, syncersSeen map[string]struct{} if cookie.HasLiveSync(syncer.Key()) { return nil, BidderEvaluation{Status: StatusAlreadySynced, Bidder: bidder, SyncerKey: syncer.Key()} } - // integrate with syncUser activity here - // will need to provide a RequestWrapper. since this is one of the few activities without a natural request. - //!!! compose a fake one. you won't have geo, but can provide gppsid. - /*if !privacy.ActivityAllows(bidder) { - return nil, BidderEvaluation{Status: StatusBlockedByPrivacy, Bidder: bidder, SyncerKey: syncer.Key()} - } - */ if !privacy.GDPRAllowsBidderSync(bidder) { return nil, BidderEvaluation{Status: StatusBlockedByGDPR, Bidder: bidder, SyncerKey: syncer.Key()} From 62fdcb6a0e15cf27793c54143967f44ec60068d7 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Fri, 23 Jun 2023 20:57:58 -0700 Subject: [PATCH 09/14] Unit tests part II --- exchange/utils_test.go | 68 ++++++++++++++++++++++++++++++ privacy/activities/transmittids.go | 40 ------------------ 2 files changed, 68 insertions(+), 40 deletions(-) delete mode 100644 privacy/activities/transmittids.go diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 6fe0e55fe50..1cec140815f 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -17,6 +17,7 @@ import ( "github.com/prebid/prebid-server/gdpr" "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/privacy" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -4273,3 +4274,70 @@ func TestGetMediaTypeForBid(t *testing.T) { }) } } + +func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { + testCases := []struct { + description string + req *openrtb2.BidRequest + componentName string + allow bool + expectedReqNumber int + }{ + { + description: "request with one bidder allowed", + req: newBidRequest(t), + componentName: "appnexus", + allow: true, + expectedReqNumber: 1, + }, + { + description: "request with one bidder not allowed", + req: newBidRequest(t), + componentName: "appnexus", + allow: false, + expectedReqNumber: 0, + }, + } + + for _, test := range testCases { + privacyConfig := getDefaultActivityConfig(test.componentName, test.allow) + activities, err := privacy.NewActivityControl(privacyConfig) + assert.NoError(t, err, "") + auctionReq := AuctionRequest{ + BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, + UserSyncs: &emptyUsersync{}, + Activitities: activities, + } + + bidderToSyncerKey := map[string]string{} + reqSplitter := &requestSplitter{ + bidderToSyncerKey: bidderToSyncerKey, + me: &metrics.MetricsEngineMock{}, + hostSChainNode: nil, + bidderInfo: config.BidderInfos{}, + } + + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + assert.Empty(t, errs, "unexpected error returned") + assert.Len(t, bidderRequests, test.expectedReqNumber, "incorrect number of requests") + } +} + +func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { + return &config.AccountPrivacy{ + AllowActivities: config.AllowActivities{ + FetchBids: config.Activity{ + Default: ptrutil.ToPtr(true), + Rules: []config.ActivityRule{ + { + Allow: allow, + Condition: config.ActivityCondition{ + ComponentName: []string{componentName}, + ComponentType: []string{"bidder"}, + }, + }, + }, + }, + }, + } +} diff --git a/privacy/activities/transmittids.go b/privacy/activities/transmittids.go deleted file mode 100644 index 1b19f6cb341..00000000000 --- a/privacy/activities/transmittids.go +++ /dev/null @@ -1,40 +0,0 @@ -package activities - -import ( - "encoding/json" - "github.com/prebid/openrtb/v19/openrtb2" -) - -func RemoveTIds(bidReq *openrtb2.BidRequest) error { - - //!!!only for testing - bidReq.ID = "" - - if bidReq.Source != nil { - bidReq.Source.TID = "" - } - - //bidders don't receive imp.ext data except of bidder parameters - for i, imp := range bidReq.Imp { - if len(imp.Ext) > 0 { - var err error - //!!!better way to search and remove? - var impExt map[string]interface{} - err = json.Unmarshal(imp.Ext, &impExt) - if err != nil { - return err - } - - if _, present := impExt["tid"]; present { - delete(impExt, "tid") - newExt, err := json.Marshal(impExt) - if err != nil { - return err - } - bidReq.Imp[i].Ext = newExt - } - - } - } - return nil -} From fb353b23f55eae64af299c968942498ad859b648 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Wed, 28 Jun 2023 09:57:37 -0700 Subject: [PATCH 10/14] Minor optimization --- privacy/enforcer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 8bef427b25a..51eca0ff239 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -164,7 +164,7 @@ func (r ComponentEnforcementRule) Allow(target ScopedName) ActivityResult { } for _, scope := range r.componentName { if strings.EqualFold(scope.Scope, target.Scope) && - (strings.EqualFold(scope.Name, target.Name) || scope.Name == "*") { + (scope.Name == "*" || strings.EqualFold(scope.Name, target.Name)) { componentNameFound = true break } From 7bc602705245c05fedf2590bbf587f8b2483a260 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Wed, 28 Jun 2023 15:28:28 -0700 Subject: [PATCH 11/14] Code review fixes --- config/activity.go | 2 +- endpoints/openrtb2/amp_auction.go | 2 +- endpoints/openrtb2/auction.go | 2 +- exchange/exchange.go | 2 +- exchange/utils.go | 2 +- exchange/utils_test.go | 16 +++--- privacy/activity.go | 4 +- privacy/enforcer.go | 40 ++++++------- privacy/enforcer_test.go | 95 +++++++++++++++++-------------- 9 files changed, 85 insertions(+), 80 deletions(-) diff --git a/config/activity.go b/config/activity.go index 9a0ad767dae..5bddc7c6405 100644 --- a/config/activity.go +++ b/config/activity.go @@ -8,7 +8,7 @@ type AllowActivities struct { TransmitUserFPD Activity `mapstructure:"transmitUfpd" json:"transmitUfpd"` TransmitPreciseGeo Activity `mapstructure:"transmitPreciseGeo" json:"transmitPreciseGeo"` TransmitUniqueRequestIds Activity `mapstructure:"transmitUniqueRequestIds" json:"transmitUniqueRequestIds"` - TransmitTIds Activity `mapstructure:"transmitTid" json:"transmitTid"` + TransmitTids Activity `mapstructure:"transmitTid" json:"transmitTid"` } type Activity struct { diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index 100861a1036..12a1869c9d5 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -247,7 +247,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h HookExecutor: hookExecutor, QueryParams: r.URL.Query(), TCF2Config: tcf2Config, - Activitities: activities, + Activities: activities, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 42c0c93d9b9..0dddb1940b7 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -244,7 +244,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http PubID: labels.PubID, HookExecutor: hookExecutor, TCF2Config: tcf2Config, - Activitities: activities, + Activities: activities, } auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil) ao.RequestWrapper = req diff --git a/exchange/exchange.go b/exchange/exchange.go index fcd48482d8e..09d9f4ddd37 100644 --- a/exchange/exchange.go +++ b/exchange/exchange.go @@ -196,7 +196,7 @@ type AuctionRequest struct { GlobalPrivacyControlHeader string ImpExtInfoMap map[string]ImpExtInfo TCF2Config gdpr.TCF2ConfigReader - Activitities privacy.ActivityControl + Activities privacy.ActivityControl // LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests // in HoldAuction until we get to factoring it away. Do not use for anything new. diff --git a/exchange/utils.go b/exchange/utils.go index e3ea6514b69..5b29fd6292f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -151,7 +151,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, bidRequestAllowed := true // fetchBids activity - fetchBidsActivityAllowed := auctionReq.Activitities.Allow(privacy.ActivityFetchBids, + fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids, privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()}) if fetchBidsActivityAllowed == privacy.ActivityDeny { // skip the call to a bidder if fetchBids activity is not allowed diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 1cec140815f..e3ed628cc3c 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -4277,21 +4277,21 @@ func TestGetMediaTypeForBid(t *testing.T) { func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { testCases := []struct { - description string + name string req *openrtb2.BidRequest componentName string allow bool expectedReqNumber int }{ { - description: "request with one bidder allowed", + name: "request_with_one_bidder_allowed", req: newBidRequest(t), componentName: "appnexus", allow: true, expectedReqNumber: 1, }, { - description: "request with one bidder not allowed", + name: "request_with_one_bidder_not_allowed", req: newBidRequest(t), componentName: "appnexus", allow: false, @@ -4306,7 +4306,7 @@ func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { auctionReq := AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, UserSyncs: &emptyUsersync{}, - Activitities: activities, + Activities: activities, } bidderToSyncerKey := map[string]string{} @@ -4317,9 +4317,11 @@ func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { bidderInfo: config.BidderInfos{}, } - bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) - assert.Empty(t, errs, "unexpected error returned") - assert.Len(t, bidderRequests, test.expectedReqNumber, "incorrect number of requests") + t.Run(test.name, func(t *testing.T) { + bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo) + assert.Empty(t, errs) + assert.Len(t, bidderRequests, test.expectedReqNumber) + }) } } diff --git a/privacy/activity.go b/privacy/activity.go index cac17a486fc..6ca48ae0b93 100644 --- a/privacy/activity.go +++ b/privacy/activity.go @@ -11,7 +11,7 @@ const ( ActivityTransmitUserFPD ActivityTransmitPreciseGeo ActivityTransmitUniqueRequestIds - ActivityTransmitTIds + ActivityTransmitTids ) func (a Activity) String() string { @@ -30,7 +30,7 @@ func (a Activity) String() string { return "transmitPreciseGeo" case ActivityTransmitUniqueRequestIds: return "transmitUniqueRequestIds" - case ActivityTransmitTIds: + case ActivityTransmitTids: return "transmitTid" } diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 51eca0ff239..a69474fb7ed 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -14,6 +14,14 @@ const ( ActivityDeny ) +const ( + ScopeTypeBidder = "bidder" + ScopeTypeAnalytics = "analytics" + ScopeTypeRTD = "rtd" // real time data + ScopeTypeUserID = "userid" + ScopeTypeGeneral = "general" +) + type ActivityControl struct { plans map[Activity]ActivityPlan } @@ -56,7 +64,7 @@ func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, er if err != nil { return ac, err } - plans[ActivityTransmitTIds], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitTIds) + plans[ActivityTransmitTids], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitTids) if err != nil { return ac, err } @@ -158,26 +166,24 @@ func (r ComponentEnforcementRule) Allow(target ScopedName) ActivityResult { return ActivityAbstain } - componentNameFound := false - if len(r.componentName) == 0 { - componentNameFound = true - } + componentNameFound := true for _, scope := range r.componentName { if strings.EqualFold(scope.Scope, target.Scope) && (scope.Name == "*" || strings.EqualFold(scope.Name, target.Name)) { componentNameFound = true break + } else { + componentNameFound = false } } - typeFound := false - if len(r.componentType) == 0 { - typeFound = true - } + typeFound := true for _, componentType := range r.componentType { if strings.EqualFold(componentType, target.Scope) { typeFound = true break + } else { + typeFound = false } } // behavior if rule matches: can be either true=allow or false=deny. result is abstain if the rule doesn't match @@ -197,14 +203,6 @@ type ScopedName struct { Name string } -const ( - ScopeTypeBidder = "bidder" - ScopeTypeAnalytics = "analytics" - ScopeTypeRTD = "rtd" // real time data - ScopeTypeUserId = "userid" - ScopeTypeGeneral = "general" -) - func NewScopedName(condition string) (ScopedName, error) { if condition == "" { return ScopedName{}, fmt.Errorf("unable to parse empty condition") @@ -213,7 +211,7 @@ func NewScopedName(condition string) (ScopedName, error) { split := strings.Split(condition, ".") if len(split) == 2 { s := strings.ToLower(split[0]) - if s == ScopeTypeBidder || s == ScopeTypeAnalytics || s == ScopeTypeUserId { + if s == ScopeTypeBidder || s == ScopeTypeAnalytics || s == ScopeTypeUserID { scope = s } else if strings.Contains(s, ScopeTypeRTD) { scope = ScopeTypeRTD @@ -233,9 +231,3 @@ func NewScopedName(condition string) (ScopedName, error) { Name: name, }, nil } - -// ex: "USA.VA", "USA". see all comments in https://github.com/prebid/prebid-server/issues/2622 -type Geo struct { - Country string - Region string -} diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go index 7a3ad3439b1..2b9715f87f8 100644 --- a/privacy/enforcer_test.go +++ b/privacy/enforcer_test.go @@ -17,13 +17,13 @@ func TestNewActivityControl(t *testing.T) { err error }{ { - name: "privacy config is nil", + name: "privacy_config_is_nil", privacyConf: nil, activityControl: ActivityControl{plans: nil}, err: nil, }, { - name: "privacy config is specified and correct", + name: "privacy_config_is_specified_and_correct", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ SyncUser: getDefaultActivityConfig(), @@ -33,7 +33,7 @@ func TestNewActivityControl(t *testing.T) { TransmitUserFPD: getDefaultActivityConfig(), TransmitPreciseGeo: getDefaultActivityConfig(), TransmitUniqueRequestIds: getDefaultActivityConfig(), - TransmitTIds: getDefaultActivityConfig(), + TransmitTids: getDefaultActivityConfig(), }, }, activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ @@ -44,12 +44,12 @@ func TestNewActivityControl(t *testing.T) { ActivityTransmitUserFPD: getDefaultActivityPlan(), ActivityTransmitPreciseGeo: getDefaultActivityPlan(), ActivityTransmitUniqueRequestIds: getDefaultActivityPlan(), - ActivityTransmitTIds: getDefaultActivityPlan(), + ActivityTransmitTids: getDefaultActivityPlan(), }}, err: nil, }, { - name: "privacy config is specified and SyncUser is incorrect", + name: "privacy_config_is_specified_and_SyncUser_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ SyncUser: getIncorrectActivityConfig(), @@ -59,7 +59,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and FetchBids is incorrect", + name: "privacy_config_is_specified_and_FetchBids_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ FetchBids: getIncorrectActivityConfig(), @@ -69,7 +69,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and EnrichUserFPD is incorrect", + name: "privacy_config_is_specified_and_EnrichUserFPD_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ EnrichUserFPD: getIncorrectActivityConfig(), @@ -79,7 +79,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and ReportAnalytics is incorrect", + name: "privacy_config_is_specified_and_ReportAnalytics_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ ReportAnalytics: getIncorrectActivityConfig(), @@ -89,7 +89,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and TransmitUserFPD is incorrect", + name: "privacy_config_is_specified_and_TransmitUserFPD_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ TransmitUserFPD: getIncorrectActivityConfig(), @@ -99,7 +99,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and TransmitPreciseGeo is incorrect", + name: "privacy_config_is_specified_and_TransmitPreciseGeo_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ TransmitPreciseGeo: getIncorrectActivityConfig(), @@ -109,7 +109,7 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and TransmitUniqueRequestIds is incorrect", + name: "privacy_config_is_specified_and_TransmitUniqueRequestIds_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ TransmitUniqueRequestIds: getIncorrectActivityConfig(), @@ -119,10 +119,10 @@ func TestNewActivityControl(t *testing.T) { err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "privacy config is specified and TransmitTIds is incorrect", + name: "privacy_config_is_specified_and_TransmitTids_is_incorrect", privacyConf: &config.AccountPrivacy{ AllowActivities: config.AllowActivities{ - TransmitTIds: getIncorrectActivityConfig(), + TransmitTids: getIncorrectActivityConfig(), }, }, activityControl: ActivityControl{plans: nil}, @@ -134,10 +134,10 @@ func TestNewActivityControl(t *testing.T) { t.Run(test.name, func(t *testing.T) { actualAC, actualErr := NewActivityControl(test.privacyConf) if test.err == nil { - assert.Equal(t, test.activityControl, actualAC, "incorrect activity control") - assert.NoError(t, actualErr, "error should be nil") + assert.Equal(t, test.activityControl, actualAC) + assert.NoError(t, actualErr) } else { - assert.EqualError(t, actualErr, test.err.Error(), "error is incorrect") + assert.EqualError(t, actualErr, test.err.Error()) } }) } @@ -151,17 +151,17 @@ func TestActivityDefaultToDefaultResult(t *testing.T) { expectedResult ActivityResult }{ { - name: "activityDefault is nil", + name: "activityDefault_is_nil", activityDefault: nil, expectedResult: ActivityAllow, }, { - name: "activityDefault is nil", + name: "activityDefault_is_true", activityDefault: ptrutil.ToPtr(true), expectedResult: ActivityAllow, }, { - name: "activityDefault is nil", + name: "activityDefault_is_false", activityDefault: ptrutil.ToPtr(false), expectedResult: ActivityDeny, }, @@ -170,8 +170,7 @@ func TestActivityDefaultToDefaultResult(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actualResult := activityDefaultToDefaultResult(test.activityDefault) - assert.Equal(t, actualResult, test.expectedResult, "result is incorrect") - + assert.Equal(t, test.expectedResult, actualResult) }) } } @@ -186,14 +185,14 @@ func TestAllowActivityControl(t *testing.T) { activityResult ActivityResult }{ { - name: "plans is nil", + name: "plans_is_nil", activityControl: ActivityControl{plans: nil}, activity: ActivityFetchBids, target: ScopedName{Scope: "bidder", Name: "bidderA"}, activityResult: ActivityAbstain, }, { - name: "activity not defined", + name: "activity_not_defined", activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ ActivitySyncUser: getDefaultActivityPlan()}}, activity: ActivityFetchBids, @@ -201,7 +200,7 @@ func TestAllowActivityControl(t *testing.T) { activityResult: ActivityAbstain, }, { - name: "activity defined but not allowed", + name: "activity_defined_but_not_found_default_returned", activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ ActivityFetchBids: getDefaultActivityPlan()}}, activity: ActivityFetchBids, @@ -209,7 +208,7 @@ func TestAllowActivityControl(t *testing.T) { activityResult: ActivityAllow, }, { - name: "activity defined and allowed", + name: "activity_defined_and_allowed", activityControl: ActivityControl{plans: map[Activity]ActivityPlan{ ActivityFetchBids: getDefaultActivityPlan()}}, activity: ActivityFetchBids, @@ -221,7 +220,7 @@ func TestAllowActivityControl(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actualResult := test.activityControl.Allow(test.activity, test.target) - assert.Equal(t, test.activityResult, actualResult, "incorrect allow activity result") + assert.Equal(t, test.activityResult, actualResult) }) } @@ -236,7 +235,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult ActivityResult }{ { - name: "activity is allowed", + name: "activity_is_allowed", componentRule: ComponentEnforcementRule{ allowed: true, componentName: []ScopedName{ @@ -248,7 +247,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult: ActivityAllow, }, { - name: "activity is not allowed", + name: "activity_is_not_allowed", componentRule: ComponentEnforcementRule{ allowed: false, componentName: []ScopedName{ @@ -260,7 +259,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult: ActivityDeny, }, { - name: "activity is not found", + name: "abstain_both_clauses_do_not_match", componentRule: ComponentEnforcementRule{ allowed: true, componentName: []ScopedName{ @@ -272,7 +271,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult: ActivityAbstain, }, { - name: "activity is not allowed, componentName only", + name: "activity_is_not_allowed_componentName_only", componentRule: ComponentEnforcementRule{ allowed: true, componentName: []ScopedName{ @@ -283,7 +282,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult: ActivityAllow, }, { - name: "activity is allowed, componentType only", + name: "activity_is_allowed_componentType_only", componentRule: ComponentEnforcementRule{ allowed: true, componentType: []string{"bidder"}, @@ -292,7 +291,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { activityResult: ActivityAllow, }, { - name: "activity is allowed, no componentType and no componentName", + name: "abstain_activity_no_componentType_and_no_componentName", componentRule: ComponentEnforcementRule{ allowed: true, }, @@ -304,7 +303,7 @@ func TestAllowComponentEnforcementRule(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { actualResult := test.componentRule.Allow(test.target) - assert.Equal(t, test.activityResult, actualResult, "incorrect allow activity result") + assert.Equal(t, test.activityResult, actualResult) }) } @@ -319,37 +318,49 @@ func TestNewScopedName(t *testing.T) { err error }{ { - name: "condition is empty", + name: "condition_is_empty", condition: "", expectedScopeName: ScopedName{}, err: errors.New("unable to parse empty condition"), }, { - name: "condition is incorrect", + name: "condition_is_incorrect", condition: "bidder.bidderA.bidderB", expectedScopeName: ScopedName{}, err: errors.New("unable to parse condition: bidder.bidderA.bidderB"), }, { - name: "condition is correct with separator", + name: "condition_is_scoped_to_bidder", condition: "bidder.bidderA", expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"}, err: nil, }, { - name: "condition is bidder name", + name: "condition_is_scoped_to_analytics", + condition: "analytics.bidderA", + expectedScopeName: ScopedName{Scope: "analytics", Name: "bidderA"}, + err: nil, + }, + { + name: "condition_is_scoped_to_userid", + condition: "userid.bidderA", + expectedScopeName: ScopedName{Scope: "userid", Name: "bidderA"}, + err: nil, + }, + { + name: "condition_is_bidder_name", condition: "bidderA", expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"}, err: nil, }, { - name: "condition is bidder name", + name: "condition_is_module_tag_rtd", condition: "rtd.test", expectedScopeName: ScopedName{Scope: "rtd", Name: "test"}, err: nil, }, { - name: "condition is bidder name", + name: "condition_scope_defaults_to_genera", condition: "test.test", expectedScopeName: ScopedName{Scope: "general", Name: "test"}, err: nil, @@ -360,10 +371,10 @@ func TestNewScopedName(t *testing.T) { t.Run(test.name, func(t *testing.T) { actualSN, actualErr := NewScopedName(test.condition) if test.err == nil { - assert.Equal(t, test.expectedScopeName, actualSN, "incorrect activity control") - assert.NoError(t, actualErr, "error should be nil") + assert.Equal(t, test.expectedScopeName, actualSN) + assert.NoError(t, actualErr) } else { - assert.EqualError(t, actualErr, test.err.Error(), "error is incorrect") + assert.EqualError(t, actualErr, test.err.Error()) } }) } From 40e6491a06f5f6aec212ea613841f30faf09c3fc Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Wed, 28 Jun 2023 18:14:35 -0700 Subject: [PATCH 12/14] Added conditional gdpr execution if activity result is abstain --- exchange/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchange/utils.go b/exchange/utils.go index 5b29fd6292f..15f20e5d028 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -163,7 +163,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) // GDPR - if gdprEnforced { + if (fetchBidsActivityAllowed == privacy.ActivityAbstain) && gdprEnforced { auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) bidRequestAllowed = auctionPermissions.AllowBidRequest From 99c21ea8ea395a3eaaf2c799257a37f37b6cbdfc Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Thu, 29 Jun 2023 18:54:47 -0700 Subject: [PATCH 13/14] Added extra verification activities are not configured at the host and account levels --- config/config.go | 4 ++++ exchange/utils.go | 2 +- exchange/utils_test.go | 2 +- privacy/enforcer.go | 4 ++++ privacy/enforcer_test.go | 2 +- 5 files changed, 11 insertions(+), 3 deletions(-) diff --git a/config/config.go b/config/config.go index eabf62ca414..f825f846e52 100644 --- a/config/config.go +++ b/config/config.go @@ -155,6 +155,10 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { cfg.TmaxAdjustments.Enabled = false } + if cfg.AccountDefaults.Privacy != nil { + errs = append(errs, errors.New("account_defaults.Privacy has no effect as the feature is under development.")) + } + errs = cfg.Experiment.validate(errs) errs = cfg.BidderInfos.validate(errs) return errs diff --git a/exchange/utils.go b/exchange/utils.go index 15f20e5d028..5b29fd6292f 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -163,7 +163,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String()) // GDPR - if (fetchBidsActivityAllowed == privacy.ActivityAbstain) && gdprEnforced { + if gdprEnforced { auctionPermissions, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName) bidRequestAllowed = auctionPermissions.AllowBidRequest diff --git a/exchange/utils_test.go b/exchange/utils_test.go index e3ed628cc3c..6fa9ec7d7b3 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -4275,7 +4275,7 @@ func TestGetMediaTypeForBid(t *testing.T) { } } -func TestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { +func TemporarilyDisabledTestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) { testCases := []struct { name string req *openrtb2.BidRequest diff --git a/privacy/enforcer.go b/privacy/enforcer.go index a69474fb7ed..242c077d91a 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -1,6 +1,7 @@ package privacy import ( + "errors" "fmt" "github.com/prebid/prebid-server/config" "strings" @@ -32,6 +33,9 @@ func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, er if privacyConf == nil { return ac, err + } else { + //temporarily disable Activities if they are specified at the account level + return ac, errors.New("account.Privacy has no effect as the feature is under development.") } plans := make(map[Activity]ActivityPlan) diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go index 2b9715f87f8..e87a9eb2bff 100644 --- a/privacy/enforcer_test.go +++ b/privacy/enforcer_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func TestNewActivityControl(t *testing.T) { +func TemporarilyDisabledTestNewActivityControl(t *testing.T) { testCases := []struct { name string From 59833911e279ece381cc5105c227054e89082ae1 Mon Sep 17 00:00:00 2001 From: VeronikaSolovei9 Date: Fri, 30 Jun 2023 15:28:47 -0700 Subject: [PATCH 14/14] Minor tweaks --- config/config.go | 2 +- endpoints/openrtb2/auction.go | 6 ++++-- privacy/enforcer.go | 21 ++++++++++----------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/config/config.go b/config/config.go index f825f846e52..650cac86449 100644 --- a/config/config.go +++ b/config/config.go @@ -156,7 +156,7 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { } if cfg.AccountDefaults.Privacy != nil { - errs = append(errs, errors.New("account_defaults.Privacy has no effect as the feature is under development.")) + glog.Warning("account_defaults.Privacy has no effect as the feature is under development.") } errs = cfg.Experiment.validate(errs) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 0dddb1940b7..3f01967a4c2 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -195,8 +195,10 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http activities, activitiesErr := privacy.NewActivityControl(account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) - writeError(errL, w, &labels) - return + if errortypes.ContainsFatalError(errL) { + writeError(errL, w, &labels) + return + } } ctx := context.Background() diff --git a/privacy/enforcer.go b/privacy/enforcer.go index 242c077d91a..d63cd8de31f 100644 --- a/privacy/enforcer.go +++ b/privacy/enforcer.go @@ -1,9 +1,9 @@ package privacy import ( - "errors" "fmt" "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/errortypes" "strings" ) @@ -28,14 +28,14 @@ type ActivityControl struct { } func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, error) { - ac := ActivityControl{plans: nil} + ac := ActivityControl{} var err error if privacyConf == nil { return ac, err } else { //temporarily disable Activities if they are specified at the account level - return ac, errors.New("account.Privacy has no effect as the feature is under development.") + return ac, &errortypes.Warning{Message: "account.Privacy has no effect as the feature is under development."} } plans := make(map[Activity]ActivityPlan) @@ -170,28 +170,27 @@ func (r ComponentEnforcementRule) Allow(target ScopedName) ActivityResult { return ActivityAbstain } - componentNameFound := true + nameClauseExists := len(r.componentName) > 0 + typeClauseExists := len(r.componentType) > 0 + + componentNameFound := false for _, scope := range r.componentName { if strings.EqualFold(scope.Scope, target.Scope) && (scope.Name == "*" || strings.EqualFold(scope.Name, target.Name)) { componentNameFound = true break - } else { - componentNameFound = false } } - typeFound := true + componentTypeFound := false for _, componentType := range r.componentType { if strings.EqualFold(componentType, target.Scope) { - typeFound = true + componentTypeFound = true break - } else { - typeFound = false } } // behavior if rule matches: can be either true=allow or false=deny. result is abstain if the rule doesn't match - matchFound := componentNameFound && typeFound + matchFound := (componentNameFound || !nameClauseExists) && (componentTypeFound || !typeClauseExists) if matchFound { if r.allowed { return ActivityAllow