diff --git a/account/account.go b/account/account.go index 052d01994ce..156ad5e6d39 100644 --- a/account/account.go +++ b/account/account.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/buger/jsonparser" "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/config" @@ -12,6 +11,7 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/iputil" jsonpatch "gopkg.in/evanphx/json-patch.v4" ) @@ -103,6 +103,14 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r return nil, errs } + if ipV6Err := account.Privacy.IPv6Config.Validate(nil); len(ipV6Err) > 0 { + account.Privacy.IPv6Config.AnonKeepBits = iputil.IPv6DefaultMaskingBitSize + } + + if ipV4Err := account.Privacy.IPv4Config.Validate(nil); len(ipV4Err) > 0 { + account.Privacy.IPv4Config.AnonKeepBits = iputil.IPv4DefaultMaskingBitSize + } + // set the value of events.enabled field based on deprecated events_enabled field and ensure backward compatibility deprecateEventsEnabledField(account) diff --git a/account/account_test.go b/account/account_test.go index 4bac0894577..d2694abc5a1 100644 --- a/account/account_test.go +++ b/account/account_test.go @@ -12,17 +12,19 @@ import ( "github.com/prebid/prebid-server/metrics" "github.com/prebid/prebid-server/openrtb_ext" "github.com/prebid/prebid-server/stored_requests" + "github.com/prebid/prebid-server/util/iputil" "github.com/prebid/prebid-server/util/ptrutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) var mockAccountData = map[string]json.RawMessage{ - "valid_acct": json.RawMessage(`{"disabled":false}`), - "disabled_acct": json.RawMessage(`{"disabled":true}`), - "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), - "gdpr_channel_enabled_acct": json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`), - "ccpa_channel_enabled_acct": json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`), + "valid_acct": json.RawMessage(`{"disabled":false}`), + "invalid_acct_ipv6_ipv4": json.RawMessage(`{"disabled":false, "privacy": {"ipv6": {"anon_keep_bits": -32}, "ipv4": {"anon_keep_bits": -16}}}`), + "disabled_acct": json.RawMessage(`{"disabled":true}`), + "malformed_acct": json.RawMessage(`{"disabled":"invalid type"}`), + "gdpr_channel_enabled_acct": json.RawMessage(`{"disabled":false,"gdpr":{"channel_enabled":{"amp":true}}}`), + "ccpa_channel_enabled_acct": json.RawMessage(`{"disabled":false,"ccpa":{"channel_enabled":{"amp":true}}}`), "gdpr_channel_enabled_deprecated_purpose_acct": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}, "channel_enabled":{"amp":true}}}`), "gdpr_deprecated_purpose1": json.RawMessage(`{"disabled":false,"gdpr":{"purpose1":{"enforce_purpose":"full"}}}`), "gdpr_deprecated_purpose2": json.RawMessage(`{"disabled":false,"gdpr":{"purpose2":{"enforce_purpose":"full"}}}`), @@ -54,6 +56,8 @@ func TestGetAccount(t *testing.T) { required bool // account_defaults.disabled disabled bool + // checkDefaultIP indicates IPv6 and IPv6 should be set to default values + checkDefaultIP bool // expected error, or nil if account should be found err error }{ @@ -78,6 +82,8 @@ func TestGetAccount(t *testing.T) { {accountID: "valid_acct", required: false, disabled: true, err: nil}, {accountID: "valid_acct", required: true, disabled: true, err: nil}, + {accountID: "invalid_acct_ipv6_ipv4", required: true, disabled: false, err: nil, checkDefaultIP: true}, + // pubID given and matches a host account explicitly disabled (Disabled: true on account json) {accountID: "disabled_acct", required: false, disabled: false, err: &errortypes.BlacklistedAcct{}}, {accountID: "disabled_acct", required: true, disabled: false, err: &errortypes.BlacklistedAcct{}}, @@ -129,6 +135,10 @@ func TestGetAccount(t *testing.T) { assert.Nil(t, account, "return account must be nil on error") assert.IsType(t, test.err, errors[0], "error is of unexpected type") } + if test.checkDefaultIP { + assert.Equal(t, account.Privacy.IPv6Config.AnonKeepBits, iputil.IPv6DefaultMaskingBitSize, "ipv6 should be set to default value") + assert.Equal(t, account.Privacy.IPv4Config.AnonKeepBits, iputil.IPv4DefaultMaskingBitSize, "ipv4 should be set to default value") + } }) } } diff --git a/config/account.go b/config/account.go index 020402114de..038a8daaf9e 100644 --- a/config/account.go +++ b/config/account.go @@ -8,6 +8,7 @@ import ( "github.com/prebid/go-gdpr/consentconstants" "github.com/prebid/prebid-server/openrtb_ext" + "github.com/prebid/prebid-server/util/iputil" ) // ChannelType enumerates the values of integrations Prebid Server can configure for an account @@ -40,7 +41,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. @@ -295,5 +296,31 @@ func (a *AccountChannel) IsSet() bool { } type AccountPrivacy struct { - AllowActivities AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` + AllowActivities *AllowActivities `mapstructure:"allowactivities" json:"allowactivities"` + IPv6Config IPv6 `mapstructure:"ipv6" json:"ipv6"` + IPv4Config IPv4 `mapstructure:"ipv4" json:"ipv4"` +} + +type IPv6 struct { + AnonKeepBits int `mapstructure:"anon_keep_bits" json:"anon_keep_bits"` +} + +type IPv4 struct { + AnonKeepBits int `mapstructure:"anon_keep_bits" json:"anon_keep_bits"` +} + +func (ip *IPv6) Validate(errs []error) []error { + if ip.AnonKeepBits > iputil.IPv6BitSize || ip.AnonKeepBits < 0 { + err := fmt.Errorf("bits cannot exceed %d in ipv6 address, or be less than 0", iputil.IPv6BitSize) + errs = append(errs, err) + } + return errs +} + +func (ip *IPv4) Validate(errs []error) []error { + if ip.AnonKeepBits > iputil.IPv4BitSize || ip.AnonKeepBits < 0 { + err := fmt.Errorf("bits cannot exceed %d in ipv4 address, or be less than 0", iputil.IPv4BitSize) + errs = append(errs, err) + } + return errs } diff --git a/config/account_test.go b/config/account_test.go index 20c6053246e..31d9796a622 100644 --- a/config/account_test.go +++ b/config/account_test.go @@ -910,3 +910,49 @@ func TestAccountPriceFloorsValidate(t *testing.T) { }) } } + +func TestIPMaskingValidate(t *testing.T) { + tests := []struct { + name string + privacy AccountPrivacy + want []error + }{ + { + name: "valid", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: 1}, + IPv6Config: IPv6{AnonKeepBits: 0}, + }, + }, + { + name: "invalid", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: -100}, + IPv6Config: IPv6{AnonKeepBits: -200}, + }, + want: []error{ + errors.New("bits cannot exceed 32 in ipv4 address, or be less than 0"), + errors.New("bits cannot exceed 128 in ipv6 address, or be less than 0"), + }, + }, + { + name: "mixed", + privacy: AccountPrivacy{ + IPv4Config: IPv4{AnonKeepBits: 10}, + IPv6Config: IPv6{AnonKeepBits: -10}, + }, + want: []error{ + errors.New("bits cannot exceed 128 in ipv6 address, or be less than 0"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var errs []error + errs = tt.privacy.IPv4Config.Validate(errs) + errs = tt.privacy.IPv6Config.Validate(errs) + assert.ElementsMatch(t, errs, tt.want) + }) + } +} diff --git a/config/config.go b/config/config.go index 2b539c578d5..e564c34ae3a 100644 --- a/config/config.go +++ b/config/config.go @@ -149,12 +149,11 @@ func (cfg *Configuration) validate(v *viper.Viper) []error { errs = append(errs, errors.New("account_defaults.Events.VASTEvents has no effect as the feature is under development.")) } - if cfg.AccountDefaults.Privacy != nil { - glog.Warning("account_defaults.Privacy has no effect as the feature is under development.") - } - errs = cfg.Experiment.validate(errs) errs = cfg.BidderInfos.validate(errs) + errs = cfg.AccountDefaults.Privacy.IPv6Config.Validate(errs) + errs = cfg.AccountDefaults.Privacy.IPv4Config.Validate(errs) + return errs } @@ -1019,6 +1018,8 @@ func SetupViper(v *viper.Viper, filename string, bidderInfos BidderInfos) { v.SetDefault("account_defaults.price_floors.max_rules", 100) v.SetDefault("account_defaults.price_floors.max_schema_dims", 3) v.SetDefault("account_defaults.events_enabled", false) + v.SetDefault("account_defaults.privacy.ipv6.anon_keep_bits", 56) + v.SetDefault("account_defaults.privacy.ipv4.anon_keep_bits", 24) v.SetDefault("compression.response.enable_gzip", false) v.SetDefault("compression.request.enable_gzip", false) diff --git a/config/config_test.go b/config/config_test.go index a35891babd3..057ed06ecc4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -206,6 +206,9 @@ func TestDefaults(t *testing.T) { cmpUnsignedInts(t, "tmax_adjustments.bidder_network_latency_buffer_ms", 0, cfg.TmaxAdjustments.BidderNetworkLatencyBuffer) cmpUnsignedInts(t, "tmax_adjustments.pbs_response_preparation_duration_ms", 0, cfg.TmaxAdjustments.PBSResponsePreparationDuration) + cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 56, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) + cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 24, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) + //Assert purpose VendorExceptionMap hash tables were built correctly expectedTCF2 := TCF2{ Enabled: true, @@ -477,6 +480,11 @@ account_defaults: use_dynamic_data: true max_rules: 120 max_schema_dims: 5 + privacy: + ipv6: + anon_keep_bits: 50 + ipv4: + anon_keep_bits: 20 tmax_adjustments: enabled: true bidder_response_duration_min_ms: 700 @@ -583,6 +591,9 @@ func TestFullConfig(t *testing.T) { cmpBools(t, "account_defaults.events_enabled", *cfg.AccountDefaults.EventsEnabled, true) cmpNils(t, "account_defaults.events.enabled", cfg.AccountDefaults.Events.Enabled) + cmpInts(t, "account_defaults.privacy.ipv6.anon_keep_bits", 50, cfg.AccountDefaults.Privacy.IPv6Config.AnonKeepBits) + cmpInts(t, "account_defaults.privacy.ipv4.anon_keep_bits", 20, cfg.AccountDefaults.Privacy.IPv4Config.AnonKeepBits) + // Assert compression related defaults cmpBools(t, "enable_gzip", false, cfg.EnableGzip) cmpBools(t, "compression.request.enable_gzip", true, cfg.Compression.Request.GZIP) diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go index 5caa543cbea..50cb449193a 100644 --- a/endpoints/cookie_sync.go +++ b/endpoints/cookie_sync.go @@ -149,7 +149,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr } } - activityControl, activitiesErr := privacy.NewActivityControl(account.Privacy) + activityControl, activitiesErr := privacy.NewActivityControl(&account.Privacy) if activitiesErr != nil { if errortypes.ContainsFatalError([]error{activitiesErr}) { activityControl = privacy.ActivityControl{} diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go index 22ecc1faf9e..45c27f0ea31 100644 --- a/endpoints/cookie_sync_test.go +++ b/endpoints/cookie_sync_test.go @@ -2108,7 +2108,7 @@ func (p *fakePermissions) AuctionActivitiesAllowed(ctx context.Context, bidderCo func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy { return &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + AllowActivities: &config.AllowActivities{ SyncUser: config.Activity{ Default: ptrutil.ToPtr(true), Rules: []config.ActivityRule{ diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go index fb4b192824a..142f7320e27 100644 --- a/endpoints/openrtb2/amp_auction.go +++ b/endpoints/openrtb2/amp_auction.go @@ -229,7 +229,7 @@ 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) + activities, activitiesErr := privacy.NewActivityControl(&account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) writeError(errL, w, &labels) diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 6ce2c323996..b56b7fc55a4 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -193,7 +193,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) + activities, activitiesErr := privacy.NewActivityControl(&account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) if errortypes.ContainsFatalError(errL) { diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 8fc2f844c71..5f848446333 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -303,7 +303,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re return } - activities, activitiesErr := privacy.NewActivityControl(account.Privacy) + activities, activitiesErr := privacy.NewActivityControl(&account.Privacy) if activitiesErr != nil { errL = append(errL, activitiesErr) if errortypes.ContainsFatalError(errL) { diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go index a32afd24907..3c2528e2aeb 100644 --- a/endpoints/openrtb2/video_auction_test.go +++ b/endpoints/openrtb2/video_auction_test.go @@ -1299,8 +1299,8 @@ func mockDepsInvalidPrivacy(t *testing.T, ex *mockExchangeVideo) *endpointDeps { &mockAccountFetcher{data: mockVideoAccountData}, &config.Configuration{MaxRequestSize: maxSize, AccountDefaults: config.Account{ - Privacy: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + Privacy: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitPreciseGeo: config.Activity{Rules: []config.ActivityRule{ {Condition: config.ActivityCondition{ComponentName: []string{"bidderA.BidderB.bidderC"}}}, }}, diff --git a/endpoints/setuid.go b/endpoints/setuid.go index 561989e029d..30a1cce2751 100644 --- a/endpoints/setuid.go +++ b/endpoints/setuid.go @@ -104,7 +104,7 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use return } - activities, activitiesErr := privacy.NewActivityControl(account.Privacy) + activities, activitiesErr := privacy.NewActivityControl(&account.Privacy) if activitiesErr != nil { if errortypes.ContainsFatalError([]error{activitiesErr}) { activities = privacy.ActivityControl{} diff --git a/exchange/utils.go b/exchange/utils.go index baedcf78475..267d928ca72 100644 --- a/exchange/utils.go +++ b/exchange/utils.go @@ -214,7 +214,7 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context, privacyEnforcement.TID = !auctionReq.Activities.Allow(privacy.ActivityTransmitTids, scopedName) - privacyEnforcement.Apply(bidderRequest.BidRequest) + privacyEnforcement.Apply(bidderRequest.BidRequest, auctionReq.Account.Privacy) allowedBidderRequests = append(allowedBidderRequests, bidderRequest) // GPP downgrade: always downgrade unless we can confirm GPP is supported diff --git a/exchange/utils_test.go b/exchange/utils_test.go index 63ca54c2a2e..1ab86fcd9e7 100644 --- a/exchange/utils_test.go +++ b/exchange/utils_test.go @@ -4272,7 +4272,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { testCases := []struct { name string req *openrtb2.BidRequest - privacyConfig *config.AccountPrivacy + privacyConfig config.AccountPrivacy componentName string allow bool expectedReqNumber int @@ -4364,7 +4364,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) { } for _, test := range testCases { - activities, err := privacy.NewActivityControl(test.privacyConfig) + activities, err := privacy.NewActivityControl(&test.privacyConfig) assert.NoError(t, err, "") auctionReq := AuctionRequest{ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req}, @@ -4410,33 +4410,33 @@ func buildDefaultActivityConfig(componentName string, allow bool) config.Activit } } -func getFetchBidsActivityConfig(componentName string, allow bool) *config.AccountPrivacy { - return &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ +func getFetchBidsActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ FetchBids: buildDefaultActivityConfig(componentName, allow), }, } } -func getTransmitUFPDActivityConfig(componentName string, allow bool) *config.AccountPrivacy { - return &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ +func getTransmitUFPDActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitUserFPD: buildDefaultActivityConfig(componentName, allow), }, } } -func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) *config.AccountPrivacy { - return &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ +func getTransmitPreciseGeoActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitPreciseGeo: buildDefaultActivityConfig(componentName, allow), }, } } -func getTransmitTIDActivityConfig(componentName string, allow bool) *config.AccountPrivacy { - return &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ +func getTransmitTIDActivityConfig(componentName string, allow bool) config.AccountPrivacy { + return config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitTids: buildDefaultActivityConfig(componentName, allow), }, } diff --git a/privacy/activitycontrol.go b/privacy/activitycontrol.go index fee5c6508a6..5d68ce71770 100644 --- a/privacy/activitycontrol.go +++ b/privacy/activitycontrol.go @@ -22,7 +22,7 @@ func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, er ac := ActivityControl{} var err error - if privacyConf == nil { + if privacyConf == nil || privacyConf.AllowActivities == nil { return ac, nil } diff --git a/privacy/activitycontrol_test.go b/privacy/activitycontrol_test.go index 7927014a928..db5d588de44 100644 --- a/privacy/activitycontrol_test.go +++ b/privacy/activitycontrol_test.go @@ -12,20 +12,20 @@ import ( func TestNewActivityControl(t *testing.T) { testCases := []struct { name string - privacyConf *config.AccountPrivacy + privacyConf config.AccountPrivacy activityControl ActivityControl err error }{ { - name: "privacy_config_is_nil", - privacyConf: nil, + name: "privacy_config_is_empty", + privacyConf: config.AccountPrivacy{}, activityControl: ActivityControl{plans: nil}, err: nil, }, { name: "privacy_config_is_specified_and_correct", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ SyncUser: getDefaultActivityConfig(), FetchBids: getDefaultActivityConfig(), EnrichUserFPD: getDefaultActivityConfig(), @@ -50,8 +50,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_SyncUser_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ SyncUser: getIncorrectActivityConfig(), }, }, @@ -60,8 +60,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_FetchBids_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ FetchBids: getIncorrectActivityConfig(), }, }, @@ -70,8 +70,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_EnrichUserFPD_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ EnrichUserFPD: getIncorrectActivityConfig(), }, }, @@ -80,8 +80,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_ReportAnalytics_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ ReportAnalytics: getIncorrectActivityConfig(), }, }, @@ -90,8 +90,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_TransmitUserFPD_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitUserFPD: getIncorrectActivityConfig(), }, }, @@ -100,8 +100,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_TransmitPreciseGeo_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitPreciseGeo: getIncorrectActivityConfig(), }, }, @@ -110,8 +110,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_TransmitUniqueRequestIds_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitUniqueRequestIds: getIncorrectActivityConfig(), }, }, @@ -120,8 +120,8 @@ func TestNewActivityControl(t *testing.T) { }, { name: "privacy_config_is_specified_and_TransmitTids_is_incorrect", - privacyConf: &config.AccountPrivacy{ - AllowActivities: config.AllowActivities{ + privacyConf: config.AccountPrivacy{ + AllowActivities: &config.AllowActivities{ TransmitTids: getIncorrectActivityConfig(), }, }, @@ -132,7 +132,7 @@ func TestNewActivityControl(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - actualAC, actualErr := NewActivityControl(test.privacyConf) + actualAC, actualErr := NewActivityControl(&test.privacyConf) if test.err == nil { assert.Equal(t, test.activityControl, actualAC) assert.NoError(t, actualErr) diff --git a/privacy/enforcement.go b/privacy/enforcement.go index 1a66cfab929..8074d96acf3 100644 --- a/privacy/enforcement.go +++ b/privacy/enforcement.go @@ -1,6 +1,9 @@ package privacy -import "github.com/prebid/openrtb/v19/openrtb2" +import ( + "github.com/prebid/openrtb/v19/openrtb2" + "github.com/prebid/prebid-server/config" +) // Enforcement represents the privacy policies to enforce for an OpenRTB bid request. type Enforcement struct { @@ -27,8 +30,8 @@ func (e Enforcement) AnyActivities() bool { } // Apply cleans personally identifiable information from an OpenRTB bid request. -func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest) { - e.apply(bidRequest, NewScrubber()) +func (e Enforcement) Apply(bidRequest *openrtb2.BidRequest, privacy config.AccountPrivacy) { + e.apply(bidRequest, NewScrubber(privacy.IPv6Config, privacy.IPv4Config)) } func (e Enforcement) apply(bidRequest *openrtb2.BidRequest, scrubber Scrubber) { @@ -55,21 +58,16 @@ func (e Enforcement) getDeviceIDScrubStrategy() ScrubStrategyDeviceID { func (e Enforcement) getIPv4ScrubStrategy() ScrubStrategyIPV4 { if e.COPPA || e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV4Lowest8 + return ScrubStrategyIPV4Subnet } return ScrubStrategyIPV4None } func (e Enforcement) getIPv6ScrubStrategy() ScrubStrategyIPV6 { - if e.COPPA { - return ScrubStrategyIPV6Lowest32 + if e.GDPRGeo || e.CCPA || e.LMT || e.COPPA { + return ScrubStrategyIPV6Subnet } - - if e.GDPRGeo || e.CCPA || e.LMT { - return ScrubStrategyIPV6Lowest16 - } - return ScrubStrategyIPV6None } diff --git a/privacy/enforcement_test.go b/privacy/enforcement_test.go index be1ab0dca9f..a97779eb903 100644 --- a/privacy/enforcement_test.go +++ b/privacy/enforcement_test.go @@ -76,8 +76,8 @@ func TestApplyGDPR(t *testing.T) { LMT: true, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoFull, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoFull, @@ -92,8 +92,8 @@ func TestApplyGDPR(t *testing.T) { LMT: false, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoReducedPrecision, @@ -108,8 +108,8 @@ func TestApplyGDPR(t *testing.T) { LMT: false, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoFull, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoFull, @@ -124,8 +124,8 @@ func TestApplyGDPR(t *testing.T) { LMT: false, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoReducedPrecision, @@ -156,8 +156,8 @@ func TestApplyGDPR(t *testing.T) { LMT: false, }, expectedDeviceID: ScrubStrategyDeviceIDNone, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserNone, expectedUserGeo: ScrubStrategyGeoReducedPrecision, @@ -172,8 +172,8 @@ func TestApplyGDPR(t *testing.T) { LMT: true, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest16, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoReducedPrecision, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoReducedPrecision, @@ -188,8 +188,8 @@ func TestApplyGDPR(t *testing.T) { LMT: false, }, expectedDeviceID: ScrubStrategyDeviceIDAll, - expectedDeviceIPv4: ScrubStrategyIPV4Lowest8, - expectedDeviceIPv6: ScrubStrategyIPV6Lowest32, + expectedDeviceIPv4: ScrubStrategyIPV4Subnet, + expectedDeviceIPv6: ScrubStrategyIPV6Subnet, expectedDeviceGeo: ScrubStrategyGeoFull, expectedUser: ScrubStrategyUserIDAndDemographic, expectedUserGeo: ScrubStrategyGeoFull, @@ -329,7 +329,7 @@ func TestApplyToggle(t *testing.T) { m.On("ScrubUser", req.User, ScrubStrategyUserIDAndDemographic, ScrubStrategyGeoFull).Return(replacedUser).Once() } if test.expectedScrubDeviceExecuted { - m.On("ScrubDevice", req.Device, ScrubStrategyDeviceIDAll, ScrubStrategyIPV4Lowest8, ScrubStrategyIPV6Lowest32, ScrubStrategyGeoFull).Return(replacedDevice).Once() + m.On("ScrubDevice", req.Device, ScrubStrategyDeviceIDAll, ScrubStrategyIPV4Subnet, ScrubStrategyIPV6Subnet, ScrubStrategyGeoFull).Return(replacedDevice).Once() } test.enforcement.apply(req, m) diff --git a/privacy/scrubber.go b/privacy/scrubber.go index f2fb790dc12..4cc90c825df 100644 --- a/privacy/scrubber.go +++ b/privacy/scrubber.go @@ -2,8 +2,10 @@ package privacy import ( "encoding/json" + "github.com/prebid/prebid-server/config" + "github.com/prebid/prebid-server/util/iputil" "github.com/prebid/prebid-server/util/ptrutil" - "strings" + "net" "github.com/prebid/openrtb/v19/openrtb2" ) @@ -15,8 +17,8 @@ const ( // ScrubStrategyIPV4None does not remove any part of an IPV4 address. ScrubStrategyIPV4None ScrubStrategyIPV4 = iota - // ScrubStrategyIPV4Lowest8 zeroes out the last 8 bits of an IPV4 address. - ScrubStrategyIPV4Lowest8 + // ScrubStrategyIPV4Subnet zeroes out the last 8 bits of an IPV4 address. + ScrubStrategyIPV4Subnet ) // ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address. @@ -26,11 +28,8 @@ const ( // ScrubStrategyIPV6None does not remove any part of an IPV6 address. ScrubStrategyIPV6None ScrubStrategyIPV6 = iota - // ScrubStrategyIPV6Lowest16 zeroes out the last 16 bits of an IPV6 address. - ScrubStrategyIPV6Lowest16 - - // ScrubStrategyIPV6Lowest32 zeroes out the last 32 bits of an IPV6 address. - ScrubStrategyIPV6Lowest32 + // ScrubStrategyIPV6Subnet zeroes out the last 16 bits of an IPV6 sub net address. + ScrubStrategyIPV6Subnet ) // ScrubStrategyGeo defines the approach to scrub PII from geographical data. @@ -76,14 +75,20 @@ type Scrubber interface { ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User } -type scrubber struct{} +type scrubber struct { + ipV6 config.IPv6 + ipV4 config.IPv4 +} // NewScrubber returns an OpenRTB scrubber. -func NewScrubber() Scrubber { - return scrubber{} +func NewScrubber(ipV6 config.IPv6, ipV4 config.IPv4) Scrubber { + return scrubber{ + ipV6: ipV6, + ipV4: ipV4, + } } -func (scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { +func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest { var userExtParsed map[string]json.RawMessage userExtModified := false @@ -169,8 +174,8 @@ func (scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforc if deviceCopy.Geo != nil { deviceCopy.Geo = scrubGeoPrecision(deviceCopy.Geo) } - deviceCopy.IP = scrubIPV4Lowest8(deviceCopy.IP) - deviceCopy.IPv6 = scrubIPV6Lowest32Bits(deviceCopy.IPv6) + deviceCopy.IP = scrubIP(deviceCopy.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) + deviceCopy.IPv6 = scrubIP(deviceCopy.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) } } @@ -179,7 +184,7 @@ func (scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforc return bidRequest } -func (scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { +func (s scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device { if device == nil { return nil } @@ -198,15 +203,13 @@ func (scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, i } switch ipv4 { - case ScrubStrategyIPV4Lowest8: - deviceCopy.IP = scrubIPV4Lowest8(device.IP) + case ScrubStrategyIPV4Subnet: + deviceCopy.IP = scrubIP(device.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize) } switch ipv6 { - case ScrubStrategyIPV6Lowest16: - deviceCopy.IPv6 = scrubIPV6Lowest16Bits(device.IPv6) - case ScrubStrategyIPV6Lowest32: - deviceCopy.IPv6 = scrubIPV6Lowest32Bits(device.IPv6) + case ScrubStrategyIPV6Subnet: + deviceCopy.IPv6 = scrubIP(device.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize) } switch geo { @@ -244,44 +247,13 @@ func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo S return &userCopy } -func scrubIPV4Lowest8(ip string) string { - i := strings.LastIndex(ip, ".") - if i == -1 { - return "" - } - - return ip[0:i] + ".0" -} - -func scrubIPV6Lowest16Bits(ip string) string { - ip = removeLowestIPV6Segment(ip) - - if ip != "" { - ip += ":0" - } - - return ip -} - -func scrubIPV6Lowest32Bits(ip string) string { - ip = removeLowestIPV6Segment(ip) - ip = removeLowestIPV6Segment(ip) - - if ip != "" { - ip += ":0:0" - } - - return ip -} - -func removeLowestIPV6Segment(ip string) string { - i := strings.LastIndex(ip, ":") - - if i == -1 { +func scrubIP(ip string, ones, bits int) string { + if ip == "" { return "" } - - return ip[0:i] + ipMask := net.CIDRMask(ones, bits) + ipMasked := net.ParseIP(ip).Mask(ipMask) + return ipMasked.String() } func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo { diff --git a/privacy/scrubber_test.go b/privacy/scrubber_test.go index 0691b2c7a2c..59e593fc167 100644 --- a/privacy/scrubber_test.go +++ b/privacy/scrubber_test.go @@ -2,6 +2,7 @@ package privacy import ( "encoding/json" + "github.com/prebid/prebid-server/config" "testing" "github.com/prebid/openrtb/v19/openrtb2" @@ -38,12 +39,12 @@ func TestScrubDevice(t *testing.T) { MACMD5: "", IFA: "", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", + IPv6: "2001:1db8:2233:4400::", Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDAll, - ipv4: ScrubStrategyIPV4Lowest8, - ipv6: ScrubStrategyIPV6Lowest32, + ipv4: ScrubStrategyIPV4Subnet, + ipv6: ScrubStrategyIPV6Subnet, geo: ScrubStrategyGeoFull, }, { @@ -57,7 +58,7 @@ func TestScrubDevice(t *testing.T) { MACMD5: "", IFA: "", IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: device.Geo, }, id: ScrubStrategyDeviceIDAll, @@ -76,16 +77,16 @@ func TestScrubDevice(t *testing.T) { MACMD5: "anyMACMD5", IFA: "anyIFA", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: device.Geo, }, id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4Lowest8, + ipv4: ScrubStrategyIPV4Subnet, ipv6: ScrubStrategyIPV6None, geo: ScrubStrategyGeoNone, }, { - description: "Isolated - IPv6 - Lowest 16", + description: "Isolated - IPv6", expected: &openrtb2.Device{ DIDMD5: "anyDIDMD5", DIDSHA1: "anyDIDSHA1", @@ -95,31 +96,12 @@ func TestScrubDevice(t *testing.T) { MACMD5: "anyMACMD5", IFA: "anyIFA", IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:0", + IPv6: "2001:1db8:2233:4400::", Geo: device.Geo, }, id: ScrubStrategyDeviceIDNone, ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Lowest16, - geo: ScrubStrategyGeoNone, - }, - { - description: "Isolated - IPv6 - Lowest 32", - expected: &openrtb2.Device{ - DIDMD5: "anyDIDMD5", - DIDSHA1: "anyDIDSHA1", - DPIDMD5: "anyDPIDMD5", - DPIDSHA1: "anyDPIDSHA1", - MACSHA1: "anyMACSHA1", - MACMD5: "anyMACMD5", - IFA: "anyIFA", - IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", - Geo: device.Geo, - }, - id: ScrubStrategyDeviceIDNone, - ipv4: ScrubStrategyIPV4None, - ipv6: ScrubStrategyIPV6Lowest32, + ipv6: ScrubStrategyIPV6Subnet, geo: ScrubStrategyGeoNone, }, { @@ -133,7 +115,7 @@ func TestScrubDevice(t *testing.T) { MACMD5: "anyMACMD5", IFA: "anyIFA", IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, @@ -158,7 +140,7 @@ func TestScrubDevice(t *testing.T) { MACMD5: "anyMACMD5", IFA: "anyIFA", IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: &openrtb2.Geo{}, }, id: ScrubStrategyDeviceIDNone, @@ -167,15 +149,16 @@ func TestScrubDevice(t *testing.T) { geo: ScrubStrategyGeoFull, }, } - + testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber().ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) + result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(device, test.id, test.ipv4, test.ipv6, test.geo) assert.Equal(t, test.expected, result, test.description) } } func TestScrubDeviceNil(t *testing.T) { - result := NewScrubber().ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) + testIPMasking := getTestIPMasking() + result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubDevice(nil, ScrubStrategyDeviceIDNone, ScrubStrategyIPV4None, ScrubStrategyIPV6None, ScrubStrategyGeoNone) assert.Nil(t, result) } @@ -292,14 +275,16 @@ func TestScrubUser(t *testing.T) { }, } + testIPMasking := getTestIPMasking() for _, test := range testCases { - result := NewScrubber().ScrubUser(user, test.scrubUser, test.scrubGeo) + result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(user, test.scrubUser, test.scrubGeo) assert.Equal(t, test.expected, result, test.description) } } func TestScrubUserNil(t *testing.T) { - result := NewScrubber().ScrubUser(nil, ScrubStrategyUserNone, ScrubStrategyGeoNone) + testIPMasking := getTestIPMasking() + result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubUser(nil, ScrubStrategyUserNone, ScrubStrategyGeoNone) assert.Nil(t, result) } @@ -336,7 +321,7 @@ func TestScrubRequest(t *testing.T) { }, Device: &openrtb2.Device{ IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: device.Geo, }, }, @@ -354,7 +339,7 @@ func TestScrubRequest(t *testing.T) { }, Device: &openrtb2.Device{ IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: device.Geo, }, }, @@ -431,7 +416,7 @@ func TestScrubRequest(t *testing.T) { MACSHA1: "anyMACSHA1", MACMD5: "anyMACMD5", IP: "1.2.3.0", - IPv6: "2001:0db8:0000:0000:0000:ff00:0:0", + IPv6: "2001:1db8:2233:4400::", Geo: &openrtb2.Geo{ Lat: 123.46, Lon: 678.89, Metro: "some metro", @@ -443,6 +428,7 @@ func TestScrubRequest(t *testing.T) { }, } + testIPMasking := getTestIPMasking() for _, test := range testCases { t.Run(test.description, func(t *testing.T) { bidRequest := &openrtb2.BidRequest{ @@ -462,127 +448,110 @@ func TestScrubRequest(t *testing.T) { } bidRequest.User.EIDs = []openrtb2.EID{{Source: "test"}} - result := NewScrubber().ScrubRequest(bidRequest, test.enforcement) + result := NewScrubber(testIPMasking.IPv6Config, testIPMasking.IPv4Config).ScrubRequest(bidRequest, test.enforcement) assert.Equal(t, test.expected, result, test.description) }) } } -func TestScrubIPV4(t *testing.T) { +func TestScrubIP(t *testing.T) { testCases := []struct { - IP string - cleanedIP string - description string + IP string + cleanedIP string + bits int + maskBits int }{ { - IP: "0.0.0.0", - cleanedIP: "0.0.0.0", - description: "Shouldn't do anything for a 0.0.0.0 IP address", + IP: "0:0:0:0:0:0:0:0", + cleanedIP: "::", + bits: 128, + maskBits: 56, }, { - IP: "192.127.111.134", - cleanedIP: "192.127.111.0", - description: "Should remove the lowest 8 bits", + IP: "", + cleanedIP: "", + bits: 128, + maskBits: 56, }, { - IP: "192.127.111.0", - cleanedIP: "192.127.111.0", - description: "Shouldn't change anything if the lowest 8 bits are already 0", + IP: "1111:2222:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:2222:3333:4400::", + bits: 128, + maskBits: 56, }, { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + IP: "1111:2222:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:2222::", + bits: 128, + maskBits: 34, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", - }, - } - - for _, test := range testCases { - result := scrubIPV4Lowest8(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) - } -} - -func TestScrubIPV6Lowest16Bits(t *testing.T) { - testCases := []struct { - IP string - cleanedIP string - description string - }{ - { - IP: "0:0:0:0", - cleanedIP: "0:0:0:0", - description: "Shouldn't do anything for a 0:0:0:0 IP address", + IP: "1111:0:3333:4444:5555:6666:7777:8888", + cleanedIP: "1111:0:3333:4400::", + bits: 128, + maskBits: 56, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", - description: "Should remove lowest 16 bits", + IP: "1111::6666:7777:8888", + cleanedIP: "1111::", + bits: 128, + maskBits: 56, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:0", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0042:0", - description: "Shouldn't do anything if the lowest 16 bits are already 0", + IP: "2001:1db8:0000:0000:0000:ff00:0042:8329", + cleanedIP: "2001:1db8::ff00:0:0", + bits: 128, + maskBits: 96, }, { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + IP: "2001:1db8:0000:0000:0000:ff00:0:0", + cleanedIP: "2001:1db8::ff00:0:0", + bits: 128, + maskBits: 96, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + IP: "2001:1db8:0000:0000:0000:ff00:0042:8329", + cleanedIP: "2001:1db8::ff00:42:0", + bits: 128, + maskBits: 112, }, - } - - for _, test := range testCases { - result := scrubIPV6Lowest16Bits(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) - } -} - -func TestScrubIPV6Lowest32Bits(t *testing.T) { - testCases := []struct { - IP string - cleanedIP string - description string - }{ { - IP: "0:0:0:0", - cleanedIP: "0:0:0:0", - description: "Shouldn't do anything for a 0:0:0:0 IP address", + IP: "2001:1db8:0000:0000:0000:ff00:0042:0", + cleanedIP: "2001:1db8::ff00:42:0", + bits: 128, + maskBits: 112, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0042:8329", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", - description: "Should remove lowest 32 bits", + IP: "127.0.0.1", + cleanedIP: "127.0.0.0", + bits: 32, + maskBits: 24, }, { - IP: "2001:0db8:0000:0000:0000:ff00:0:0", - cleanedIP: "2001:0db8:0000:0000:0000:ff00:0:0", - description: "Shouldn't do anything if the lowest 32 bits are already 0", + IP: "0.0.0.0", + cleanedIP: "0.0.0.0", + bits: 32, + maskBits: 24, }, - { - IP: "not an ip", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + IP: "192.127.111.134", + cleanedIP: "192.127.111.0", + bits: 32, + maskBits: 24, }, { - IP: "", - cleanedIP: "", - description: "Should return an empty string for a bad IP", + IP: "192.127.111.0", + cleanedIP: "192.127.111.0", + bits: 32, + maskBits: 24, }, } - for _, test := range testCases { - result := scrubIPV6Lowest32Bits(test.IP) - assert.Equal(t, test.cleanedIP, result, test.description) + t.Run(test.IP, func(t *testing.T) { + // bits: ipv6 - 128, ipv4 - 32 + result := scrubIP(test.IP, test.maskBits, test.bits) + assert.Equal(t, test.cleanedIP, result) + }) } } @@ -739,7 +708,7 @@ func getTestDevice() *openrtb2.Device { MACMD5: "anyMACMD5", IFA: "anyIFA", IP: "1.2.3.4", - IPv6: "2001:0db8:0000:0000:0000:ff00:0042:8329", + IPv6: "2001:1db8:2233:4455:6677:ff00:0042:8329", Geo: &openrtb2.Geo{ Lat: 123.456, Lon: 678.89, @@ -748,5 +717,15 @@ func getTestDevice() *openrtb2.Device { ZIP: "some zip", }, } +} +func getTestIPMasking() config.AccountPrivacy { + return config.AccountPrivacy{ + IPv6Config: config.IPv6{ + 54, + }, + IPv4Config: config.IPv4{ + 24, + }, + } } diff --git a/util/iputil/parse.go b/util/iputil/parse.go index bcb00760e22..c226ece8e5e 100644 --- a/util/iputil/parse.go +++ b/util/iputil/parse.go @@ -15,6 +15,14 @@ const ( IPv6 IPVersion = 6 ) +const ( + IPv4BitSize = 32 + IPv6BitSize = 128 + + IPv4DefaultMaskingBitSize = 24 + IPv6DefaultMaskingBitSize = 56 +) + // ParseIP parses v as an ip address returning the result and version, or nil and unknown if invalid. func ParseIP(v string) (net.IP, IPVersion) { if ip := net.ParseIP(v); ip != nil {