github.com/prebid/prebid-server@v0.275.0/privacy/scrubber.go (about)

     1  package privacy
     2  
     3  import (
     4  	"encoding/json"
     5  	"net"
     6  
     7  	"github.com/prebid/prebid-server/config"
     8  	"github.com/prebid/prebid-server/util/iputil"
     9  	"github.com/prebid/prebid-server/util/ptrutil"
    10  
    11  	"github.com/prebid/openrtb/v19/openrtb2"
    12  )
    13  
    14  // ScrubStrategyIPV4 defines the approach to scrub PII from an IPV4 address.
    15  type ScrubStrategyIPV4 int
    16  
    17  const (
    18  	// ScrubStrategyIPV4None does not remove any part of an IPV4 address.
    19  	ScrubStrategyIPV4None ScrubStrategyIPV4 = iota
    20  
    21  	// ScrubStrategyIPV4Subnet zeroes out the last 8 bits of an IPV4 address.
    22  	ScrubStrategyIPV4Subnet
    23  )
    24  
    25  // ScrubStrategyIPV6 defines the approach to scrub PII from an IPV6 address.
    26  type ScrubStrategyIPV6 int
    27  
    28  const (
    29  	// ScrubStrategyIPV6None does not remove any part of an IPV6 address.
    30  	ScrubStrategyIPV6None ScrubStrategyIPV6 = iota
    31  
    32  	// ScrubStrategyIPV6Subnet zeroes out the last 16 bits of an IPV6 sub net address.
    33  	ScrubStrategyIPV6Subnet
    34  )
    35  
    36  // ScrubStrategyGeo defines the approach to scrub PII from geographical data.
    37  type ScrubStrategyGeo int
    38  
    39  const (
    40  	// ScrubStrategyGeoNone does not remove any geographical data.
    41  	ScrubStrategyGeoNone ScrubStrategyGeo = iota
    42  
    43  	// ScrubStrategyGeoFull removes all geographical data.
    44  	ScrubStrategyGeoFull
    45  
    46  	// ScrubStrategyGeoReducedPrecision anonymizes geographical data with rounding.
    47  	ScrubStrategyGeoReducedPrecision
    48  )
    49  
    50  // ScrubStrategyUser defines the approach to scrub PII from user data.
    51  type ScrubStrategyUser int
    52  
    53  const (
    54  	// ScrubStrategyUserNone does not remove non-location data.
    55  	ScrubStrategyUserNone ScrubStrategyUser = iota
    56  
    57  	// ScrubStrategyUserIDAndDemographic removes the user's buyer id, exchange id year of birth, and gender.
    58  	ScrubStrategyUserIDAndDemographic
    59  )
    60  
    61  // ScrubStrategyDeviceID defines the approach to remove hardware id and device id data.
    62  type ScrubStrategyDeviceID int
    63  
    64  const (
    65  	// ScrubStrategyDeviceIDNone does not remove hardware id and device id data.
    66  	ScrubStrategyDeviceIDNone ScrubStrategyDeviceID = iota
    67  
    68  	// ScrubStrategyDeviceIDAll removes all hardware and device id data (ifa, mac hashes device id hashes)
    69  	ScrubStrategyDeviceIDAll
    70  )
    71  
    72  // Scrubber removes PII from parts of an OpenRTB request.
    73  type Scrubber interface {
    74  	ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest
    75  	ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device
    76  	ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User
    77  }
    78  
    79  type scrubber struct {
    80  	ipV6 config.IPv6
    81  	ipV4 config.IPv4
    82  }
    83  
    84  // NewScrubber returns an OpenRTB scrubber.
    85  func NewScrubber(ipV6 config.IPv6, ipV4 config.IPv4) Scrubber {
    86  	return scrubber{
    87  		ipV6: ipV6,
    88  		ipV4: ipV4,
    89  	}
    90  }
    91  
    92  func (s scrubber) ScrubRequest(bidRequest *openrtb2.BidRequest, enforcement Enforcement) *openrtb2.BidRequest {
    93  	var userExtParsed map[string]json.RawMessage
    94  	userExtModified := false
    95  
    96  	// expressed in two lines because IntelliJ cannot infer the generic type
    97  	var userCopy *openrtb2.User
    98  	userCopy = ptrutil.Clone(bidRequest.User)
    99  
   100  	// expressed in two lines because IntelliJ cannot infer the generic type
   101  	var deviceCopy *openrtb2.Device
   102  	deviceCopy = ptrutil.Clone(bidRequest.Device)
   103  
   104  	if userCopy != nil && (enforcement.UFPD || enforcement.Eids) {
   105  		if len(userCopy.Ext) != 0 {
   106  			json.Unmarshal(userCopy.Ext, &userExtParsed)
   107  		}
   108  	}
   109  
   110  	if enforcement.UFPD {
   111  		// transmitUfpd covers user.ext.data, user.data, user.id, user.buyeruid, user.yob, user.gender, user.keywords, user.kwarray
   112  		// and device.{ifa, macsha1, macmd5, dpidsha1, dpidmd5, didsha1, didmd5}
   113  		if deviceCopy != nil {
   114  			deviceCopy.DIDMD5 = ""
   115  			deviceCopy.DIDSHA1 = ""
   116  			deviceCopy.DPIDMD5 = ""
   117  			deviceCopy.DPIDSHA1 = ""
   118  			deviceCopy.IFA = ""
   119  			deviceCopy.MACMD5 = ""
   120  			deviceCopy.MACSHA1 = ""
   121  		}
   122  		if userCopy != nil {
   123  			userCopy.Data = nil
   124  			userCopy.ID = ""
   125  			userCopy.BuyerUID = ""
   126  			userCopy.Yob = 0
   127  			userCopy.Gender = ""
   128  			userCopy.Keywords = ""
   129  			userCopy.KwArray = nil
   130  
   131  			_, hasField := userExtParsed["data"]
   132  			if hasField {
   133  				delete(userExtParsed, "data")
   134  				userExtModified = true
   135  			}
   136  		}
   137  	}
   138  	if enforcement.Eids {
   139  		//transmitEids covers user.eids and user.ext.eids
   140  		if userCopy != nil {
   141  			userCopy.EIDs = nil
   142  			_, hasField := userExtParsed["eids"]
   143  			if hasField {
   144  				delete(userExtParsed, "eids")
   145  				userExtModified = true
   146  			}
   147  		}
   148  	}
   149  
   150  	if userExtModified {
   151  		userExt, _ := json.Marshal(userExtParsed)
   152  		userCopy.Ext = userExt
   153  	}
   154  
   155  	if enforcement.TID {
   156  		//remove source.tid and imp.ext.tid
   157  		if bidRequest.Source != nil {
   158  			sourceCopy := ptrutil.Clone(bidRequest.Source)
   159  			sourceCopy.TID = ""
   160  			bidRequest.Source = sourceCopy
   161  		}
   162  		for ind, imp := range bidRequest.Imp {
   163  			impExt := scrubExtIDs(imp.Ext, "tid")
   164  			bidRequest.Imp[ind].Ext = impExt
   165  		}
   166  	}
   167  
   168  	if enforcement.PreciseGeo {
   169  		//round user's geographic location by rounding off IP address and lat/lng data.
   170  		//this applies to both device.geo and user.geo
   171  		if userCopy != nil && userCopy.Geo != nil {
   172  			userCopy.Geo = scrubGeoPrecision(userCopy.Geo)
   173  		}
   174  
   175  		if deviceCopy != nil {
   176  			if deviceCopy.Geo != nil {
   177  				deviceCopy.Geo = scrubGeoPrecision(deviceCopy.Geo)
   178  			}
   179  			deviceCopy.IP = scrubIP(deviceCopy.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize)
   180  			deviceCopy.IPv6 = scrubIP(deviceCopy.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize)
   181  		}
   182  	}
   183  
   184  	bidRequest.Device = deviceCopy
   185  	bidRequest.User = userCopy
   186  	return bidRequest
   187  }
   188  
   189  func (s scrubber) ScrubDevice(device *openrtb2.Device, id ScrubStrategyDeviceID, ipv4 ScrubStrategyIPV4, ipv6 ScrubStrategyIPV6, geo ScrubStrategyGeo) *openrtb2.Device {
   190  	if device == nil {
   191  		return nil
   192  	}
   193  
   194  	deviceCopy := *device
   195  
   196  	switch id {
   197  	case ScrubStrategyDeviceIDAll:
   198  		deviceCopy.DIDMD5 = ""
   199  		deviceCopy.DIDSHA1 = ""
   200  		deviceCopy.DPIDMD5 = ""
   201  		deviceCopy.DPIDSHA1 = ""
   202  		deviceCopy.IFA = ""
   203  		deviceCopy.MACMD5 = ""
   204  		deviceCopy.MACSHA1 = ""
   205  	}
   206  
   207  	switch ipv4 {
   208  	case ScrubStrategyIPV4Subnet:
   209  		deviceCopy.IP = scrubIP(device.IP, s.ipV4.AnonKeepBits, iputil.IPv4BitSize)
   210  	}
   211  
   212  	switch ipv6 {
   213  	case ScrubStrategyIPV6Subnet:
   214  		deviceCopy.IPv6 = scrubIP(device.IPv6, s.ipV6.AnonKeepBits, iputil.IPv6BitSize)
   215  	}
   216  
   217  	switch geo {
   218  	case ScrubStrategyGeoFull:
   219  		deviceCopy.Geo = scrubGeoFull(device.Geo)
   220  	case ScrubStrategyGeoReducedPrecision:
   221  		deviceCopy.Geo = scrubGeoPrecision(device.Geo)
   222  	}
   223  
   224  	return &deviceCopy
   225  }
   226  
   227  func (scrubber) ScrubUser(user *openrtb2.User, strategy ScrubStrategyUser, geo ScrubStrategyGeo) *openrtb2.User {
   228  	if user == nil {
   229  		return nil
   230  	}
   231  
   232  	userCopy := *user
   233  
   234  	if strategy == ScrubStrategyUserIDAndDemographic {
   235  		userCopy.BuyerUID = ""
   236  		userCopy.ID = ""
   237  		userCopy.Ext = scrubExtIDs(userCopy.Ext, "eids")
   238  		userCopy.Yob = 0
   239  		userCopy.Gender = ""
   240  	}
   241  
   242  	switch geo {
   243  	case ScrubStrategyGeoFull:
   244  		userCopy.Geo = scrubGeoFull(user.Geo)
   245  	case ScrubStrategyGeoReducedPrecision:
   246  		userCopy.Geo = scrubGeoPrecision(user.Geo)
   247  	}
   248  
   249  	return &userCopy
   250  }
   251  
   252  func scrubIP(ip string, ones, bits int) string {
   253  	if ip == "" {
   254  		return ""
   255  	}
   256  	ipMask := net.CIDRMask(ones, bits)
   257  	ipMasked := net.ParseIP(ip).Mask(ipMask)
   258  	return ipMasked.String()
   259  }
   260  
   261  func scrubGeoFull(geo *openrtb2.Geo) *openrtb2.Geo {
   262  	if geo == nil {
   263  		return nil
   264  	}
   265  
   266  	return &openrtb2.Geo{}
   267  }
   268  
   269  func scrubGeoPrecision(geo *openrtb2.Geo) *openrtb2.Geo {
   270  	if geo == nil {
   271  		return nil
   272  	}
   273  
   274  	geoCopy := *geo
   275  	geoCopy.Lat = float64(int(geo.Lat*100.0+0.5)) / 100.0 // Round Latitude
   276  	geoCopy.Lon = float64(int(geo.Lon*100.0+0.5)) / 100.0 // Round Longitude
   277  	return &geoCopy
   278  }
   279  
   280  func scrubExtIDs(ext json.RawMessage, fieldName string) json.RawMessage {
   281  	if len(ext) == 0 {
   282  		return ext
   283  	}
   284  
   285  	var userExtParsed map[string]json.RawMessage
   286  	err := json.Unmarshal(ext, &userExtParsed)
   287  	if err != nil {
   288  		return ext
   289  	}
   290  
   291  	_, hasField := userExtParsed[fieldName]
   292  	if hasField {
   293  		delete(userExtParsed, fieldName)
   294  		result, err := json.Marshal(userExtParsed)
   295  		if err == nil {
   296  			return result
   297  		}
   298  	}
   299  
   300  	return ext
   301  }