github.com/prebid/prebid-server@v0.275.0/endpoints/setuid_test.go (about) 1 package endpoints 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "regexp" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/prebid/prebid-server/analytics" 16 "github.com/prebid/prebid-server/config" 17 "github.com/prebid/prebid-server/errortypes" 18 "github.com/prebid/prebid-server/gdpr" 19 "github.com/prebid/prebid-server/macros" 20 "github.com/prebid/prebid-server/metrics" 21 "github.com/prebid/prebid-server/openrtb_ext" 22 "github.com/prebid/prebid-server/usersync" 23 "github.com/stretchr/testify/assert" 24 25 analyticsConf "github.com/prebid/prebid-server/analytics/config" 26 metricsConf "github.com/prebid/prebid-server/metrics/config" 27 ) 28 29 func TestSetUIDEndpoint(t *testing.T) { 30 testCases := []struct { 31 uri string 32 syncersBidderNameToKey map[string]string 33 existingSyncs map[string]string 34 gdprAllowsHostCookies bool 35 gdprReturnsError bool 36 gdprMalformed bool 37 expectedSyncs map[string]string 38 expectedBody string 39 expectedStatusCode int 40 expectedHeaders map[string]string 41 description string 42 }{ 43 { 44 uri: "/setuid?bidder=pubmatic&uid=123", 45 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 46 existingSyncs: nil, 47 gdprAllowsHostCookies: true, 48 expectedSyncs: map[string]string{"pubmatic": "123"}, 49 expectedStatusCode: http.StatusOK, 50 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 51 description: "Set uid for valid bidder", 52 }, 53 { 54 uri: "/setuid?bidder=appnexus&uid=123", 55 syncersBidderNameToKey: map[string]string{"appnexus": "adnxs"}, 56 existingSyncs: nil, 57 gdprAllowsHostCookies: true, 58 expectedSyncs: map[string]string{"adnxs": "123"}, 59 expectedStatusCode: http.StatusOK, 60 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 61 description: "Set uid for valid bidder with different key", 62 }, 63 { 64 uri: "/setuid?bidder=unsupported-bidder&uid=123", 65 syncersBidderNameToKey: map[string]string{}, 66 existingSyncs: nil, 67 gdprAllowsHostCookies: true, 68 expectedSyncs: nil, 69 expectedStatusCode: http.StatusBadRequest, 70 expectedBody: "The bidder name provided is not supported by Prebid Server", 71 description: "Don't set uid for an unsupported bidder", 72 }, 73 { 74 uri: "/setuid?bidder=&uid=123", 75 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 76 existingSyncs: nil, 77 gdprAllowsHostCookies: true, 78 expectedSyncs: nil, 79 expectedStatusCode: http.StatusBadRequest, 80 expectedBody: `"bidder" query param is required`, 81 description: "Don't set uid for an empty bidder", 82 }, 83 { 84 uri: "/setuid?bidder=unsupported-bidder&uid=123", 85 syncersBidderNameToKey: map[string]string{}, 86 existingSyncs: map[string]string{"pubmatic": "1234"}, 87 gdprAllowsHostCookies: true, 88 expectedSyncs: nil, 89 expectedStatusCode: http.StatusBadRequest, 90 expectedBody: "The bidder name provided is not supported by Prebid Server", 91 description: "No need to set existing syncs back in response for a request " + 92 "to set uid for an unsupported bidder", 93 }, 94 { 95 uri: "/setuid?bidder=&uid=123", 96 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 97 existingSyncs: map[string]string{"pubmatic": "1234"}, 98 gdprAllowsHostCookies: true, 99 expectedSyncs: nil, 100 expectedStatusCode: http.StatusBadRequest, 101 expectedBody: `"bidder" query param is required`, 102 description: "No need to set existing syncs back in response for a request " + 103 "to set uid for an empty bidder", 104 }, 105 { 106 uri: "/setuid?bidder=pubmatic", 107 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 108 existingSyncs: map[string]string{"pubmatic": "1234"}, 109 gdprAllowsHostCookies: true, 110 expectedSyncs: map[string]string{}, 111 expectedStatusCode: http.StatusOK, 112 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 113 description: "Unset uid for a bidder if the request contains an empty uid for that bidder", 114 }, 115 { 116 uri: "/setuid?bidder=pubmatic&uid=123", 117 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 118 existingSyncs: map[string]string{"rubicon": "def"}, 119 gdprAllowsHostCookies: true, 120 expectedSyncs: map[string]string{"pubmatic": "123", "rubicon": "def"}, 121 expectedStatusCode: http.StatusOK, 122 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 123 description: "Add the uid for the requested bidder to the list of existing syncs", 124 }, 125 { 126 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=0", 127 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 128 existingSyncs: nil, 129 gdprAllowsHostCookies: true, 130 expectedSyncs: map[string]string{"pubmatic": "123"}, 131 expectedStatusCode: http.StatusOK, 132 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 133 description: "Don't care about GDPR consent if GDPR is set to 0", 134 }, 135 { 136 uri: "/setuid?uid=123", 137 syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, 138 existingSyncs: nil, 139 expectedSyncs: nil, 140 gdprAllowsHostCookies: true, 141 expectedStatusCode: http.StatusBadRequest, 142 expectedBody: `"bidder" query param is required`, 143 description: "Return an error if the bidder param is missing from the request", 144 }, 145 { 146 uri: "/setuid?bidder=appnexus&uid=123&gdpr=2", 147 syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, 148 existingSyncs: nil, 149 expectedSyncs: nil, 150 gdprAllowsHostCookies: true, 151 expectedStatusCode: http.StatusBadRequest, 152 expectedBody: "the gdpr query param must be either 0 or 1. You gave 2", 153 description: "Return an error if GDPR is set to anything else other that 0 or 1", 154 }, 155 { 156 uri: "/setuid?bidder=appnexus&uid=123&gdpr=1", 157 syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, 158 existingSyncs: nil, 159 expectedSyncs: nil, 160 gdprAllowsHostCookies: true, 161 expectedStatusCode: http.StatusBadRequest, 162 expectedBody: "GDPR consent is required when gdpr signal equals 1", 163 description: "Return an error if GDPR is set to 1 but GDPR consent string is missing", 164 }, 165 { 166 uri: "/setuid?bidder=pubmatic&uid=123&gdpr_consent=" + 167 "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", 168 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 169 existingSyncs: nil, 170 expectedSyncs: nil, 171 gdprReturnsError: true, 172 expectedStatusCode: http.StatusBadRequest, 173 expectedBody: "No global vendor list was available to interpret this consent string. " + 174 "If this is a new, valid version, it should become available soon.", 175 description: "Return an error if the GDPR string is either malformed or using a newer version that isn't yet supported", 176 }, 177 { 178 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + 179 "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", 180 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 181 existingSyncs: nil, 182 expectedSyncs: nil, 183 expectedStatusCode: http.StatusUnavailableForLegalReasons, 184 expectedBody: "The gdpr_consent string prevents cookies from being saved", 185 description: "Shouldn't set uid for a bidder if it is not allowed by the GDPR consent string", 186 }, 187 { 188 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=" + 189 "BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", 190 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 191 gdprAllowsHostCookies: true, 192 existingSyncs: nil, 193 expectedSyncs: map[string]string{"pubmatic": "123"}, 194 expectedStatusCode: http.StatusOK, 195 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 196 description: "Should set uid for a bidder that is allowed by the GDPR consent string", 197 }, 198 { 199 uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 200 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 201 gdprAllowsHostCookies: true, 202 existingSyncs: nil, 203 expectedSyncs: map[string]string{"pubmatic": "123"}, 204 expectedStatusCode: http.StatusOK, 205 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 206 description: "Sets uid for a bidder allowed by GDPR consent string in the GPP query field", 207 }, 208 { 209 uri: "/setuid?bidder=pubmatic&uid=123&gpp_sid=2,4&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA" + 210 "gdpr=1&gdpr_consent=BONciguONcjGKADACHENAOLS1rAHDAFAAEAASABQAMwAeACEAFw", 211 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 212 gdprAllowsHostCookies: true, 213 existingSyncs: nil, 214 expectedSyncs: map[string]string{"pubmatic": "123"}, 215 expectedStatusCode: http.StatusOK, 216 expectedBody: "Warning: 'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", 217 expectedHeaders: map[string]string{"Content-Type": "text/plain; charset=utf-8"}, 218 description: "Sets uid for a bidder allowed by GDPR in GPP, throws warning because GDPR legacy values weren't used", 219 }, 220 { 221 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=malformed", 222 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 223 gdprAllowsHostCookies: true, 224 gdprMalformed: true, 225 existingSyncs: nil, 226 expectedStatusCode: http.StatusBadRequest, 227 expectedBody: "gdpr_consent was invalid. malformed consent string malformed: some error", 228 description: "Should return an error if GDPR consent string is malformed", 229 }, 230 { 231 uri: "/setuid?bidder=pubmatic&uid=123&f=b", 232 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 233 existingSyncs: nil, 234 gdprAllowsHostCookies: true, 235 expectedSyncs: map[string]string{"pubmatic": "123"}, 236 expectedStatusCode: http.StatusOK, 237 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 238 description: "Set uid for valid bidder with iframe format", 239 }, 240 { 241 uri: "/setuid?bidder=pubmatic&uid=123&f=i", 242 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 243 existingSyncs: nil, 244 gdprAllowsHostCookies: true, 245 expectedSyncs: map[string]string{"pubmatic": "123"}, 246 expectedStatusCode: http.StatusOK, 247 expectedHeaders: map[string]string{"Content-Type": "image/png", "Content-Length": "86"}, 248 description: "Set uid for valid bidder with redirect format", 249 }, 250 { 251 uri: "/setuid?bidder=pubmatic&uid=123&f=x", 252 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 253 existingSyncs: nil, 254 gdprAllowsHostCookies: true, 255 expectedSyncs: nil, 256 expectedStatusCode: http.StatusBadRequest, 257 expectedBody: `"f" query param is invalid. must be "b" or "i"`, 258 description: "Set uid for valid bidder with invalid format", 259 }, 260 { 261 uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct", 262 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 263 existingSyncs: nil, 264 gdprAllowsHostCookies: true, 265 expectedSyncs: map[string]string{"pubmatic": "123"}, 266 expectedStatusCode: http.StatusOK, 267 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 268 description: "Set uid for valid bidder with valid account provided", 269 }, 270 { 271 uri: "/setuid?bidder=pubmatic&uid=123&account=disabled_acct", 272 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 273 existingSyncs: nil, 274 gdprAllowsHostCookies: true, 275 expectedSyncs: nil, 276 expectedStatusCode: http.StatusBadRequest, 277 expectedBody: "account is disabled, please reach out to the prebid server host", 278 description: "Set uid for valid bidder with valid disabled account provided", 279 }, 280 { 281 uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_enabled", 282 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 283 existingSyncs: nil, 284 gdprAllowsHostCookies: true, 285 expectedSyncs: map[string]string{"pubmatic": "123"}, 286 expectedStatusCode: http.StatusOK, 287 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 288 description: "Set uid for valid bidder with valid account provided with user sync allowed activity", 289 }, 290 { 291 uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_valid_activities_usersync_disabled", 292 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 293 existingSyncs: nil, 294 gdprAllowsHostCookies: true, 295 expectedSyncs: nil, 296 expectedStatusCode: http.StatusUnavailableForLegalReasons, 297 description: "Set uid for valid bidder with valid account provided with user sync disallowed activity", 298 }, 299 { 300 uri: "/setuid?bidder=pubmatic&uid=123&account=valid_acct_with_invalid_activities", 301 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 302 existingSyncs: nil, 303 gdprAllowsHostCookies: true, 304 expectedSyncs: map[string]string{"pubmatic": "123"}, 305 expectedStatusCode: http.StatusOK, 306 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 307 description: "Set uid for valid bidder with valid account provided with invalid user sync activity", 308 }, 309 { 310 description: "gppsid-valid", 311 uri: "/setuid?bidder=appnexus&uid=123&gpp_sid=100,101", // fake sids to avoid GDPR logic in this test 312 syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, 313 existingSyncs: nil, 314 gdprAllowsHostCookies: true, 315 expectedSyncs: map[string]string{"appnexus": "123"}, 316 expectedStatusCode: http.StatusOK, 317 expectedHeaders: map[string]string{"Content-Type": "text/html", "Content-Length": "0"}, 318 }, 319 { 320 description: "gppsid-malformed", 321 uri: "/setuid?bidder=appnexus&uid=123&gpp_sid=malformed", 322 syncersBidderNameToKey: map[string]string{"appnexus": "appnexus"}, 323 existingSyncs: nil, 324 gdprAllowsHostCookies: true, 325 expectedSyncs: nil, 326 expectedStatusCode: http.StatusBadRequest, 327 expectedBody: "invalid gpp_sid encoding, must be a csv list of integers", 328 }, 329 } 330 331 analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) 332 metrics := &metricsConf.NilMetricsEngine{} 333 334 for _, test := range testCases { 335 response := doRequest(makeRequest(test.uri, test.existingSyncs), analytics, metrics, 336 test.syncersBidderNameToKey, test.gdprAllowsHostCookies, test.gdprReturnsError, test.gdprMalformed, false, 0, nil) 337 assert.Equal(t, test.expectedStatusCode, response.Code, "Test Case: %s. /setuid returned unexpected error code", test.description) 338 339 if test.expectedSyncs != nil { 340 assertHasSyncs(t, test.description, response, test.expectedSyncs) 341 } else { 342 assert.Equal(t, "", response.Header().Get("Set-Cookie"), "Test Case: %s. /setuid returned unexpected cookie", test.description) 343 } 344 345 if test.expectedBody != "" { 346 assert.Equal(t, test.expectedBody, response.Body.String(), "Test Case: %s. /setuid returned unexpected message", test.description) 347 } 348 349 // compare header values, except for the cookies 350 responseHeaders := map[string]string{} 351 for k, v := range response.Result().Header { 352 if k != "Set-Cookie" { 353 responseHeaders[k] = v[0] 354 } 355 } 356 if test.expectedHeaders == nil { 357 test.expectedHeaders = map[string]string{} 358 } 359 assert.Equal(t, test.expectedHeaders, responseHeaders, test.description+":headers") 360 } 361 } 362 363 func TestSetUIDPriorityEjection(t *testing.T) { 364 decoder := usersync.Base64Decoder{} 365 analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) 366 syncersByBidder := map[string]string{ 367 "pubmatic": "pubmatic", 368 "syncer1": "syncer1", 369 "syncer2": "syncer2", 370 "syncer3": "syncer3", 371 "syncer4": "syncer4", 372 "mismatchedBidderName": "syncer5", 373 "syncerToEject": "syncerToEject", 374 } 375 376 testCases := []struct { 377 description string 378 uri string 379 givenExistingSyncs []string 380 givenPriorityGroups [][]string 381 givenMaxCookieSize int 382 expectedStatusCode int 383 expectedSyncer string 384 expectedUID string 385 expectedNumOfElements int 386 expectedWarning string 387 }{ 388 { 389 description: "Cookie empty, expect bidder to be synced, no ejection", 390 uri: "/setuid?bidder=pubmatic&uid=123", 391 givenPriorityGroups: [][]string{}, 392 givenMaxCookieSize: 500, 393 expectedSyncer: "pubmatic", 394 expectedUID: "123", 395 expectedNumOfElements: 1, 396 expectedStatusCode: http.StatusOK, 397 }, 398 { 399 description: "Cookie full, no priority groups, one ejection", 400 uri: "/setuid?bidder=pubmatic&uid=123", 401 givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer4"}, 402 givenPriorityGroups: [][]string{}, 403 givenMaxCookieSize: 500, 404 expectedUID: "123", 405 expectedSyncer: "pubmatic", 406 expectedNumOfElements: 4, 407 expectedStatusCode: http.StatusOK, 408 }, 409 { 410 description: "Cookie full, eject lowest priority element", 411 uri: "/setuid?bidder=pubmatic&uid=123", 412 givenExistingSyncs: []string{"syncer2", "syncer3", "syncer4", "syncerToEject"}, 413 givenPriorityGroups: [][]string{{"pubmatic", "syncer2", "syncer3", "syncer4"}, {"syncerToEject"}}, 414 givenMaxCookieSize: 500, 415 expectedUID: "123", 416 expectedSyncer: "pubmatic", 417 expectedNumOfElements: 4, 418 expectedStatusCode: http.StatusOK, 419 }, 420 { 421 description: "Cookie full, all elements same priority, one ejection", 422 uri: "/setuid?bidder=pubmatic&uid=123", 423 givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer5"}, 424 givenPriorityGroups: [][]string{{"pubmatic", "syncer1", "syncer2", "syncer3", "mismatchedBidderName"}}, 425 givenMaxCookieSize: 500, 426 expectedUID: "123", 427 expectedSyncer: "pubmatic", 428 expectedNumOfElements: 4, 429 expectedStatusCode: http.StatusOK, 430 }, 431 { 432 description: "There are only priority elements left, but the bidder being synced isn't one", 433 uri: "/setuid?bidder=pubmatic&uid=123", 434 givenExistingSyncs: []string{"syncer1", "syncer2", "syncer3", "syncer4"}, 435 givenPriorityGroups: [][]string{{"syncer1", "syncer2", "syncer3", "syncer4"}}, 436 givenMaxCookieSize: 500, 437 expectedStatusCode: http.StatusOK, 438 expectedWarning: "Warning: syncer key is not a priority, and there are only priority elements left, cookie not updated", 439 }, 440 { 441 description: "Uid that's trying to be synced is bigger than MaxCookieSize", 442 uri: "/setuid?bidder=pubmatic&uid=123", 443 givenMaxCookieSize: 1, 444 expectedStatusCode: http.StatusBadRequest, 445 }, 446 } 447 for _, test := range testCases { 448 request := httptest.NewRequest("GET", test.uri, nil) 449 450 // Cookie Set Up 451 cookie := usersync.NewCookie() 452 for _, key := range test.givenExistingSyncs { 453 cookie.Sync(key, "111") 454 } 455 httpCookie, err := ToHTTPCookie(cookie) 456 assert.NoError(t, err) 457 request.AddCookie(httpCookie) 458 459 // Make Request to /setuid 460 response := doRequest(request, analytics, &metricsConf.NilMetricsEngine{}, syncersByBidder, true, false, false, false, test.givenMaxCookieSize, test.givenPriorityGroups) 461 462 if test.expectedWarning != "" { 463 assert.Equal(t, test.expectedWarning, response.Body.String(), test.description) 464 } else if test.expectedSyncer != "" { 465 // Get Cookie From Header 466 var cookieHeader string 467 for k, v := range response.Result().Header { 468 if k == "Set-Cookie" { 469 cookieHeader = v[0] 470 } 471 } 472 encodedCookieValue := getUIDFromHeader(cookieHeader) 473 474 // Check That Bidder On Request was Synced, it's UID matches, and that the right number of elements are present after ejection 475 decodedCookie := decoder.Decode(encodedCookieValue) 476 decodedCookieUIDs := decodedCookie.GetUIDs() 477 478 assert.Equal(t, test.expectedUID, decodedCookieUIDs[test.expectedSyncer], test.description) 479 assert.Equal(t, test.expectedNumOfElements, len(decodedCookieUIDs), test.description) 480 481 // Specific test case handling where we eject the lowest priority element 482 if len(test.givenPriorityGroups) == 2 { 483 syncer := test.givenPriorityGroups[len(test.givenPriorityGroups)-1][0] 484 _, syncerExists := decodedCookieUIDs[syncer] 485 assert.False(t, syncerExists, test.description) 486 } 487 } 488 assert.Equal(t, test.expectedStatusCode, response.Result().StatusCode, test.description) 489 } 490 } 491 492 func TestParseSignalFromGPPSID(t *testing.T) { 493 type testOutput struct { 494 signal gdpr.Signal 495 err error 496 } 497 testCases := []struct { 498 desc string 499 strSID string 500 expected testOutput 501 }{ 502 { 503 desc: "Empty gpp_sid, expect gdpr.SignalAmbiguous", 504 strSID: "", 505 expected: testOutput{ 506 signal: gdpr.SignalAmbiguous, 507 err: nil, 508 }, 509 }, 510 { 511 desc: "Malformed gpp_sid, expect gdpr.SignalAmbiguous", 512 strSID: "malformed", 513 expected: testOutput{ 514 signal: gdpr.SignalAmbiguous, 515 err: errors.New(`Error parsing gpp_sid strconv.ParseInt: parsing "malformed": invalid syntax`), 516 }, 517 }, 518 { 519 desc: "Valid gpp_sid doesn't come with TCF2, expect gdpr.SignalNo", 520 strSID: "6", 521 expected: testOutput{ 522 signal: gdpr.SignalNo, 523 err: nil, 524 }, 525 }, 526 { 527 desc: "Valid gpp_sid comes with TCF2, expect gdpr.SignalYes", 528 strSID: "2", 529 expected: testOutput{ 530 signal: gdpr.SignalYes, 531 err: nil, 532 }, 533 }, 534 } 535 for _, tc := range testCases { 536 outSignal, outErr := parseSignalFromGppSidStr(tc.strSID) 537 538 assert.Equal(t, tc.expected.signal, outSignal, tc.desc) 539 assert.Equal(t, tc.expected.err, outErr, tc.desc) 540 } 541 } 542 543 func TestParseConsentFromGppStr(t *testing.T) { 544 type testOutput struct { 545 gdprConsent string 546 err error 547 } 548 testCases := []struct { 549 desc string 550 inGppQuery string 551 expected testOutput 552 }{ 553 { 554 desc: "Empty gpp field, expect empty GDPR consent", 555 inGppQuery: "", 556 expected: testOutput{ 557 gdprConsent: "", 558 err: nil, 559 }, 560 }, 561 { 562 desc: "Malformed gpp field value, expect empty GDPR consent and error", 563 inGppQuery: "malformed", 564 expected: testOutput{ 565 gdprConsent: "", 566 err: errors.New(`error parsing GPP header, base64 decoding: illegal base64 data at input byte 8`), 567 }, 568 }, 569 { 570 desc: "Valid gpp string comes with TCF2 in its gppConstants.SectionID's, expect non-empty GDPR consent", 571 inGppQuery: "DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 572 expected: testOutput{ 573 gdprConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 574 err: nil, 575 }, 576 }, 577 { 578 desc: "Valid gpp string doesn't come with TCF2 in its gppConstants.SectionID's, expect blank GDPR consent", 579 inGppQuery: "DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 580 expected: testOutput{ 581 gdprConsent: "", 582 err: nil, 583 }, 584 }, 585 } 586 for _, tc := range testCases { 587 outConsent, outErr := parseConsentFromGppStr(tc.inGppQuery) 588 589 assert.Equal(t, tc.expected.gdprConsent, outConsent, tc.desc) 590 assert.Equal(t, tc.expected.err, outErr, tc.desc) 591 } 592 } 593 594 func TestParseGDPRFromGPP(t *testing.T) { 595 type testOutput struct { 596 reqInfo gdpr.RequestInfo 597 err error 598 } 599 type aTest struct { 600 desc string 601 inUri string 602 expected testOutput 603 } 604 testGroups := []struct { 605 groupDesc string 606 testCases []aTest 607 }{ 608 { 609 groupDesc: "No gpp_sid nor gpp", 610 testCases: []aTest{ 611 { 612 desc: "Input URL is mising gpp_sid and gpp, expect signal ambiguous and no error", 613 inUri: "/setuid?bidder=pubmatic&uid=123", 614 expected: testOutput{ 615 reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 616 err: nil, 617 }, 618 }, 619 }, 620 }, 621 { 622 groupDesc: "gpp only", 623 testCases: []aTest{ 624 { 625 desc: "gpp is malformed, expect error", 626 inUri: "/setuid?gpp=malformed", 627 expected: testOutput{ 628 reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 629 err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), 630 }, 631 }, 632 { 633 desc: "gpp with a valid TCF2 value. Expect valid consent string and no error", 634 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 635 expected: testOutput{ 636 reqInfo: gdpr.RequestInfo{ 637 GDPRSignal: gdpr.SignalAmbiguous, 638 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 639 }, 640 err: nil, 641 }, 642 }, 643 { 644 desc: "gpp does not include TCF2 string. Expect empty consent string and no error", 645 inUri: "/setuid?gpp=DBABjw~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN", 646 expected: testOutput{ 647 reqInfo: gdpr.RequestInfo{ 648 GDPRSignal: gdpr.SignalAmbiguous, 649 Consent: "", 650 }, 651 err: nil, 652 }, 653 }, 654 }, 655 }, 656 { 657 groupDesc: "gpp_sid only", 658 testCases: []aTest{ 659 { 660 desc: "gpp_sid is malformed, expect error", 661 inUri: "/setuid?gpp_sid=malformed", 662 expected: testOutput{ 663 reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 664 err: errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"), 665 }, 666 }, 667 { 668 desc: "TCF2 found in gpp_sid list. Given that the consent string will be empty, expect an error", 669 inUri: "/setuid?gpp_sid=2,6", 670 expected: testOutput{ 671 reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalYes}, 672 err: nil, 673 }, 674 }, 675 { 676 desc: "TCF2 not found in gpp_sid list. Expect SignalNo and no error", 677 inUri: "/setuid?gpp_sid=6,8", 678 expected: testOutput{ 679 reqInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo}, 680 err: nil, 681 }, 682 }, 683 }, 684 }, 685 { 686 groupDesc: "both gpp_sid and gpp", 687 testCases: []aTest{ 688 { 689 desc: "TCF2 found in gpp_sid list and gpp has a valid GDPR string. Expect no error", 690 inUri: "/setuid?gpp_sid=2,6&gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 691 expected: testOutput{ 692 reqInfo: gdpr.RequestInfo{ 693 GDPRSignal: gdpr.SignalYes, 694 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 695 }, 696 err: nil, 697 }, 698 }, 699 }, 700 }, 701 } 702 for _, tgroup := range testGroups { 703 for _, tc := range tgroup.testCases { 704 // set test 705 testURL, err := url.Parse(tc.inUri) 706 assert.NoError(t, err, "%s - %s", tgroup.groupDesc, tc.desc) 707 708 query := testURL.Query() 709 710 // run 711 outReqInfo, outErr := parseGDPRFromGPP(query) 712 713 // assertions 714 assert.Equal(t, tc.expected.reqInfo, outReqInfo, "%s - %s", tgroup.groupDesc, tc.desc) 715 assert.Equal(t, tc.expected.err, outErr, "%s - %s", tgroup.groupDesc, tc.desc) 716 } 717 } 718 } 719 720 func TestParseLegacyGDPRFields(t *testing.T) { 721 type testInput struct { 722 uri string 723 gppGDPRSignal gdpr.Signal 724 gppGDPRConsent string 725 } 726 type testOutput struct { 727 signal gdpr.Signal 728 consent string 729 err error 730 } 731 testCases := []struct { 732 desc string 733 in testInput 734 expected testOutput 735 }{ 736 { 737 desc: `both "gdpr" and "gdpr_consent" missing from URI, expect SignalAmbiguous, blank consent and no error`, 738 in: testInput{ 739 uri: "/setuid?bidder=pubmatic&uid=123", 740 }, 741 expected: testOutput{ 742 signal: gdpr.SignalAmbiguous, 743 consent: "", 744 err: nil, 745 }, 746 }, 747 { 748 desc: `invalid "gdpr" value, expect SignalAmbiguous, blank consent and error`, 749 in: testInput{ 750 uri: "/setuid?gdpr=2", 751 gppGDPRSignal: gdpr.SignalAmbiguous, 752 }, 753 expected: testOutput{ 754 signal: gdpr.SignalAmbiguous, 755 consent: "", 756 err: errors.New("the gdpr query param must be either 0 or 1. You gave 2"), 757 }, 758 }, 759 { 760 desc: `valid "gdpr" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`, 761 in: testInput{ 762 uri: "/setuid?gdpr=1", 763 gppGDPRSignal: gdpr.SignalYes, 764 }, 765 expected: testOutput{ 766 signal: gdpr.SignalAmbiguous, 767 consent: "", 768 err: &errortypes.Warning{ 769 Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", 770 WarningCode: errortypes.UnknownWarningCode, 771 }, 772 }, 773 }, 774 { 775 desc: `valid "gdpr_consent" value but valid GDPRSignal was previously parsed before, expect SignalAmbiguous, blank consent and a warning`, 776 in: testInput{ 777 uri: "/setuid?gdpr_consent=someConsent", 778 gppGDPRConsent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 779 }, 780 expected: testOutput{ 781 signal: gdpr.SignalAmbiguous, 782 consent: "", 783 err: &errortypes.Warning{ 784 Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", 785 WarningCode: errortypes.UnknownWarningCode, 786 }, 787 }, 788 }, 789 } 790 for _, tc := range testCases { 791 // set test 792 testURL, err := url.Parse(tc.in.uri) 793 assert.NoError(t, err, tc.desc) 794 795 query := testURL.Query() 796 797 // run 798 outSignal, outConsent, outErr := parseLegacyGDPRFields(query, tc.in.gppGDPRSignal, tc.in.gppGDPRConsent) 799 800 // assertions 801 assert.Equal(t, tc.expected.signal, outSignal, tc.desc) 802 assert.Equal(t, tc.expected.consent, outConsent, tc.desc) 803 assert.Equal(t, tc.expected.err, outErr, tc.desc) 804 } 805 } 806 807 func TestExtractGDPRInfo(t *testing.T) { 808 type testOutput struct { 809 requestInfo gdpr.RequestInfo 810 err error 811 } 812 type testCase struct { 813 desc string 814 inUri string 815 expected testOutput 816 } 817 testSuite := []struct { 818 sDesc string 819 tests []testCase 820 }{ 821 { 822 sDesc: "no gdpr nor gpp values in query", 823 tests: []testCase{ 824 { 825 desc: "expect blank consent, signalNo and nil error", 826 inUri: "/setuid?bidder=pubmatic&uid=123", 827 expected: testOutput{ 828 requestInfo: gdpr.RequestInfo{ 829 Consent: "", 830 GDPRSignal: gdpr.SignalAmbiguous, 831 }, 832 err: nil, 833 }, 834 }, 835 }, 836 }, 837 { 838 sDesc: "missing gpp, gdpr only", 839 tests: []testCase{ 840 { 841 desc: "Invalid gdpr signal value in query, expect blank request info and error", 842 inUri: "/setuid?gdpr=2", 843 expected: testOutput{ 844 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 845 err: errors.New("the gdpr query param must be either 0 or 1. You gave 2"), 846 }, 847 }, 848 { 849 desc: "GDPR equals 0, blank consent, expect blank consent, signalNo and nil error", 850 inUri: "/setuid?gdpr=0", 851 expected: testOutput{ 852 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalNo}, 853 err: nil, 854 }, 855 }, 856 { 857 desc: "GDPR equals 1, blank consent, expect blank request info and error", 858 inUri: "/setuid?gdpr=1", 859 expected: testOutput{ 860 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 861 err: errors.New("GDPR consent is required when gdpr signal equals 1"), 862 }, 863 }, 864 { 865 desc: "GDPR equals 0, non-blank consent, expect non-blank request info and nil error", 866 inUri: "/setuid?gdpr=0&gdpr_consent=someConsent", 867 expected: testOutput{ 868 requestInfo: gdpr.RequestInfo{ 869 Consent: "someConsent", 870 GDPRSignal: gdpr.SignalNo, 871 }, 872 err: nil, 873 }, 874 }, 875 { 876 desc: "GDPR equals 1, non-blank consent, expect non-blank request info and nil error", 877 inUri: "/setuid?gdpr=1&gdpr_consent=someConsent", 878 expected: testOutput{ 879 requestInfo: gdpr.RequestInfo{ 880 Consent: "someConsent", 881 GDPRSignal: gdpr.SignalYes, 882 }, 883 err: nil, 884 }, 885 }, 886 }, 887 }, 888 { 889 sDesc: "missing gdpr, gpp only", 890 tests: []testCase{ 891 { 892 desc: "Malformed GPP_SID string, expect blank request info and error", 893 inUri: "/setuid?gpp_sid=malformed", 894 expected: testOutput{ 895 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 896 err: errors.New("Error parsing gpp_sid strconv.ParseInt: parsing \"malformed\": invalid syntax"), 897 }, 898 }, 899 { 900 desc: "Valid GPP_SID string but invalid GPP string in query, expect blank request info and error", 901 inUri: "/setuid?gpp=malformed&gpp_sid=2", 902 expected: testOutput{ 903 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 904 err: errors.New("error parsing GPP header, base64 decoding: illegal base64 data at input byte 8"), 905 }, 906 }, 907 { 908 desc: "SectionTCFEU2 not found in GPP string, expect blank consent and signalAmbiguous", 909 inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA", 910 expected: testOutput{ 911 requestInfo: gdpr.RequestInfo{ 912 Consent: "", 913 GDPRSignal: gdpr.SignalAmbiguous, 914 }, 915 err: nil, 916 }, 917 }, 918 { 919 desc: "No GPP string, nor SectionTCFEU2 found in SID list in query, expect blank consent and signalAmbiguous", 920 inUri: "/setuid?gpp_sid=3,6", 921 expected: testOutput{ 922 requestInfo: gdpr.RequestInfo{ 923 Consent: "", 924 GDPRSignal: gdpr.SignalNo, 925 }, 926 err: nil, 927 }, 928 }, 929 { 930 desc: "No GPP string, SectionTCFEU2 found in SID list in query, expect blank request info and error", 931 inUri: "/setuid?gpp_sid=2", 932 expected: testOutput{ 933 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 934 err: errors.New("GDPR consent is required when gdpr signal equals 1"), 935 }, 936 }, 937 { 938 desc: "SectionTCFEU2 only found in SID list, expect blank request info and error", 939 inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2", 940 expected: testOutput{ 941 requestInfo: gdpr.RequestInfo{GDPRSignal: gdpr.SignalAmbiguous}, 942 err: errors.New("GDPR consent is required when gdpr signal equals 1"), 943 }, 944 }, 945 { 946 desc: "SectionTCFEU2 found in GPP string but SID list is nil, expect valid consent and SignalAmbiguous", 947 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 948 expected: testOutput{ 949 requestInfo: gdpr.RequestInfo{ 950 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 951 GDPRSignal: gdpr.SignalAmbiguous, 952 }, 953 err: nil, 954 }, 955 }, 956 { 957 desc: "SectionTCFEU2 found in GPP string but not in the non-nil SID list, expect valid consent and signalNo", 958 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6", 959 expected: testOutput{ 960 requestInfo: gdpr.RequestInfo{ 961 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 962 GDPRSignal: gdpr.SignalNo, 963 }, 964 err: nil, 965 }, 966 }, 967 { 968 desc: "SectionTCFEU2 found both in GPP string and SID list, expect valid consent and signalYes", 969 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4", 970 expected: testOutput{ 971 requestInfo: gdpr.RequestInfo{ 972 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 973 GDPRSignal: gdpr.SignalYes, 974 }, 975 err: nil, 976 }, 977 }, 978 }, 979 }, 980 { 981 sDesc: "GPP values take priority over GDPR", 982 tests: []testCase{ 983 { 984 desc: "SignalNo in gdpr field but SignalYes in SID list, CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA consent in gpp but legacyConsent in gdpr_consent, expect GPP values to prevail", 985 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0&gdpr_consent=legacyConsent", 986 expected: testOutput{ 987 requestInfo: gdpr.RequestInfo{ 988 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 989 GDPRSignal: gdpr.SignalYes, 990 }, 991 err: &errortypes.Warning{ 992 Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", 993 WarningCode: errortypes.UnknownWarningCode, 994 }, 995 }, 996 }, 997 { 998 desc: "SignalNo in gdpr field but SignalYes in SID list because SectionTCFEU2 is listed, expect GPP to prevail", 999 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=2,4&gdpr=0", 1000 expected: testOutput{ 1001 requestInfo: gdpr.RequestInfo{ 1002 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 1003 GDPRSignal: gdpr.SignalYes, 1004 }, 1005 err: &errortypes.Warning{ 1006 Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", 1007 WarningCode: errortypes.UnknownWarningCode, 1008 }, 1009 }, 1010 }, 1011 { 1012 desc: "No gpp string in URL query, use gdpr_consent and SignalYes found in SID list because SectionTCFEU2 is listed", 1013 inUri: "/setuid?gpp_sid=2,4&gdpr_consent=legacyConsent", 1014 expected: testOutput{ 1015 requestInfo: gdpr.RequestInfo{ 1016 Consent: "", 1017 GDPRSignal: gdpr.SignalAmbiguous, 1018 }, 1019 err: errors.New("GDPR consent is required when gdpr signal equals 1"), 1020 }, 1021 }, 1022 { 1023 desc: "SectionTCFEU2 not found in GPP string but found in SID list, choose the GDPR_CONSENT and GPP_SID signal", 1024 inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=2&gdpr=0&gdpr_consent=legacyConsent", 1025 expected: testOutput{ 1026 requestInfo: gdpr.RequestInfo{ 1027 Consent: "", 1028 GDPRSignal: gdpr.SignalAmbiguous, 1029 }, 1030 err: errors.New("GDPR consent is required when gdpr signal equals 1"), 1031 }, 1032 }, 1033 { 1034 desc: "SectionTCFEU2 found in GPP string but not in SID list, choose GDPR signal GPP consent value", 1035 inUri: "/setuid?gpp=DBABMA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent", 1036 expected: testOutput{ 1037 requestInfo: gdpr.RequestInfo{ 1038 Consent: "CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA", 1039 GDPRSignal: gdpr.SignalNo, 1040 }, 1041 err: &errortypes.Warning{ 1042 Message: "'gpp' value will be used over the one found in the deprecated 'gdpr_consent' field.", 1043 WarningCode: errortypes.UnknownWarningCode, 1044 }, 1045 }, 1046 }, 1047 { 1048 desc: "SectionTCFEU2 not found in GPP, use GDPR_CONSENT value. SignalYes found in gdpr field, but not in the valid SID list, expect SignalNo", 1049 inUri: "/setuid?gpp=DBABBgA~xlgWEYCZAA&gpp_sid=6&gdpr=1&gdpr_consent=legacyConsent", 1050 expected: testOutput{ 1051 requestInfo: gdpr.RequestInfo{ 1052 Consent: "", 1053 GDPRSignal: gdpr.SignalNo, 1054 }, 1055 err: &errortypes.Warning{ 1056 Message: "'gpp_sid' signal value will be used over the one found in the deprecated 'gdpr' field.", 1057 WarningCode: errortypes.UnknownWarningCode, 1058 }, 1059 }, 1060 }, 1061 }, 1062 }, 1063 } 1064 1065 for _, ts := range testSuite { 1066 for _, tc := range ts.tests { 1067 // set test 1068 testURL, err := url.Parse(tc.inUri) 1069 assert.NoError(t, err, tc.desc) 1070 1071 query := testURL.Query() 1072 1073 // run 1074 outReqInfo, outErr := extractGDPRInfo(query) 1075 1076 // assertions 1077 assert.Equal(t, tc.expected.requestInfo, outReqInfo, tc.desc) 1078 assert.Equal(t, tc.expected.err, outErr, tc.desc) 1079 } 1080 } 1081 } 1082 1083 func TestSetUIDEndpointMetrics(t *testing.T) { 1084 cookieWithOptOut := usersync.NewCookie() 1085 cookieWithOptOut.SetOptOut(true) 1086 1087 testCases := []struct { 1088 description string 1089 uri string 1090 cookies []*usersync.Cookie 1091 syncersBidderNameToKey map[string]string 1092 gdprAllowsHostCookies bool 1093 cfgAccountRequired bool 1094 expectedResponseCode int 1095 expectedMetrics func(*metrics.MetricsEngineMock) 1096 expectedAnalytics func(*MockAnalytics) 1097 }{ 1098 { 1099 description: "Success - Sync", 1100 uri: "/setuid?bidder=pubmatic&uid=123", 1101 cookies: []*usersync.Cookie{}, 1102 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1103 gdprAllowsHostCookies: true, 1104 expectedResponseCode: 200, 1105 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1106 m.On("RecordSetUid", metrics.SetUidOK).Once() 1107 m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidOK).Once() 1108 }, 1109 expectedAnalytics: func(a *MockAnalytics) { 1110 expected := analytics.SetUIDObject{ 1111 Status: 200, 1112 Bidder: "pubmatic", 1113 UID: "123", 1114 Errors: []error{}, 1115 Success: true, 1116 } 1117 a.On("LogSetUIDObject", &expected).Once() 1118 }, 1119 }, 1120 { 1121 description: "Success - Unsync", 1122 uri: "/setuid?bidder=pubmatic&uid=", 1123 cookies: []*usersync.Cookie{}, 1124 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1125 gdprAllowsHostCookies: true, 1126 expectedResponseCode: 200, 1127 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1128 m.On("RecordSetUid", metrics.SetUidOK).Once() 1129 m.On("RecordSyncerSet", "pubmatic", metrics.SyncerSetUidCleared).Once() 1130 }, 1131 expectedAnalytics: func(a *MockAnalytics) { 1132 expected := analytics.SetUIDObject{ 1133 Status: 200, 1134 Bidder: "pubmatic", 1135 UID: "", 1136 Errors: []error{}, 1137 Success: true, 1138 } 1139 a.On("LogSetUIDObject", &expected).Once() 1140 }, 1141 }, 1142 { 1143 description: "Cookie Opted Out", 1144 uri: "/setuid?bidder=pubmatic&uid=123", 1145 cookies: []*usersync.Cookie{cookieWithOptOut}, 1146 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1147 gdprAllowsHostCookies: true, 1148 expectedResponseCode: 401, 1149 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1150 m.On("RecordSetUid", metrics.SetUidOptOut).Once() 1151 }, 1152 expectedAnalytics: func(a *MockAnalytics) { 1153 expected := analytics.SetUIDObject{ 1154 Status: 401, 1155 Bidder: "", 1156 UID: "", 1157 Errors: []error{}, 1158 Success: false, 1159 } 1160 a.On("LogSetUIDObject", &expected).Once() 1161 }, 1162 }, 1163 { 1164 description: "Unknown Syncer Key", 1165 uri: "/setuid?bidder=pubmatic&uid=123", 1166 cookies: []*usersync.Cookie{}, 1167 syncersBidderNameToKey: map[string]string{}, 1168 gdprAllowsHostCookies: true, 1169 expectedResponseCode: 400, 1170 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1171 m.On("RecordSetUid", metrics.SetUidSyncerUnknown).Once() 1172 }, 1173 expectedAnalytics: func(a *MockAnalytics) { 1174 expected := analytics.SetUIDObject{ 1175 Status: 400, 1176 Bidder: "", 1177 UID: "", 1178 Errors: []error{errors.New("The bidder name provided is not supported by Prebid Server")}, 1179 Success: false, 1180 } 1181 a.On("LogSetUIDObject", &expected).Once() 1182 }, 1183 }, 1184 { 1185 description: "Unknown Format", 1186 uri: "/setuid?bidder=pubmatic&uid=123&f=z", 1187 cookies: []*usersync.Cookie{}, 1188 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1189 gdprAllowsHostCookies: true, 1190 expectedResponseCode: 400, 1191 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1192 m.On("RecordSetUid", metrics.SetUidBadRequest).Once() 1193 }, 1194 expectedAnalytics: func(a *MockAnalytics) { 1195 expected := analytics.SetUIDObject{ 1196 Status: 400, 1197 Bidder: "pubmatic", 1198 UID: "", 1199 Errors: []error{errors.New(`"f" query param is invalid. must be "b" or "i"`)}, 1200 Success: false, 1201 } 1202 a.On("LogSetUIDObject", &expected).Once() 1203 }, 1204 }, 1205 { 1206 description: "Prevented By GDPR - Invalid Consent String", 1207 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1", 1208 cookies: []*usersync.Cookie{}, 1209 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1210 gdprAllowsHostCookies: true, 1211 expectedResponseCode: 400, 1212 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1213 m.On("RecordSetUid", metrics.SetUidBadRequest).Once() 1214 }, 1215 expectedAnalytics: func(a *MockAnalytics) { 1216 expected := analytics.SetUIDObject{ 1217 Status: 400, 1218 Bidder: "pubmatic", 1219 UID: "", 1220 Errors: []error{errors.New("GDPR consent is required when gdpr signal equals 1")}, 1221 Success: false, 1222 } 1223 a.On("LogSetUIDObject", &expected).Once() 1224 }, 1225 }, 1226 { 1227 description: "Prevented By GDPR - Permission Denied By Consent String", 1228 uri: "/setuid?bidder=pubmatic&uid=123&gdpr=1&gdpr_consent=any", 1229 cookies: []*usersync.Cookie{}, 1230 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1231 gdprAllowsHostCookies: false, 1232 expectedResponseCode: 451, 1233 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1234 m.On("RecordSetUid", metrics.SetUidGDPRHostCookieBlocked).Once() 1235 }, 1236 expectedAnalytics: func(a *MockAnalytics) { 1237 expected := analytics.SetUIDObject{ 1238 Status: 451, 1239 Bidder: "pubmatic", 1240 UID: "", 1241 Errors: []error{errors.New("The gdpr_consent string prevents cookies from being saved")}, 1242 Success: false, 1243 } 1244 a.On("LogSetUIDObject", &expected).Once() 1245 }, 1246 }, 1247 { 1248 description: "Blocked account", 1249 uri: "/setuid?bidder=pubmatic&uid=123&account=blocked_acct", 1250 cookies: []*usersync.Cookie{}, 1251 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1252 gdprAllowsHostCookies: true, 1253 expectedResponseCode: 400, 1254 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1255 m.On("RecordSetUid", metrics.SetUidAccountBlocked).Once() 1256 }, 1257 expectedAnalytics: func(a *MockAnalytics) { 1258 expected := analytics.SetUIDObject{ 1259 Status: 400, 1260 Bidder: "pubmatic", 1261 UID: "", 1262 Errors: []error{errCookieSyncAccountBlocked}, 1263 Success: false, 1264 } 1265 a.On("LogSetUIDObject", &expected).Once() 1266 }, 1267 }, 1268 { 1269 description: "Invalid account", 1270 uri: "/setuid?bidder=pubmatic&uid=123&account=unknown", 1271 cookies: []*usersync.Cookie{}, 1272 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1273 gdprAllowsHostCookies: true, 1274 cfgAccountRequired: true, 1275 expectedResponseCode: 400, 1276 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1277 m.On("RecordSetUid", metrics.SetUidAccountInvalid).Once() 1278 }, 1279 expectedAnalytics: func(a *MockAnalytics) { 1280 expected := analytics.SetUIDObject{ 1281 Status: 400, 1282 Bidder: "pubmatic", 1283 UID: "", 1284 Errors: []error{errCookieSyncAccountInvalid}, 1285 Success: false, 1286 } 1287 a.On("LogSetUIDObject", &expected).Once() 1288 }, 1289 }, 1290 { 1291 description: "Malformed account", 1292 uri: "/setuid?bidder=pubmatic&uid=123&account=malformed_acct", 1293 cookies: []*usersync.Cookie{}, 1294 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1295 gdprAllowsHostCookies: true, 1296 cfgAccountRequired: true, 1297 expectedResponseCode: 400, 1298 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1299 m.On("RecordSetUid", metrics.SetUidAccountConfigMalformed).Once() 1300 }, 1301 expectedAnalytics: func(a *MockAnalytics) { 1302 expected := analytics.SetUIDObject{ 1303 Status: 400, 1304 Bidder: "pubmatic", 1305 UID: "", 1306 Errors: []error{errCookieSyncAccountConfigMalformed}, 1307 Success: false, 1308 } 1309 a.On("LogSetUIDObject", &expected).Once() 1310 }, 1311 }, 1312 { 1313 description: "Invalid JSON account", 1314 uri: "/setuid?bidder=pubmatic&uid=123&account=invalid_json_acct", 1315 cookies: []*usersync.Cookie{}, 1316 syncersBidderNameToKey: map[string]string{"pubmatic": "pubmatic"}, 1317 gdprAllowsHostCookies: true, 1318 cfgAccountRequired: true, 1319 expectedResponseCode: 400, 1320 expectedMetrics: func(m *metrics.MetricsEngineMock) { 1321 m.On("RecordSetUid", metrics.SetUidBadRequest).Once() 1322 }, 1323 expectedAnalytics: func(a *MockAnalytics) { 1324 expected := analytics.SetUIDObject{ 1325 Status: 400, 1326 Bidder: "pubmatic", 1327 UID: "", 1328 Errors: []error{errors.New("unexpected end of JSON input")}, 1329 Success: false, 1330 } 1331 a.On("LogSetUIDObject", &expected).Once() 1332 }, 1333 }, 1334 } 1335 1336 for _, test := range testCases { 1337 analyticsEngine := &MockAnalytics{} 1338 test.expectedAnalytics(analyticsEngine) 1339 1340 metricsEngine := &metrics.MetricsEngineMock{} 1341 test.expectedMetrics(metricsEngine) 1342 1343 req := httptest.NewRequest("GET", test.uri, nil) 1344 for _, v := range test.cookies { 1345 addCookie(req, v) 1346 } 1347 response := doRequest(req, analyticsEngine, metricsEngine, test.syncersBidderNameToKey, test.gdprAllowsHostCookies, false, false, test.cfgAccountRequired, 0, nil) 1348 1349 assert.Equal(t, test.expectedResponseCode, response.Code, test.description) 1350 analyticsEngine.AssertExpectations(t) 1351 metricsEngine.AssertExpectations(t) 1352 } 1353 } 1354 1355 func TestOptedOut(t *testing.T) { 1356 request := httptest.NewRequest("GET", "/setuid?bidder=pubmatic&uid=123", nil) 1357 cookie := usersync.NewCookie() 1358 cookie.SetOptOut(true) 1359 addCookie(request, cookie) 1360 syncersBidderNameToKey := map[string]string{"pubmatic": "pubmatic"} 1361 analytics := analyticsConf.NewPBSAnalytics(&config.Analytics{}) 1362 metrics := &metricsConf.NilMetricsEngine{} 1363 response := doRequest(request, analytics, metrics, syncersBidderNameToKey, true, false, false, false, 0, nil) 1364 1365 assert.Equal(t, http.StatusUnauthorized, response.Code) 1366 } 1367 1368 func TestSiteCookieCheck(t *testing.T) { 1369 testCases := []struct { 1370 ua string 1371 expectedResult bool 1372 description string 1373 }{ 1374 { 1375 ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36", 1376 expectedResult: true, 1377 description: "Should return true for a valid chrome version", 1378 }, 1379 { 1380 ua: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3770.142 Safari/537.36", 1381 expectedResult: false, 1382 description: "Should return false for chrome version below than the supported min version", 1383 }, 1384 } 1385 1386 for _, test := range testCases { 1387 assert.Equal(t, test.expectedResult, siteCookieCheck(test.ua), test.description) 1388 } 1389 } 1390 1391 func TestGetResponseFormat(t *testing.T) { 1392 testCases := []struct { 1393 urlValues url.Values 1394 syncer usersync.Syncer 1395 expectedFormat string 1396 expectedError string 1397 description string 1398 }{ 1399 { 1400 urlValues: url.Values{}, 1401 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, 1402 expectedFormat: "b", 1403 description: "parameter not provided, use default sync type iframe", 1404 }, 1405 { 1406 urlValues: url.Values{}, 1407 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, 1408 expectedFormat: "i", 1409 description: "parameter not provided, use default sync type redirect", 1410 }, 1411 { 1412 urlValues: url.Values{}, 1413 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncType("invalid")}, 1414 expectedFormat: "", 1415 description: "parameter not provided, default sync type is invalid", 1416 }, 1417 { 1418 urlValues: url.Values{"f": []string{"b"}}, 1419 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, 1420 expectedFormat: "b", 1421 description: "parameter given as `b`, default sync type is opposite", 1422 }, 1423 { 1424 urlValues: url.Values{"f": []string{"B"}}, 1425 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, 1426 expectedFormat: "b", 1427 description: "parameter given as `b`, default sync type is opposite - case insensitive", 1428 }, 1429 { 1430 urlValues: url.Values{"f": []string{"i"}}, 1431 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, 1432 expectedFormat: "i", 1433 description: "parameter given as `b`, default sync type is opposite", 1434 }, 1435 { 1436 urlValues: url.Values{"f": []string{"I"}}, 1437 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, 1438 expectedFormat: "i", 1439 description: "parameter given as `b`, default sync type is opposite - case insensitive", 1440 }, 1441 { 1442 urlValues: url.Values{"f": []string{"x"}}, 1443 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeIFrame}, 1444 expectedError: `"f" query param is invalid. must be "b" or "i"`, 1445 description: "parameter given invalid", 1446 }, 1447 { 1448 urlValues: url.Values{"f": []string{}}, 1449 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, 1450 expectedFormat: "i", 1451 description: "parameter given is empty (by slice), use default sync type redirect", 1452 }, 1453 { 1454 urlValues: url.Values{"f": []string{""}}, 1455 syncer: fakeSyncer{key: "a", defaultSyncType: usersync.SyncTypeRedirect}, 1456 expectedFormat: "i", 1457 description: "parameter given is empty (by empty item), use default sync type redirect", 1458 }, 1459 } 1460 1461 for _, test := range testCases { 1462 result, err := getResponseFormat(test.urlValues, test.syncer) 1463 1464 if test.expectedError == "" { 1465 assert.NoError(t, err, test.description+":err") 1466 assert.Equal(t, test.expectedFormat, result, test.description+":result") 1467 } else { 1468 assert.EqualError(t, err, test.expectedError, test.description+":err") 1469 assert.Empty(t, result, test.description+":result") 1470 } 1471 } 1472 } 1473 1474 func TestIsSyncerPriority(t *testing.T) { 1475 testCases := []struct { 1476 name string 1477 givenBidderNameFromSyncerQuery string 1478 givenPriorityGroups [][]string 1479 expected bool 1480 }{ 1481 { 1482 name: "bidder-name-is-priority", 1483 givenBidderNameFromSyncerQuery: "priorityBidder", 1484 givenPriorityGroups: [][]string{ 1485 {"priorityBidder"}, 1486 {"2", "3"}, 1487 }, 1488 expected: true, 1489 }, 1490 { 1491 name: "bidder-name-is-not-priority", 1492 givenBidderNameFromSyncerQuery: "notPriorityBidderName", 1493 givenPriorityGroups: [][]string{ 1494 {"1"}, 1495 {"2", "3"}, 1496 }, 1497 expected: false, 1498 }, 1499 { 1500 name: "no-bidder-name-given", 1501 givenBidderNameFromSyncerQuery: "", 1502 givenPriorityGroups: [][]string{ 1503 {"1"}, 1504 {"2", "3"}, 1505 }, 1506 expected: false, 1507 }, 1508 { 1509 name: "no-priority-groups-given", 1510 givenBidderNameFromSyncerQuery: "bidderName", 1511 givenPriorityGroups: [][]string{}, 1512 expected: false, 1513 }, 1514 } 1515 1516 for _, test := range testCases { 1517 t.Run(test.name, func(t *testing.T) { 1518 isPriority := isSyncerPriority(test.givenBidderNameFromSyncerQuery, test.givenPriorityGroups) 1519 assert.Equal(t, test.expected, isPriority) 1520 }) 1521 } 1522 } 1523 1524 func assertHasSyncs(t *testing.T, testCase string, resp *httptest.ResponseRecorder, syncs map[string]string) { 1525 t.Helper() 1526 cookie := parseCookieString(t, resp) 1527 1528 assert.Equal(t, len(syncs), len(cookie.GetUIDs()), "Test Case: %s. /setuid response doesn't contain expected number of syncs", testCase) 1529 1530 for bidder, uid := range syncs { 1531 assert.True(t, cookie.HasLiveSync(bidder), "Test Case: %s. /setuid response cookie doesn't contain uid for bidder: %s", testCase, bidder) 1532 actualUID, _, _ := cookie.GetUID(bidder) 1533 assert.Equal(t, uid, actualUID, "Test Case: %s. /setuid response cookie doesn't contain correct uid for bidder: %s", testCase, bidder) 1534 } 1535 } 1536 1537 func makeRequest(uri string, existingSyncs map[string]string) *http.Request { 1538 request := httptest.NewRequest("GET", uri, nil) 1539 if len(existingSyncs) > 0 { 1540 pbsCookie := usersync.NewCookie() 1541 for key, value := range existingSyncs { 1542 pbsCookie.Sync(key, value) 1543 } 1544 addCookie(request, pbsCookie) 1545 } 1546 return request 1547 } 1548 1549 func doRequest(req *http.Request, analytics analytics.PBSAnalyticsModule, metrics metrics.MetricsEngine, syncersBidderNameToKey map[string]string, gdprAllowsHostCookies, gdprReturnsError, gdprReturnsMalformedError, cfgAccountRequired bool, maxCookieSize int, priorityGroups [][]string) *httptest.ResponseRecorder { 1550 cfg := config.Configuration{ 1551 AccountRequired: cfgAccountRequired, 1552 BlacklistedAcctMap: map[string]bool{ 1553 "blocked_acct": true, 1554 }, 1555 AccountDefaults: config.Account{}, 1556 UserSync: config.UserSync{ 1557 PriorityGroups: priorityGroups, 1558 }, 1559 HostCookie: config.HostCookie{ 1560 MaxCookieSizeBytes: maxCookieSize, 1561 }, 1562 } 1563 cfg.MarshalAccountDefaults() 1564 1565 query := req.URL.Query() 1566 1567 perms := &fakePermsSetUID{ 1568 allowHost: gdprAllowsHostCookies, 1569 consent: query.Get("gdpr_consent"), 1570 errorHost: gdprReturnsError, 1571 errorMalformed: gdprReturnsMalformedError, 1572 personalInfoAllowed: true, 1573 } 1574 gdprPermsBuilder := fakePermissionsBuilder{ 1575 permissions: perms, 1576 }.Builder 1577 tcf2ConfigBuilder := fakeTCF2ConfigBuilder{ 1578 cfg: gdpr.NewTCF2Config(config.TCF2{}, config.AccountGDPR{}), 1579 }.Builder 1580 1581 syncersByBidder := make(map[string]usersync.Syncer) 1582 for bidderName, syncerKey := range syncersBidderNameToKey { 1583 syncersByBidder[bidderName] = fakeSyncer{key: syncerKey, defaultSyncType: usersync.SyncTypeIFrame} 1584 if priorityGroups == nil { 1585 cfg.UserSync.PriorityGroups = [][]string{{}} 1586 cfg.UserSync.PriorityGroups[0] = append(cfg.UserSync.PriorityGroups[0], bidderName) 1587 } 1588 } 1589 1590 fakeAccountsFetcher := FakeAccountsFetcher{AccountData: map[string]json.RawMessage{ 1591 "valid_acct": json.RawMessage(`{"disabled":false}`), 1592 "disabled_acct": json.RawMessage(`{"disabled":true}`), 1593 "malformed_acct": json.RawMessage(`{"disabled":"malformed"}`), 1594 "invalid_json_acct": json.RawMessage(`{"}`), 1595 1596 "valid_acct_with_valid_activities_usersync_enabled": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": true}}}}`), 1597 "valid_acct_with_valid_activities_usersync_disabled": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"default": false}}}}`), 1598 "valid_acct_with_invalid_activities": json.RawMessage(`{"privacy":{"allowactivities":{"syncUser":{"rules":[{"condition":{"componentName": ["bidderA.bidderB.bidderC"]}}]}}}}`), 1599 }} 1600 1601 endpoint := NewSetUIDEndpoint(&cfg, syncersByBidder, gdprPermsBuilder, tcf2ConfigBuilder, analytics, fakeAccountsFetcher, metrics) 1602 response := httptest.NewRecorder() 1603 endpoint(response, req, nil) 1604 return response 1605 } 1606 1607 func addCookie(req *http.Request, cookie *usersync.Cookie) { 1608 httpCookie, _ := ToHTTPCookie(cookie) 1609 req.AddCookie(httpCookie) 1610 } 1611 1612 func parseCookieString(t *testing.T, response *httptest.ResponseRecorder) *usersync.Cookie { 1613 decoder := usersync.Base64Decoder{} 1614 cookieString := response.Header().Get("Set-Cookie") 1615 parser := regexp.MustCompile("uids=(.*?);") 1616 res := parser.FindStringSubmatch(cookieString) 1617 assert.Equal(t, 2, len(res)) 1618 httpCookie := http.Cookie{ 1619 Name: "uids", 1620 Value: res[1], 1621 } 1622 return decoder.Decode(httpCookie.Value) 1623 } 1624 1625 type fakePermissionsBuilder struct { 1626 permissions gdpr.Permissions 1627 } 1628 1629 func (fpb fakePermissionsBuilder) Builder(gdpr.TCF2ConfigReader, gdpr.RequestInfo) gdpr.Permissions { 1630 return fpb.permissions 1631 } 1632 1633 type fakeTCF2ConfigBuilder struct { 1634 cfg gdpr.TCF2ConfigReader 1635 } 1636 1637 func (fcr fakeTCF2ConfigBuilder) Builder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader { 1638 return fcr.cfg 1639 } 1640 1641 type fakePermsSetUID struct { 1642 allowHost bool 1643 consent string 1644 errorHost bool 1645 errorMalformed bool 1646 personalInfoAllowed bool 1647 } 1648 1649 func (g *fakePermsSetUID) HostCookiesAllowed(ctx context.Context) (bool, error) { 1650 if g.errorMalformed { 1651 return g.allowHost, &gdpr.ErrorMalformedConsent{Consent: g.consent, Cause: errors.New("some error")} 1652 } 1653 if g.errorHost { 1654 return g.allowHost, errors.New("something went wrong") 1655 } 1656 return g.allowHost, nil 1657 } 1658 1659 func (g *fakePermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName) (bool, error) { 1660 return false, nil 1661 } 1662 1663 func (g *fakePermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions gdpr.AuctionPermissions, err error) { 1664 return gdpr.AuctionPermissions{ 1665 AllowBidRequest: g.personalInfoAllowed, 1666 PassGeo: g.personalInfoAllowed, 1667 PassID: g.personalInfoAllowed, 1668 }, nil 1669 } 1670 1671 type fakeSyncer struct { 1672 key string 1673 defaultSyncType usersync.SyncType 1674 } 1675 1676 func (s fakeSyncer) Key() string { 1677 return s.key 1678 } 1679 1680 func (s fakeSyncer) DefaultSyncType() usersync.SyncType { 1681 return s.defaultSyncType 1682 } 1683 1684 func (s fakeSyncer) SupportsType(syncTypes []usersync.SyncType) bool { 1685 return true 1686 } 1687 1688 func (s fakeSyncer) GetSync(syncTypes []usersync.SyncType, privacyMacros macros.UserSyncPrivacy) (usersync.Sync, error) { 1689 return usersync.Sync{}, nil 1690 } 1691 1692 func ToHTTPCookie(cookie *usersync.Cookie) (*http.Cookie, error) { 1693 encoder := usersync.Base64Encoder{} 1694 encodedCookie, err := encoder.Encode(cookie) 1695 if err != nil { 1696 return nil, nil 1697 } 1698 1699 return &http.Cookie{ 1700 Name: uidCookieName, 1701 Value: encodedCookie, 1702 Expires: time.Now().Add((90 * 24 * time.Hour)), 1703 Path: "/", 1704 }, nil 1705 } 1706 1707 func getUIDFromHeader(setCookieHeader string) string { 1708 cookies := strings.Split(setCookieHeader, ";") 1709 for _, cookie := range cookies { 1710 trimmedCookie := strings.TrimSpace(cookie) 1711 if strings.HasPrefix(trimmedCookie, "uids=") { 1712 parts := strings.SplitN(trimmedCookie, "=", 2) 1713 if len(parts) == 2 { 1714 return parts[1] 1715 } 1716 } 1717 } 1718 return "" 1719 }