Skip to content
Open
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
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func Main() {
if err != nil {
glog.Exitf("Configuration could not be loaded or did not pass validation: %v", err)
}
main_ow(cfg)
main_ow(cfg)

// Create a soft memory limit on the total amount of memory that PBS uses to tune the behavior
// of the Go garbage collector. In summary, `cfg.GarbageCollectorThreshold` serves as a fixed cost
Expand Down
14 changes: 14 additions & 0 deletions modules/pubmatic/openwrap/beforevalidationhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,20 @@ func (m OpenWrap) handleBeforeValidationHook(
return result, errors.New("invalid profile data")
}

// Country filter
if shouldApplyCountryFilter(rCtx.Endpoint) {
country := m.getCountryFromContext(rCtx)
if country != "" {
mode, countryCodes := getCountryFilterConfig(partnerConfigMap)
if !isCountryAllowed(country, mode, countryCodes) {
result.Reject = true
result.NbrCode = int(nbr.RequestBlockedGeoFiltered)
result.Errors = append(result.Errors, "Request rejected due to country filter")
return result, nil
}
}
}

if rCtx.IsCTVRequest && rCtx.Endpoint == models.EndpointJson {
if len(rCtx.ResponseFormat) > 0 {
if rCtx.ResponseFormat != models.ResponseFormatJSON && rCtx.ResponseFormat != models.ResponseFormatRedirect {
Expand Down
46 changes: 46 additions & 0 deletions modules/pubmatic/openwrap/country_filtering.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package openwrap

import (
"strings"

"github.com/prebid/prebid-server/v3/modules/pubmatic/openwrap/models"
)

func (m *OpenWrap) getCountryFromContext(rCtx models.RequestCtx) string {
if len(rCtx.DeviceCtx.Country) == 2 {
return rCtx.DeviceCtx.Country
}

if rCtx.DeviceCtx.IP != "" {
code, _ := m.getCountryCodes(rCtx.DeviceCtx.IP)
return code
}
return ""
}

func getCountryFilterConfig(partnerConfigMap map[int]map[string]string) (mode string, countryCodes string) {
mode = models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.CountryFilterModeKey)
if mode == "" {
return "", ""
}

countryCodes = models.GetVersionLevelPropertyFromPartnerConfig(partnerConfigMap, models.CountryCodesKey)
return mode, countryCodes

}

func isCountryAllowed(country string, mode string, countryCodes string) bool {
if mode == "" || countryCodes == "" {
return true
}

found := strings.Contains(countryCodes, country)

// For allowlist (mode "1"), return true if country is found
// For blocklist (mode "0"), return true if country is not found
return (mode == "1" && found) || (mode == "0" && !found)
}

func shouldApplyCountryFilter(endpoint string) bool {
return endpoint == models.EndpointAppLovinMax
}
244 changes: 244 additions & 0 deletions modules/pubmatic/openwrap/country_filtering_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package openwrap

import (
"testing"

"github.com/golang/mock/gomock"
"github.com/prebid/prebid-server/v3/modules/pubmatic/openwrap/geodb"
mock_geodb "github.com/prebid/prebid-server/v3/modules/pubmatic/openwrap/geodb/mock"
"github.com/prebid/prebid-server/v3/modules/pubmatic/openwrap/models"
"github.com/stretchr/testify/assert"
)

func TestShouldApplyCountryFilter(t *testing.T) {
tests := []struct {
name string
endpoint string
want bool
}{
{
name: "EndpointAppLovinMax",
endpoint: models.EndpointAppLovinMax,
want: true,
},
{
name: "EndpointV25",
endpoint: models.EndpointV25,
want: false,
},
{
name: "EndpointJson",
endpoint: models.EndpointJson,
want: false,
},
{
name: "EndpointHybrid",
endpoint: models.EndpointHybrid,
want: false,
},
{
name: "Empty endpoint",
endpoint: "",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := shouldApplyCountryFilter(tt.endpoint)
assert.Equal(t, tt.want, got)
})
}
}

func TestGetCountryFilterConfig(t *testing.T) {
tests := []struct {
name string
partnerConfigMap map[int]map[string]string
wantMode string
wantCodes string
}{
{
name: "Config exists",
partnerConfigMap: map[int]map[string]string{
models.VersionLevelConfigID: {
models.CountryFilterModeKey: "1",
models.CountryCodesKey: "[\"US\",\"UK\",\"IN\"]",
},
},
wantMode: "1",
wantCodes: "[\"US\",\"UK\",\"IN\"]",
},
{
name: "Empty config",
partnerConfigMap: map[int]map[string]string{
models.VersionLevelConfigID: {},
},
wantMode: "",
wantCodes: "",
},
{
name: "Nil config",
partnerConfigMap: nil,
wantMode: "",
wantCodes: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mode, codes := getCountryFilterConfig(tt.partnerConfigMap)
assert.Equal(t, tt.wantMode, mode)
assert.Equal(t, tt.wantCodes, codes)
})
}
}

func TestIsCountryAllowed(t *testing.T) {
tests := []struct {
name string
country string
mode string
countryCodes string
want bool
}{
{
name: "include_mode_country_in_list",
country: "US",
mode: "1",
countryCodes: "US,UK,IN",
want: true,
},
{
name: "include_mode_country_not_in_list",
country: "FR",
mode: "1",
countryCodes: "US,UK,IN",
want: false,
},
{
name: "exclude_mode_country_in_list",
country: "US",
mode: "0",
countryCodes: "US,UK,IN",
want: false,
},
{
name: "exclude_mode_country_not_in_list",
country: "FR",
mode: "0",
countryCodes: "US,UK,IN",
want: true,
},
{
name: "empty_mode",
country: "US",
mode: "",
countryCodes: "US,UK,IN",
want: true,
},
{
name: "empty_country_codes",
country: "US",
mode: "1",
countryCodes: "",
want: true,
},
{
name: "empty_country",
country: "",
mode: "1",
countryCodes: "US,UK,IN",
want: true,
},
{
name: "invalid_mode",
country: "US",
mode: "invalid",
countryCodes: "US,UK,IN",
want: false,
},
{
name: "case_insensitive_country_match",
country: "us",
mode: "1",
countryCodes: "US,UK,IN",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isCountryAllowed(tt.country, tt.mode, tt.countryCodes)
assert.Equal(t, tt.want, got)
})
}
}

func TestGetCountryFromContext(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockGeoDb := mock_geodb.NewMockGeography(ctrl)

tests := []struct {
name string
rCtx models.RequestCtx
setup func()
want string
}{
{
name: "getting_country_from_request",
rCtx: models.RequestCtx{
DeviceCtx: models.DeviceCtx{
Country: "US",
},
},
want: "US",
},
{
name: "detecting_country_from_request_ip",
rCtx: models.RequestCtx{
DeviceCtx: models.DeviceCtx{
IP: "101.143.255.255",
},
},
setup: func() {
mockGeoDb.EXPECT().LookUp("101.143.255.255").Return(&geodb.GeoInfo{
CountryCode: "jp", ISOCountryCode: "JP", RegionCode: "13", City: "tokyo", PostalCode: "", DmaCode: 392001, Latitude: 35.68000030517578, Longitude: 139.75, AreaCode: "", AlphaThreeCountryCode: "JPN",
}, nil)
},
want: "JP",
},
{
name: "empty_country_present_in_device_ctx",
rCtx: models.RequestCtx{
DeviceCtx: models.DeviceCtx{
Country: "",
},
},
want: "",
},
{
name: "empty_ip_present_in_device_ctx",
rCtx: models.RequestCtx{
DeviceCtx: models.DeviceCtx{
Country: "",
},
},
want: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ow := OpenWrap{
geoInfoFetcher: mockGeoDb,
}
if tt.setup != nil {
tt.setup()
}
got := ow.getCountryFromContext(tt.rCtx)
assert.Equal(t, tt.want, got)
})
}
}
2 changes: 2 additions & 0 deletions modules/pubmatic/openwrap/models/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
PriceGranularityKey = "priceGranularity"
VideoAdDurationKey = "videoAdDuration"
VideoAdDurationMatchingKey = "videoAdDurationMatching"
CountryFilterModeKey = "countryFilterMode"
CountryCodesKey = "countryCodes"
REVSHARE = "rev_share"
THROTTLE = "throttle"
REFRESH_INTERVAL = "refreshInterval"
Expand Down
2 changes: 2 additions & 0 deletions modules/pubmatic/openwrap/models/nbr/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
RequestBlockedPartnerFiltered openrtb3.NoBidReason = 505
LossBidLostInVastUnwrap openrtb3.NoBidReason = 506
LossBidLostInVastVersionValidation openrtb3.NoBidReason = 507
RequestBlockedGeoFiltered openrtb3.NoBidReason = 508
)

// Openwrap module specific codes
Expand All @@ -35,4 +36,5 @@ const (
InvalidResponseFormat openrtb3.NoBidReason = 618
MissingOWRedirectURL openrtb3.NoBidReason = 619
ResponseRejectedDSA openrtb3.NoBidReason = 620 // Response Rejected - DSA
InvalidRequestCountryFiltered openrtb3.NoBidReason = 621
)
Loading