Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default TCF1 GVL in anticipation of IAB no longer hosting the v1 GVL #1433

Merged
merged 3 commits into from
Aug 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ type GDPR struct {
Timeouts GDPRTimeouts `mapstructure:"timeouts_ms"`
NonStandardPublishers []string `mapstructure:"non_standard_publishers,flow"`
NonStandardPublisherMap map[string]int
TCF1 TCF1 `mapstructure:"tcf1"`
TCF2 TCF2 `mapstructure:"tcf2"`
AMPException bool `mapstructure:"amp_exception"`
}
Expand All @@ -180,6 +181,12 @@ func (t *GDPRTimeouts) ActiveTimeout() time.Duration {
return time.Duration(t.ActiveVendorlistFetch) * time.Millisecond
}

// TCF1 defines the TCF1 specific configurations for GDPR
type TCF1 struct {
FetchGVL bool `mapstructure:"fetch_gvl"`
FallbackGVLPath string `mapstructure:"fallback_gvl_path"`
}

// TCF2 defines the TCF2 specific configurations for GDPR
type TCF2 struct {
Enabled bool `mapstructure:"enabled"`
Expand Down Expand Up @@ -885,6 +892,8 @@ func SetupViper(v *viper.Viper, filename string) {
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("gdpr.tcf1.fetch_gvl", true)
v.SetDefault("gdpr.tcf1.fallback_gvl_path", "./static/tcf1/fallback_gvl.json")
v.SetDefault("gdpr.tcf2.enabled", true)
v.SetDefault("gdpr.tcf2.purpose1.enabled", true)
v.SetDefault("gdpr.tcf2.purpose2.enabled", true)
Expand Down
33 changes: 30 additions & 3 deletions gdpr/vendorlist-fetching.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,20 @@ type saveVendors func(uint16, api.VendorList)
// Nothing in this file is exported. Public APIs can be found in gdpr.go

func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16, uint8) string, TCFVer uint8) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
var fallbackVL api.VendorList = nil

if TCFVer == tCF1 && len(cfg.TCF1.FallbackGVLPath) > 0 {
fallbackVL = loadFallbackGVL(cfg.TCF1.FallbackGVLPath)
}

// If we are not going to try fetching the GVL dynamically, we have a simple fetcher
if !cfg.TCF1.FetchGVL && TCFVer == tCF1 && fallbackVL != nil {
return func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
return fallbackVL, nil
}
}
// These save and load functions can be used to store & retrieve lists from our cache.
save, load := newVendorListCache()
save, load := newVendorListCache(fallbackVL)

withTimeout, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
defer cancel()
Expand All @@ -46,6 +58,9 @@ func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http
if list != nil {
return list, nil
}
if fallbackVL != nil {
return fallbackVL, nil
}
return nil, fmt.Errorf("gdpr vendor list version %d does not exist, or has not been loaded yet. Try again in a few minutes", id)
}
}
Expand Down Expand Up @@ -132,7 +147,7 @@ func saveOne(ctx context.Context, client *http.Client, url string, saver saveVen
return newList.Version()
}

func newVendorListCache() (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) {
func newVendorListCache(fallbackVL api.VendorList) (save func(id uint16, list api.VendorList), load func(id uint16) api.VendorList) {
cache := &sync.Map{}

save = func(id uint16, list api.VendorList) {
Expand All @@ -143,7 +158,19 @@ func newVendorListCache() (save func(id uint16, list api.VendorList), load func(
if ok {
return list.(vendorlist.VendorList)
}
return nil
return fallbackVL
}
return
}

func loadFallbackGVL(fallbackGVLPath string) vendorlist.VendorList {
fallbackVLbody, err := ioutil.ReadFile(fallbackGVLPath)
if err != nil {
glog.Fatalf("Error reading from file %s: %v", fallbackGVLPath, err)
}
fallbackVL, err := vendorlist.ParseEagerly(fallbackVLbody)
if err != nil {
glog.Fatalf("Error processing default GVL from %s: %v", fallbackGVLPath, err)
}
return fallbackVL
}
97 changes: 97 additions & 0 deletions gdpr/vendorlist-fetching_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"testing"
"time"

"github.com/stretchr/testify/assert"

"github.com/prebid/prebid-server/config"
)

Expand Down Expand Up @@ -139,6 +141,101 @@ func TestVendorListMaker(t *testing.T) {
assertStringsEqual(t, "https://vendorlist.consensu.org/v2/archives/vendor-list-v7.json", vendorListURLMaker(7, 2))
}

func TestDefaultVendorList(t *testing.T) {
SyntaxNode marked this conversation as resolved.
Show resolved Hide resolved
firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
32: {
purposes: []int{1, 2},
},
})
secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
12: {
purposes: []int{2},
},
})
server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{
1: firstVendorList,
2: secondVendorList,
})))
defer server.Close()

testcfg := testConfig()
testcfg.TCF1.FetchGVL = true
testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)

list, err := fetcher(context.Background(), 12)
assert.NoError(t, err, "Error with fetching default vendorlist: %v", err)
assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version())

// Testing that we got the default vendorlist data, and not the version off the server.
vendor := list.Vendor(12)
assert.Equal(t, true, vendor.Purpose(1))
assert.Equal(t, false, vendor.Purpose(2))
}

func TestDefaultVendorListPassthrough(t *testing.T) {
firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
32: {
purposes: []int{1, 2},
},
})
secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
12: {
purposes: []int{2},
},
})
server := httptest.NewServer(http.HandlerFunc(mockServer(2, map[int]string{
1: firstVendorList,
2: secondVendorList,
})))
defer server.Close()

testcfg := testConfig()
testcfg.TCF1.FetchGVL = true
testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)
list, err := fetcher(context.Background(), 2)
assert.NoError(t, err, "Error with fetching existing vendorlist: %v", err)
assert.Equal(t, uint16(2), list.Version(), "Expected to fetch mock list version 2, got version %d", list.Version())

// Testing that we got the testing vendorlist data, and not the default.
vendor := list.Vendor(12)
assert.Equal(t, false, vendor.Purpose(1))
assert.Equal(t, true, vendor.Purpose(2))
}

func TestDefaultVendorListNoFetch(t *testing.T) {
firstVendorList := mockVendorListData(t, 1, map[uint16]*purposes{
32: {
purposes: []int{1, 2},
},
})
secondVendorList := mockVendorListData(t, 2, map[uint16]*purposes{
12: {
purposes: []int{2},
},
})
server := httptest.NewServer(http.HandlerFunc(mockServer(1, map[int]string{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we please use a var to hold the list version that we use here when creating the mockServer (for this and the tests above) and then later to assert that same version? It just helps understand better what those integer values represent while reading the tests.

1: firstVendorList,
2: secondVendorList,
})))
defer server.Close()

testcfg := testConfig()
testcfg.TCF1.FetchGVL = false
testcfg.TCF1.FallbackGVLPath = "../static/tcf1/fallback_gvl.json"
fetcher := newVendorListFetcher(context.Background(), testcfg, server.Client(), testURLMaker(server), 1)
list, err := fetcher(context.Background(), 2)
assert.NoError(t, err, "Error with fetching default vendorlist: %v", err)
assert.Equal(t, uint16(214), list.Version(), "Expected to fetch default version 214, got %d", list.Version())

// Testing that we got the default vendorlist data, and not the version off the server.
vendor := list.Vendor(12)
assert.Equal(t, true, vendor.Purpose(1))
assert.Equal(t, false, vendor.Purpose(2))

}

// mockServer returns a handler which returns the given response for each global vendor list version.
// The latestVersion param can be used to mock "updates" which occur after PBS has been turned on.
// For example, if latestVersion is 3, but the responses map has data at "4", the server will return
Expand Down
1 change: 1 addition & 0 deletions static/tcf1/fallback_gvl.json

Large diffs are not rendered by default.