-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: same site cookie context and duplicate cookies #23438
Conversation
Thanks for taking the time to open a PR!
|
Test summaryRun details
View run in Cypress Dashboard ➡️ This comment has been generated by cypress-bot as a result of this project's GitHub integration settings. You can manage this integration in this project's settings in the Cypress Dashboard |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The unit tests are great, but could you add a more e2e-style test? Best place is probably cookie_login.cy.ts. We could have passing unit tests for an incorrect implementation. A test that actually runs through the browser will give us a bit more confidence it's correct.
this.isFalse = (url1, url2) => { | ||
expect(cors.urlOriginsMatch(url1, url2)).to.be.false | ||
} | ||
|
||
this.isTrue = (url1, url2) => { | ||
expect(cors.urlOriginsMatch(url1, url2)).to.be.true | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe assertOriginsDontMatch
and assertOriginsMatch
instead? I feel like isFalse/isTrue
should return whether the argument is false or true.
These could also be defined in the context's scope so there wouldn't be a need to use this.
everywhere, e.g.:
context('.urlOriginsMatch', () => {
const assertOriginsDontMatch = () => { /* ... */ }
it('test', () => {
assertOriginsDontMatch('https://foo.bar:443', this.url)
assertOriginsDontMatch('https://foo.bar:80', this.url)
// .. and so on ...
})
})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like that style a lot better. I went through the other tests in lib/cors
and refactored them in 8404853
sameSiteContext calculation method and add tests
…k of the cookie or not in the server side cookie jar
… appropriately to request header Cookie
…r. Add additional tests to verify expected behavior
…or vs what spec behavior should/will be
f1f8cbb
to
d02fc1f
Compare
@chrisbreiding the tests in |
…ite none cookies in firefox
…-site-cookie-context
@@ -191,7 +191,8 @@ describe('cy.origin - cookie login', () => { | |||
verifyIdpNotLoggedIn({ expectNullCookie: false }) | |||
}) | |||
|
|||
it('SameSite=None -> not logged in', () => { | |||
// FIXME: Currently in Firefox, the default cookie setting in the extension is no_restriction, which can be set with Secure=false. | |||
it('SameSite=None -> not logged in', { browser: '!firefox' }, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this broken or the tests arn't aligned? should we add a test specifically for firefox?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I believe was happening here before is that since this request that sets cookies was seemed "cross-origin", X-Set-Cookie
was being used in the header and not setting the cookie in the browser. In the server side cookie jar, we ignore cookies that are SameSite=None
and Secure=false
.
Now, since we use Set-Cookie
all the time, the cookie is actually set in firefox because we currently allow SameSite=None
and Secure=false
to be set in the extension, which actually allows this test to log in.
We can add a test that verifies this behavior, but I am somewhat of the opinion that we shouldn't allow setting insecure samesite none cookies in firefox.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, since we use Set-Cookie all the time, the cookie is actually set in firefox because we currently allow SameSite=None and Secure=false to be set in the extension, which actually allows this test to log in.
I would expect this to be a problem for our user if it works in chrome one way and differently in firefox. Shouldn't we be updating the extension to have the same checks / ignore the same attributes as the server-side cookie jar that is using to manage the cookies for chrome & electron ?
const httpsApp = createApp(httpsPort) | ||
const httpsServer = httpsProxy.httpsServer(httpsApp) | ||
httpsPorts.forEach((port) => { | ||
const httpsApp = createApp(port) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to create a full new app for these tests? Could we dumb this down create an express app with the two/three routes needed for this test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is we need a different port on https
, which we currently do not have to verify same-site cookie behavior for differing ports. The added routes in the server aren't the reason for the new app, which I don't think is obvious at all. I'm thinking some documentation/comments are needed here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added some docs in ec8b751
expect(cors.parseUrlIntoDomainTldPort(url)).to.deep.eq(obj) | ||
} | ||
}) | ||
const assertParsesUrlCorrectly = (url, obj) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: named assert but uses expect
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated to expectUrlToBeParsedCorrectly
in a2637b9
const { port: port2, ...parsedUrl2 } = cors.parseUrlIntoDomainTldPort(url2) | ||
|
||
// If HTTPS, ports NEED to match. Otherwise, HTTP ports can be different and are same origin | ||
const doPortsPassSameSchemeCheck = port1 !== port2 ? (port1 !== '443' && port2 !== '443') : true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noob question - what does 443 represent?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default HTTPS port. Only reason I am adding the check here is due to the logic into parseUrlIntoDomainTldPort
. I am wondering for checking same-site capability if we want to go off the protocol scheme as well, since 443
might be SSL, and there could be other ports that are https.
For example, https://www.testme.com:8443
and (https://app.testme.com:4849
|http://app.testme.com:443
) are all same-site, but this function would evaluate false
| true
. Since we infer port fromparseUrlIntoDomainTldPort
, I am not sure this is an issue though
@@ -19,15 +60,15 @@ interface RequestDetails { | |||
// see https://github.com/salesforce/tough-cookie#samesite-cookies | |||
export const getSameSiteContext = (autUrl: string | undefined, requestUrl: string, isAUTFrameRequest: boolean) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const getSameSiteContext = (autUrl: string | undefined, requestUrl: string, isAUTFrameRequest: boolean) => { | |
export const getSameSiteContext = (autUrl: string | undefined, requestUrl: string, isAUTFrameRequest: boolean): Protocol.Network.CookieSameSite => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to pull the CDP types into the proxy, but we could also make a reserved type for this even though it is currently implied
const existingCookies = this.req.headers['cookie'] ? [this.req.headers['cookie']] : [] | ||
const cookiesToAdd = cookies.map((cookie) => `${cookie.key}=${cookie.value}`) | ||
const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext) | ||
const cookiesOnRequestString = (this.req.headers['cookie'] ? this.req.headers['cookie'] : '').split('; ') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const cookiesOnRequestString = (this.req.headers['cookie'] ? this.req.headers['cookie'] : '').split('; ') | |
const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed in 99d5ed2
const cookies = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext) | ||
const existingCookies = this.req.headers['cookie'] ? [this.req.headers['cookie']] : [] | ||
const cookiesToAdd = cookies.map((cookie) => `${cookie.key}=${cookie.value}`) | ||
const applicableCookiesInCookieJar = this.getCookieJar().getCookies(this.req.proxiedUrl, sameSiteContext) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should addCookieJarCookiesToRequest
handle pulling the cookie jar cookies?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured not since it really it just a utility and didn't want to make it state aware.
return doPortsPassSameSchemeCheck && _.isEqual(parsedUrl1, parsedUrl2) | ||
} | ||
|
||
export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookieString: string[] = []): string => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookieString: string[] = []): string => { | |
export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookies: string[] = []): string => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
addressed (somewhat) in 99d5ed2. The name was used below so I went with requestCookieStringArray
} | ||
|
||
beforeEach(() => { | ||
// FIXME: clearing cookies in the browser currently does not clear cookies in the server-side cookie jar |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you log issues for the FIXMES? seems like we should get them on the plan.
User facing changelog
Fixes an issue introduced in
10.3.0
and further exposed in10.4.0
that omitted same-site cookies when URL Scheme, Domain, and Top Level Domain match, but the ports are different (same-site).Additional details
In #22320 we added improved cookie handling that handles cookie management in the proxy server. When we calculate
SameSiteContext
, we were usingSameOriginPolicy
to calculate the context of the request. This is a bit different thanSameSite
, which cookies use to determine context as subdomains and ports do not need to match to be considered same-site.In #22963 we removed the isAUTFrame check in
checkIfNeedsCrossOriginHandling
. In the case of #23132 (comment), I thinkisAUTFrame
used to evaluate tofalse
for the fetch/XHR requests being made, which leveragedSet-Cookie
overX-Set-Cookie
. After10.4.0
, the check was removed andX-Set-Cookie
was being used as it believed the requests needed to handle the cookies in a third party context, when in fact they did not.Until #23551 is implemented, we should likely use
Set-Cookie
, even if the setting of the actual cookie fails, as we currently cannot discern the difference between requests that are specified in the browser with XMLHttpRequest Credentials or fetch credentials and once that aren't (or use more strict fetch credentials likesame-origin
oromit
).Steps to test
Unit tests were added for functions
getSameSiteContext
in@packages/proxy
andurlOriginsMatch
inpackages/network
to verify the behavior is what we expect.Unit tests for verifying duplicate cookie behavior and integration tests with tough-cookie were also added to make sure we integrate correctly.
E2E tests were added in
cookie_behavior.cy.ts
to serve as a base for when we explore #23551 to make sure the correct spec behavior is implemented. There are several tests withFIXME
s as well as comments to what the assertions should be. In the current context, we may optimistically apply cookies to request, as if each request was calledwithCredentials
or withinclude
d credentials, which the tests should demonstrate.How has the user experience changed?
PR Tasks
cypress-documentation
?type definitions
?