diff --git a/config/config.go b/config/config.go index b1516337d9e..d1e072e6dd8 100644 --- a/config/config.go +++ b/config/config.go @@ -119,9 +119,11 @@ func (cfg *AuctionTimeouts) LimitAuctionTimeout(requested time.Duration) time.Du } type GDPR struct { - HostVendorID int `mapstructure:"host_vendor_id"` - UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` - Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` + HostVendorID int `mapstructure:"host_vendor_id"` + UsersyncIfAmbiguous bool `mapstructure:"usersync_if_ambiguous"` + Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"` + NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"` + NonStandardPublisherMap map[string]int } func (cfg *GDPR) validate(errs configErrors) configErrors { @@ -384,6 +386,13 @@ func New(v *viper.Viper) (*Configuration, error) { if errs := c.validate(); len(errs) > 0 { return &c, errs } + + // To look for a request's publisher_id into the NonStandardPublishers in + // O(1) time, we fill this hash table located in the NonStandardPublisherMap field of GDPR + c.GDPR.NonStandardPublisherMap = make(map[string]int) + for i := 0; i < len(c.GDPR.NonStandardPublishers); i++ { + c.GDPR.NonStandardPublisherMap[c.GDPR.NonStandardPublishers[i]] = 1 + } return &c, nil } @@ -599,6 +608,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("gdpr.usersync_if_ambiguous", false) v.SetDefault("gdpr.timeouts_ms.init_vendorlist_fetches", 0) v.SetDefault("gdpr.timeouts_ms.active_vendorlist_fetch", 0) + v.SetDefault("gdpr.non_standard_publishers", []string{""}) v.SetDefault("currency_converter.fetch_url", "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json") v.SetDefault("currency_converter.fetch_interval_seconds", 0) // #280 Not activated for the time being v.SetDefault("default_request.type", "") diff --git a/config/config_test.go b/config/config_test.go index 89cb7f8f203..8529fda316a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -30,6 +30,7 @@ var fullConfig = []byte(` gdpr: host_vendor_id: 15 usersync_if_ambiguous: true + non_standard_publishers: ["siteID","fake-site-id","appID","agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA"] host_cookie: cookie_name: userid family: prebid @@ -157,6 +158,22 @@ func TestFullConfig(t *testing.T) { cmpInts(t, "http_client.idle_connection_timeout_seconds", cfg.Client.IdleConnTimeout, 30) cmpInts(t, "gdpr.host_vendor_id", cfg.GDPR.HostVendorID, 15) cmpBools(t, "gdpr.usersync_if_ambiguous", cfg.GDPR.UsersyncIfAmbiguous, true) + + //Assert the NonStandardPublishers was correctly unmarshalled + cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[0], "siteID") + cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[1], "fake-site-id") + cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[2], "appID") + cmpStrings(t, "gdpr.non_standard_publishers", cfg.GDPR.NonStandardPublishers[3], "agltb3B1Yi1pbmNyDAsSA0FwcBiJkfIUDA") + + //Assert the NonStandardPublisherMap hash table was built correctly + var found bool + for i := 0; i < len(cfg.GDPR.NonStandardPublishers); i++ { + _, found = cfg.GDPR.NonStandardPublisherMap[cfg.GDPR.NonStandardPublishers[i]] + cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, true) + } + _, found = cfg.GDPR.NonStandardPublisherMap["appnexus"] + cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false) + cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "recaptcha_secret", cfg.RecaptchaSecret, "asdfasdfasdfasdf") diff --git a/endpoints/auction_test.go b/endpoints/auction_test.go index 70963c91914..bf296c379bf 100644 --- a/endpoints/auction_test.go +++ b/endpoints/auction_test.go @@ -409,7 +409,7 @@ func (m *auctionMockPermissions) BidderSyncAllowed(ctx context.Context, bidder o return m.allowBidderSync, nil } -func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (m *auctionMockPermissions) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { return m.allowPI, nil } diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 0d7d28d1146..8f126bb9444 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -198,6 +198,6 @@ func (g *gdprPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.Bi return ok, nil } -func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (g *gdprPerms) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { return true, nil } diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go index df664c3833c..03b4b6c21ef 100644 --- a/endpoints/setuid_test.go +++ b/endpoints/setuid_test.go @@ -193,6 +193,6 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { return g.allowPI, nil } diff --git a/exchange/utils.go b/exchange/utils.go index 8b7173390b1..d5c8924a287 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -45,7 +45,16 @@ func cleanOpenRTBRequests(ctx context.Context, for bidder, bidReq := range requestsByBidder { // Fixes #820 coreBidder := resolveBidder(bidder.String(), aliases) - if ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, consent); !ok && err == nil { + + var publisherID string + if bidReq.Site != nil && bidReq.Site.Publisher != nil && bidReq.Site.Publisher.ID != "" { + publisherID = bidReq.Site.Publisher.ID + } else if bidReq.App != nil && bidReq.App.Publisher != nil { + publisherID = bidReq.App.Publisher.ID + } else { + publisherID = "" + } + if ok, err := gDPR.PersonalInfoAllowed(ctx, coreBidder, publisherID, consent); !ok && err == nil { cleanPI(bidReq, labels.RType == pbsmetrics.ReqTypeAMP) } } diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 7bae4feacf2..91e4f0fb417 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -24,7 +24,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return true, nil } -func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (p *permissionsMock) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { if bidder == "appnexus" { return true, nil } diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go index 56b90e1786b..bdba008a77a 100644 --- a/gdpr/gdpr.go +++ b/gdpr/gdpr.go @@ -22,7 +22,7 @@ type Permissions interface { // Determines whether or not to send PI information to a bidder, or mask it out. // // If the consent string was nonsenical, the returned error will be an ErrorMalformedConsent. - PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) + PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) } // NewPermissions gets an instance of the Permissions for use elsewhere in the project. diff --git a/gdpr/impl.go b/gdpr/impl.go index 424e1eff1eb..2fe6a67e99f 100644 --- a/gdpr/impl.go +++ b/gdpr/impl.go @@ -38,7 +38,12 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ return false, nil } -func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (p *permissionsImpl) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { + _, ok := p.cfg.NonStandardPublisherMap[PublisherID] + if ok { + return true, nil + } + id, ok := p.vendorIDs[bidder] if ok { return p.allowPI(ctx, id, consent) @@ -125,6 +130,6 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B return true, nil } -func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, consent string) (bool, error) { +func (a AlwaysAllow) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) { return true, nil } diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go index 001fefc5e34..a1d4af3346d 100644 --- a/gdpr/impl_test.go +++ b/gdpr/impl_test.go @@ -175,11 +175,17 @@ func TestAllowPersonalInfo(t *testing.T) { } // PI needs both purposes to succeed - allowPI, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, err := perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, false, allowPI) - allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderPubmatic, "", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") + assertNilErr(t, err) + assertBoolsEqual(t, true, allowPI) + + // Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array + perms.cfg.NonStandardPublisherMap = map[string]int{"appNexusAppID": 1} + allowPI, err = perms.PersonalInfoAllowed(context.Background(), openrtb_ext.BidderAppnexus, "appNexusAppID", "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA") assertNilErr(t, err) assertBoolsEqual(t, true, allowPI) }