Skip to content
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

Merged
merged 25 commits into from
Sep 8, 2022

Conversation

AtofStryker
Copy link
Contributor

@AtofStryker AtofStryker commented Aug 18, 2022

User facing changelog

Fixes an issue introduced in 10.3.0 and further exposed in 10.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 using SameOriginPolicy to calculate the context of the request. This is a bit different than SameSite, 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 think isAUTFrame used to evaluate to false for the fetch/XHR requests being made, which leveraged Set-Cookie over X-Set-Cookie. After 10.4.0, the check was removed and X-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 like same-origin or omit).

Steps to test

Unit tests were added for functions getSameSiteContext in @packages/proxy and urlOriginsMatch in packages/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 with FIXMEs 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 called withCredentials or with included credentials, which the tests should demonstrate.

How has the user experience changed?

PR Tasks

  • Have tests been added/updated?
  • Has the original issue (or this PR, if no issue exists) been tagged with a release in ZenHub? (user-facing changes only)
  • Has a PR for user-facing changes been opened in cypress-documentation?
  • Have API changes been updated in the type definitions?

@cypress-bot
Copy link
Contributor

cypress-bot bot commented Aug 18, 2022

Thanks for taking the time to open a PR!

@AtofStryker AtofStryker linked an issue Aug 18, 2022 that may be closed by this pull request
@AtofStryker AtofStryker changed the title Fix/same site cookie context fix: same site cookie context Aug 18, 2022
@cypress
Copy link

cypress bot commented Aug 18, 2022



Test summary

4020 0 434 0Flakiness 0


Run details

Project cypress
Status Passed
Commit b7e9993
Started Sep 8, 2022 3:10 AM
Ended Sep 8, 2022 3:23 AM
Duration 13:03 💡
OS Linux Debian - 11.3
Browser Webkit 16

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

@AtofStryker AtofStryker self-assigned this Aug 18, 2022
@AtofStryker AtofStryker marked this pull request as ready for review August 18, 2022 21:57
Copy link
Contributor

@chrisbreiding chrisbreiding left a 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.

Comment on lines 216 to 222
this.isFalse = (url1, url2) => {
expect(cors.urlOriginsMatch(url1, url2)).to.be.false
}

this.isTrue = (url1, url2) => {
expect(cors.urlOriginsMatch(url1, url2)).to.be.true
}
Copy link
Contributor

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 ...
    })
  })

Copy link
Contributor Author

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

@AtofStryker AtofStryker force-pushed the fix/same-site-cookie-context branch from f1f8cbb to d02fc1f Compare August 25, 2022 21:43
@AtofStryker AtofStryker changed the title fix: same site cookie context fix: same site cookie context and duplicate cookies Aug 25, 2022
@AtofStryker AtofStryker linked an issue Aug 25, 2022 that may be closed by this pull request
@AtofStryker
Copy link
Contributor Author

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.

@chrisbreiding the tests in cookie_login from what I can see look correct. For the behaviors that could we different or strange, I added a new spec called cookie_behavior in the origin directory that tests several different scenarios of how cookies can be applied. Right now, some of those are incorrect and are documented with FIXMEs, but currently showcase how we currently set/send cookies. It should be really useful when we implement #23551 (which I still need to fill out the description)

@@ -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' }, () => {
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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)
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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) => {
Copy link
Member

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

Copy link
Contributor Author

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
Copy link
Member

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?

Copy link
Contributor Author

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) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const getSameSiteContext = (autUrl: string | undefined, requestUrl: string, isAUTFrameRequest: boolean) => {
export const getSameSiteContext = (autUrl: string | undefined, requestUrl: string, isAUTFrameRequest: boolean): Protocol.Network.CookieSameSite => {

Copy link
Contributor Author

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('; ')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const cookiesOnRequestString = (this.req.headers['cookie'] ? this.req.headers['cookie'] : '').split('; ')
const cookiesOnRequest = (this.req.headers['cookie'] || '').split('; )

Copy link
Contributor Author

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)
Copy link
Member

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?

Copy link
Contributor Author

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 => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookieString: string[] = []): string => {
export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookies: string[] = []): string => {

Copy link
Contributor Author

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
Copy link
Member

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cookie behavior issue after 10.3.0 Prepending Stale Cookies to requests after 10.3
4 participants