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 }