Skip to content

Commit 88c8813

Browse files
PubMatic-OpenWraphhhjort
authored andcommitted
Adding SameSite=None cookie attribute to uids cookie (#1012)
* committing changes for adding SameSite=none cookie attribute * incorporating code review comments - moving user agent splitting logic to setuid end-point
1 parent bc4e911 commit 88c8813

File tree

14 files changed

+144
-19
lines changed

14 files changed

+144
-19
lines changed

adapters/adform/adform_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ func preparePrebidRequest(serverUrl string, t *testing.T) *pbs.PBSRequest {
185185
pbsCookie := usersync.ParsePBSCookieFromRequest(prebidHttpRequest, &config.HostCookie{})
186186
pbsCookie.TrySync("adform", adformTestData.buyerUID)
187187
fakeWriter := httptest.NewRecorder()
188-
pbsCookie.SetCookieOnResponse(fakeWriter, &config.HostCookie{Domain: ""}, time.Minute)
188+
189+
pbsCookie.SetCookieOnResponse(fakeWriter, false, &config.HostCookie{Domain: ""}, time.Minute)
189190
prebidHttpRequest.Header.Add("Cookie", fakeWriter.Header().Get("Set-Cookie"))
190191

191192
cacheClient, _ := dummycache.New()

adapters/appnexus/appnexus_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,8 @@ func TestAppNexusBasicResponse(t *testing.T) {
367367
pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{})
368368
pc.TrySync("adnxs", andata.buyerUID)
369369
fakewriter := httptest.NewRecorder()
370-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
370+
371+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
371372
req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
372373

373374
cacheClient, _ := dummycache.New()

adapters/audienceNetwork/facebook_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,8 @@ func GenerateBidRequestForTestData(fbdata bidInfo, url string) (*pbs.PBSRequest,
209209
pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{})
210210
pc.TrySync("audienceNetwork", fbdata.buyerUID)
211211
fakewriter := httptest.NewRecorder()
212-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
212+
213+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
213214
req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
214215

215216
cacheClient, _ := dummycache.New()

adapters/lifestreet/lifestreet_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,8 @@ func TestLifestreetBasicResponse(t *testing.T) {
227227

228228
pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{})
229229
fakewriter := httptest.NewRecorder()
230-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
230+
231+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
231232
req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
232233

233234
cacheClient, _ := dummycache.New()

adapters/pubmatic/pubmatic_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@ func TestPubmaticSampleRequest(t *testing.T) {
656656
pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{})
657657
pc.TrySync("pubmatic", "12345")
658658
fakewriter := httptest.NewRecorder()
659-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
659+
660+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
660661
httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
661662

662663
cacheClient, _ := dummycache.New()

adapters/pulsepoint/pulsepoint_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,8 @@ func SampleRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest {
226226
pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{})
227227
pc.TrySync("pulsepoint", "pulsepointUser123")
228228
fakewriter := httptest.NewRecorder()
229-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
229+
230+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
230231
httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
231232
// parse the http request
232233
cacheClient, _ := dummycache.New()

adapters/rubicon/rubicon_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -945,7 +945,8 @@ func CreatePrebidRequest(server *httptest.Server, t *testing.T) (an *RubiconAdap
945945
pc := usersync.ParsePBSCookieFromRequest(req, &config.HostCookie{})
946946
pc.TrySync("rubicon", rubidata.buyerUID)
947947
fakewriter := httptest.NewRecorder()
948-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
948+
949+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
949950
req.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
950951

951952
cacheClient, _ := dummycache.New()

adapters/sovrn/sovrn_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ func SampleSovrnRequest(numberOfImpressions int, t *testing.T) *pbs.PBSRequest {
188188
pc := usersync.ParsePBSCookieFromRequest(httpReq, &config.HostCookie{})
189189
pc.TrySync("sovrn", testSovrnUserId)
190190
fakewriter := httptest.NewRecorder()
191-
pc.SetCookieOnResponse(fakewriter, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
191+
192+
pc.SetCookieOnResponse(fakewriter, false, &config.HostCookie{Domain: ""}, 90*24*time.Hour)
192193
httpReq.Header.Add("Cookie", fakewriter.Header().Get("Set-Cookie"))
193194
// parse the http request
194195
cacheClient, _ := dummycache.New()

endpoints/cookie_sync.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,16 @@ func (deps *cookieSyncDeps) Endpoint(w http.ResponseWriter, r *http.Request, _ h
106106
parsedReq.Bidders = append(parsedReq.Bidders, string(bidder))
107107
}
108108
}
109+
setSiteCookie := siteCookieCheck(r.UserAgent())
110+
needSyncupForSameSite := false
111+
if setSiteCookie {
112+
_, err1 := r.Cookie(usersync.SameSiteCookieName)
113+
if err1 == http.ErrNoCookie {
114+
needSyncupForSameSite = true
115+
}
116+
}
109117

110-
parsedReq.filterExistingSyncs(deps.syncers, userSyncCookie)
118+
parsedReq.filterExistingSyncs(deps.syncers, userSyncCookie, needSyncupForSameSite)
111119
adapterSyncs := make(map[openrtb_ext.BidderName]bool)
112120
for _, b := range parsedReq.Bidders {
113121
// assume all bidders will be GDPR blocked
@@ -183,10 +191,10 @@ type cookieSyncRequest struct {
183191
Limit int `json:"limit"`
184192
}
185193

186-
func (req *cookieSyncRequest) filterExistingSyncs(valid map[openrtb_ext.BidderName]usersync.Usersyncer, cookie *usersync.PBSCookie) {
194+
func (req *cookieSyncRequest) filterExistingSyncs(valid map[openrtb_ext.BidderName]usersync.Usersyncer, cookie *usersync.PBSCookie, needSyncupForSameSite bool) {
187195
for i := 0; i < len(req.Bidders); i++ {
188196
thisBidder := req.Bidders[i]
189-
if syncer, isValid := valid[openrtb_ext.BidderName(thisBidder)]; !isValid || cookie.HasLiveSync(syncer.FamilyName()) {
197+
if syncer, isValid := valid[openrtb_ext.BidderName(thisBidder)]; !isValid || (cookie.HasLiveSync(syncer.FamilyName()) && !needSyncupForSameSite) {
190198
req.Bidders = append(req.Bidders[:i], req.Bidders[i+1:]...)
191199
i--
192200
}

endpoints/setuid.go

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package endpoints
33
import (
44
"context"
55
"net/http"
6+
"strconv"
7+
"strings"
68
"time"
79

810
"github.com/julienschmidt/httprouter"
@@ -14,6 +16,14 @@ import (
1416
"github.com/prebid/prebid-server/usersync"
1517
)
1618

19+
const (
20+
chromeStr = "Chrome/"
21+
chromeiOSStr = "CriOS/"
22+
chromeMinVer = 67
23+
chromeStrLen = len(chromeStr)
24+
chromeiOSStrLen = len(chromeiOSStr)
25+
)
26+
1727
func NewSetUIDEndpoint(cfg config.HostCookie, perms gdpr.Permissions, pbsanalytics analytics.PBSAnalyticsModule, metrics pbsmetrics.MetricsEngine) httprouter.Handle {
1828
cookieTTL := time.Duration(cfg.TTL) * 24 * time.Hour
1929
return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
@@ -76,10 +86,39 @@ func NewSetUIDEndpoint(cfg config.HostCookie, perms gdpr.Permissions, pbsanalyti
7686
so.Success = true
7787
}
7888

79-
pc.SetCookieOnResponse(w, &cfg, cookieTTL)
89+
setSiteCookie := siteCookieCheck(r.UserAgent())
90+
pc.SetCookieOnResponse(w, setSiteCookie, &cfg, cookieTTL)
8091
})
8192
}
8293

94+
// siteCookieCheck scans the input User Agent string to check if browser is Chrome and browser version is greater than the minimum version for adding the SameSite cookie attribute
95+
func siteCookieCheck(ua string) bool {
96+
result := false
97+
98+
index := strings.Index(ua, chromeStr)
99+
criOSIndex := strings.Index(ua, chromeiOSStr)
100+
if index != -1 {
101+
result = checkChromeBrowserVersion(ua, index, chromeStrLen)
102+
} else if criOSIndex != -1 {
103+
result = checkChromeBrowserVersion(ua, criOSIndex, chromeiOSStrLen)
104+
}
105+
return result
106+
}
107+
108+
func checkChromeBrowserVersion(ua string, index int, chromeStrLength int) bool {
109+
result := false
110+
vIndex := index + chromeStrLength
111+
dotIndex := strings.Index(ua[vIndex:], ".")
112+
if dotIndex == -1 {
113+
dotIndex = len(ua[vIndex:])
114+
}
115+
version, _ := strconv.Atoi(ua[vIndex : vIndex+dotIndex])
116+
if version >= chromeMinVer {
117+
result = true
118+
}
119+
return result
120+
}
121+
83122
func preventSyncsGDPR(gdprEnabled string, gdprConsent string, perms gdpr.Permissions) (bool, int, string) {
84123
switch gdprEnabled {
85124
case "0":

endpoints/setuid_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,21 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
196196
func (g *mockPermsSetUID) PersonalInfoAllowed(ctx context.Context, bidder openrtb_ext.BidderName, PublisherID string, consent string) (bool, error) {
197197
return g.allowPI, nil
198198
}
199+
200+
func TestSiteCookieCheck(t *testing.T) {
201+
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"
202+
expectedResult := true
203+
actualResult := siteCookieCheck(ua)
204+
if actualResult != expectedResult {
205+
t.Errorf("Expected: %v, but got: %v", expectedResult, actualResult)
206+
}
207+
}
208+
209+
func TestSiteCookieCheckForOlderChromeVersion(t *testing.T) {
210+
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"
211+
expectedResult := false
212+
actualResult := siteCookieCheck(ua)
213+
if actualResult != expectedResult {
214+
t.Errorf("Expected: %v, but got: %v", expectedResult, actualResult)
215+
}
216+
}

pbs/usersync.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ func (deps *UserSyncDeps) OptOut(w http.ResponseWriter, r *http.Request, _ httpr
9595
pc := usersync.ParsePBSCookieFromRequest(r, deps.HostCookieConfig)
9696
pc.SetPreference(optout == "")
9797

98-
pc.SetCookieOnResponse(w, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration())
98+
pc.SetCookieOnResponse(w, false, deps.HostCookieConfig, deps.HostCookieConfig.TTLDuration())
99+
99100
if optout == "" {
100101
http.Redirect(w, r, deps.HostCookieConfig.OptInURL, 301)
101102
} else {

usersync/cookie.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ import (
1212
"github.com/prebid/prebid-server/openrtb_ext"
1313
)
1414

15-
// DEFAULT_TTL is the default amount of time which a cookie is considered valid.
16-
const DEFAULT_TTL = 14 * 24 * time.Hour
17-
const UID_COOKIE_NAME = "uids"
15+
const (
16+
// DEFAULT_TTL is the default amount of time which a cookie is considered valid.
17+
DEFAULT_TTL = 14 * 24 * time.Hour
18+
UID_COOKIE_NAME = "uids"
19+
SameSiteCookieName = "SSCookie"
20+
SameSiteCookieValue = "1"
21+
SameSiteAttribute = "; SameSite=None"
22+
)
1823

1924
// customBidderTTLs stores rules about how long a particular UID sync is valid for each bidder.
2025
// If a bidder does a cookie sync *without* listing a rule here, then the DEFAULT_TTL will be used.
@@ -161,7 +166,7 @@ func (cookie *PBSCookie) GetId(bidderName openrtb_ext.BidderName) (id string, ex
161166
}
162167

163168
// SetCookieOnResponse is a shortcut for "ToHTTPCookie(); cookie.setDomain(domain); setCookie(w, cookie)"
164-
func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, cfg *config.HostCookie, ttl time.Duration) {
169+
func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, setSiteCookie bool, cfg *config.HostCookie, ttl time.Duration) {
165170
httpCookie := cookie.ToHTTPCookie(ttl)
166171
var domain string = cfg.Domain
167172

@@ -187,7 +192,22 @@ func (cookie *PBSCookie) SetCookieOnResponse(w http.ResponseWriter, cfg *config.
187192
}
188193
currSize = len([]byte(httpCookie.String()))
189194
}
190-
http.SetCookie(w, httpCookie)
195+
196+
uidsCookieStr := httpCookie.String()
197+
var sameSiteCookie *http.Cookie
198+
if setSiteCookie {
199+
uidsCookieStr += SameSiteAttribute
200+
sameSiteCookie = &http.Cookie{
201+
Name: SameSiteCookieName,
202+
Value: SameSiteCookieValue,
203+
Expires: time.Now().Add(ttl),
204+
Path: "/",
205+
}
206+
sameSiteCookieStr := sameSiteCookie.String()
207+
sameSiteCookieStr += SameSiteAttribute
208+
w.Header().Add("Set-Cookie", sameSiteCookieStr)
209+
}
210+
w.Header().Add("Set-Cookie", uidsCookieStr)
191211
}
192212

193213
// Unsync removes the user's ID for the given family from this cookie.

usersync/cookie_test.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"net/http"
77
"net/http/httptest"
8+
"strings"
89
"testing"
910
"time"
1011

@@ -402,11 +403,41 @@ func newTestCookie() (*PBSCookie, int) {
402403
func writeThenRead(cookie *PBSCookie, maxCookieSize int) *PBSCookie {
403404
w := httptest.NewRecorder()
404405
hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: maxCookieSize}
405-
cookie.SetCookieOnResponse(w, hostCookie, 90*24*time.Hour)
406+
cookie.SetCookieOnResponse(w, false, hostCookie, 90*24*time.Hour)
406407
writtenCookie := w.HeaderMap.Get("Set-Cookie")
407408

408409
header := http.Header{}
409410
header.Add("Cookie", writtenCookie)
410411
request := http.Request{Header: header}
411412
return ParsePBSCookieFromRequest(&request, hostCookie)
412413
}
414+
415+
func TestSetCookieOnResponseForSameSiteNone(t *testing.T) {
416+
cookie := newSampleCookie()
417+
w := httptest.NewRecorder()
418+
req := httptest.NewRequest("GET", "http://www.prebid.com", nil)
419+
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"
420+
req.Header.Set("User-Agent", ua)
421+
hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: 0}
422+
cookie.SetCookieOnResponse(w, true, hostCookie, 90*24*time.Hour)
423+
writtenCookie := w.HeaderMap.Get("Set-Cookie")
424+
t.Log("Set-Cookie is: ", writtenCookie)
425+
if !strings.Contains(writtenCookie, "SSCookie=1") {
426+
t.Error("Set-Cookie should contain SSCookie=1")
427+
}
428+
}
429+
430+
func TestSetCookieOnResponseForOlderChromeVersion(t *testing.T) {
431+
cookie := newSampleCookie()
432+
w := httptest.NewRecorder()
433+
req := httptest.NewRequest("GET", "http://www.prebid.com", nil)
434+
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"
435+
req.Header.Set("User-Agent", ua)
436+
hostCookie := &config.HostCookie{Domain: "mock-domain", MaxCookieSizeBytes: 0}
437+
cookie.SetCookieOnResponse(w, false, hostCookie, 90*24*time.Hour)
438+
writtenCookie := w.HeaderMap.Get("Set-Cookie")
439+
t.Log("Set-Cookie is: ", writtenCookie)
440+
if strings.Contains(writtenCookie, "SameSite=none") {
441+
t.Error("Set-Cookie should not contain SameSite=none")
442+
}
443+
}

0 commit comments

Comments
 (0)