Skip to content

Commit

Permalink
chore: add utility functions in proxy to be used in the near future i…
Browse files Browse the repository at this point in the history
…n the request/response middleware(s)
  • Loading branch information
AtofStryker committed Sep 19, 2022
1 parent eb819e1 commit d6ee900
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 1 deletion.
36 changes: 36 additions & 0 deletions packages/proxy/lib/http/util/cookies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,49 @@ import type Debug from 'debug'
import { URL } from 'url'
import { cors } from '@packages/network'
import { AutomationCookie, Cookie, CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies'
import { calculateSiteContext } from './top-simulation'
import type { RequestCredentialLevel, RequestResourceType } from '../../types'

interface RequestDetails {
url: string
isAUTFrame: boolean
needsCrossOriginHandling: boolean
}

export const shouldAttachAndSetCookies = (requestUrl: string, AUTUrl: string | undefined, resourceType?: RequestResourceType, credentialLevel?: RequestCredentialLevel): boolean => {
if (!AUTUrl) return false

const siteContext = calculateSiteContext(requestUrl, AUTUrl)

switch (resourceType) {
case 'fetch':
// never attach cookies regardless of siteContext if omit is optioned
if (credentialLevel === 'omit') {
return false
}

// if the siteContext is same-origin and the credential status is undefined, same-origin, or include, attach cookies.
// Otherwise, if the credentials are set to include, attach cookies
if (siteContext === 'same-origin' || credentialLevel === 'include') {
return true
}

return false
case 'xhr':
// if context is same-origin regardless of credential status, attach cookies
// otherwise, if withCredentials is set to true, attach cookies

if (siteContext === 'same-origin' || credentialLevel) {
return true
}

return false
default:
// if we cannot determine a resource level, we likely should store the cookie as it is a navigation or another event
return true
}
}

export const addCookieJarCookiesToRequest = (applicableCookieJarCookies: Cookie[] = [], requestCookieStringArray: string[] = []): string => {
const cookieMap = new Map<string, Cookie>()
const requestCookies: Cookie[] = requestCookieStringArray.map((cookie) => CookieJar.parse(cookie)).filter((cookie) => cookie !== undefined) as Cookie[]
Expand Down
30 changes: 30 additions & 0 deletions packages/proxy/lib/http/util/top-simulation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { urlOriginsMatch, urlSameSiteMatch } from '@packages/network/lib/cors'
import type { HttpMiddlewareThis } from '../index'
import type { SecFetchSite } from '../../types'

export const doesTopNeedToBeSimulated = <T>(ctx: HttpMiddlewareThis<T>): boolean => {
const currentAUTUrl = ctx.getAUTUrl()

// if the AUT url is undefined for whatever reason, return false as we do not want to complicate top simulation
if (!currentAUTUrl) {
return false
}

// only simulate top if the AUT is NOT the primary origin, meaning that we should treat the AUT as top
const doesTopNeedToSimulating = !ctx.remoteStates.isPrimaryOrigin(currentAUTUrl)

return doesTopNeedToSimulating
}

// @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Site
export const calculateSiteContext = (url1: string, url2: string, isUserInteraction = false): SecFetchSite => {
if (urlOriginsMatch(url1, url2)) {
return 'same-origin'
}

if (urlSameSiteMatch(url1, url2)) {
return 'same-site'
}

return isUserInteraction ? 'none' : 'cross-site'
}
6 changes: 6 additions & 0 deletions packages/proxy/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@ export type CypressIncomingRequest = Request & {
isAUTFrame: boolean
}

export type RequestResourceType = 'fetch' | 'xhr'

export type RequestCredentialLevel = 'same-origin' | 'include' | 'omit' | boolean

export type CypressWantsInjection = 'full' | 'fullCrossOrigin' | 'partial' | false

export type SecFetchSite = 'same-origin' | 'same-site' | 'cross-site' | 'none'

/**
* An outgoing response to an incoming request to the Cypress web server.
*/
Expand Down
74 changes: 73 additions & 1 deletion packages/proxy/test/unit/http/util/cookies.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai'
import { getSameSiteContext } from '../../../../lib/http/util/cookies'
import { getSameSiteContext, shouldAttachAndSetCookies } from '../../../../lib/http/util/cookies'

context('getSameSiteContext', () => {
describe('calculates the same site context correctly for', () => {
Expand Down Expand Up @@ -169,3 +169,75 @@ context('getSameSiteContext', () => {
})
})
})

context('shouldAttachAndSetCookies', () => {
const autUrl = 'http://localhost:8080'

context('fetch', () => {
it('returns false if credentials are set to omit, regardless of site context', () => {
// same-origin
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'fetch', 'omit')).to.be.false
// same-site
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'fetch', 'omit')).to.be.false
// cross-site
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/test-request', autUrl, 'fetch', 'omit')).to.be.false
})

it('returns true if credentials are set to "include", regardless of site context', () => {
// same-origin
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'fetch', 'include')).to.be.true
// same-site
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'fetch', 'include')).to.be.true
// cross-site
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/test-request', autUrl, 'fetch', 'include')).to.be.true
})

it('returns true if credentials are set to "same-origin" and the site context is "same-origin"', () => {
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'fetch', 'same-origin')).to.be.true
})

it('returns false if credentials are set to "same-origin" (default), but the site context is "same-site"', () => {
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'fetch', 'same-origin')).to.be.false
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'fetch')).to.be.false
})

it('returns false if credentials are set to "same-origin" (default), but the site context is "cross-site"', () => {
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/test-request', autUrl, 'fetch', 'same-origin')).to.be.false
})
})

context('xhr', () => {
it('returns true if credentials are set to true, regardless of site context', () => {
// same-origin
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'xhr', true)).to.be.true
// same-site
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'xhr', true)).to.be.true
// cross-site
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/test-request', autUrl, 'xhr', true)).to.be.true
})

it('returns true if the site context is same-origin, regardless of credential level', () => {
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'xhr', true)).to.be.true
expect(shouldAttachAndSetCookies('http://localhost:8080/test-request', autUrl, 'xhr', false)).to.be.true
})

it('returns false if site context is same-site and "withCredentials" is set to false', () => {
expect(shouldAttachAndSetCookies('http://localhost:8081/test-request', autUrl, 'xhr', false)).to.be.false
})

it('returns false if site context is cross-site and "withCredentials" is set to false', () => {
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/test-request', autUrl, 'xhr', false)).to.be.false
})
})

context('misc', () => {
it('returns true if the resource type is unknown (could be a navigation request to set top level cookies', () => {
// possibly a navigation request for a document or another resource. If this is the case, attach cookies based on the siteContext and cookies should be attached regardless
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/index.html', autUrl)).to.be.true
})

it('return false if the AUT url is undefined', () => {
expect(shouldAttachAndSetCookies('http://www.foobar.com:3500/index.html', undefined)).to.be.false
})
})
})
58 changes: 58 additions & 0 deletions packages/proxy/test/unit/http/util/top-simulation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { expect } from 'chai'
import sinon from 'sinon'
import { HttpMiddlewareThis } from '../../../../lib/http'
import { calculateSiteContext, doesTopNeedToBeSimulated } from '../../../../lib/http/util/top-simulation'

context('.doesTopNeedToBeSimulated', () => {
const autUrl = 'http://localhost:8080'

it('returns false when URL matches the AUT Url origin policy and the AUT Url exists', () => {
const mockCtx: HttpMiddlewareThis<any> = {
getAUTUrl: sinon.stub().returns(autUrl),
remoteStates: {
isPrimaryOrigin: sinon.stub().returns(true),
},
}

expect(doesTopNeedToBeSimulated(mockCtx)).to.be.false
})

it('returns false when AUT Url is not defined, regardless of primary origin stack', () => {
const mockCtx: HttpMiddlewareThis<any> = {
getAUTUrl: sinon.stub().returns(undefined),
}

expect(doesTopNeedToBeSimulated(mockCtx)).to.be.false
})

it('returns true when AUT Url is defined but AUT Url no longer matches the primary origin', () => {
const mockCtx: HttpMiddlewareThis<any> = {
getAUTUrl: sinon.stub().returns(autUrl),
remoteStates: {
isPrimaryOrigin: sinon.stub().returns(false),
},
}

expect(doesTopNeedToBeSimulated(mockCtx)).to.be.true
})
})

context('.calculateSiteContext', () => {
const autUrl = 'https://staging.google.com'

it('calculates same-origin correctly for same-origin / same-site urls', () => {
expect(calculateSiteContext(autUrl, 'https://staging.google.com')).to.equal('same-origin')
})

it('calculates same-site correctly for cross-origin / same-site urls', () => {
expect(calculateSiteContext(autUrl, 'https://app.google.com')).to.equal('same-site')
})

it('calculates cross-site correctly for cross-origin / cross-site urls', () => {
expect(calculateSiteContext(autUrl, 'https://staging.google2.com')).to.equal('cross-site')
})

it('returns "none" if the interaction is triggered by the user, regardless of other properties', () => {
expect(calculateSiteContext(autUrl, 'https://staging.google2.com', true)).to.equal('none')
})
})

0 comments on commit d6ee900

Please sign in to comment.