fix: Disallow same-superdomain-origin cy.origin blocks (#24569)

* fix: throw error if the cy.origin origin is in the same superDomainOrigin as top.

* testing test tweaks

* 'fix' cypress in cypress tests

* Inject cross origin in google subdomains when not same-origin

* style tweaks

* Ensure strict same-origin check works for google.

* test fixes

* we don't need the location object when we just want the href.

* what is in a name?

* Address PR Comments
This commit is contained in:
Matt Henkes
2022-11-09 08:29:27 -06:00
committed by GitHub
parent 7093072adf
commit 23299acc88
20 changed files with 595 additions and 521 deletions
@@ -10,6 +10,44 @@ describe('cy.origin', { browser: '!webkit' }, () => {
})
})
it('creates and injects into google subdomains', () => {
// Intercept google to keep our tests independent from google.
cy.intercept('https://www.google.com', {
body: '<html><head><title></title></head><body><p>google.com</p></body></html>',
})
cy.intercept('https://accounts.google.com', {
body: '<html><head><title></title></head><body><p>accounts.google.com</p></body></html>',
})
cy.visit('https://www.google.com')
cy.visit('https://accounts.google.com')
cy.origin('https://accounts.google.com', () => {
cy.window().then((win) => {
expect(win.Cypress).to.exist
})
})
})
it('creates and injects into google subdomains when visiting in an origin block', () => {
// Intercept google to keep our tests independent from google.
cy.intercept('https://www.google.com', {
body: '<html><head><title></title></head><body><p>google.com</p></body></html>',
})
cy.intercept('https://accounts.google.com', {
body: '<html><head><title></title></head><body><p>accounts.google.com</p></body></html>',
})
cy.visit('https://www.google.com')
cy.origin('https://accounts.google.com', () => {
cy.visit('https://accounts.google.com')
cy.window().then((win) => {
expect(win.Cypress).to.exist
})
})
})
it('passes viewportWidth/Height state to the secondary origin', () => {
const expectedViewport = [320, 480]
@@ -1,4 +1,8 @@
describe('cy.origin', { browser: '!webkit' }, () => {
beforeEach(() => {
cy.visit('')
})
describe('successes', () => {
it('succeeds on a localhost domain name', () => {
cy.origin('localhost', () => undefined)
@@ -357,3 +361,83 @@ describe('cy.origin', { browser: '!webkit' }, () => {
})
})
})
describe('cy.origin - external hosts', { browser: '!webkit' }, () => {
describe('successes', () => {
it('succeeds on a complete origin from https using https', () => {
cy.visit('https://www.foobar.com:3502/fixtures/primary-origin.html')
cy.origin('https://www.idp.com:3502', () => undefined)
cy.then(() => {
const expectedSrc = `https://www.idp.com:3502/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://www.idp.com:3502') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
})
})
it('succeeds if url is the super domain as top but the super domain is excepted and must be strictly same origin', () => {
// Intercept google to keep our tests independent from google.
cy.intercept('https://www.google.com', {
body: '<html><head><title></title></head><body><p></body></html>',
})
cy.visit('https://www.google.com')
cy.origin('accounts.google.com', () => undefined)
cy.then(() => {
const expectedSrc = `https://accounts.google.com/__cypress/spec-bridge-iframes`
const iframe = window.top?.document.getElementById('Spec\ Bridge:\ https://accounts.google.com') as HTMLIFrameElement
expect(iframe.src).to.equal(expectedSrc)
})
})
})
describe('errors', () => {
it('errors if the url param is same superDomainOrigin as top', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.origin()` requires the first argument to be a different domain than top. You passed `http://app.foobar.com` to the origin command, while top is at `http://www.foobar.com`.')
done()
})
cy.intercept('http://www.foobar.com', {
body: '<html><head><title></title></head><body><p></body></html>',
})
cy.intercept('http://app.foobar.com', {
body: '<html><head><title></title></head><body><p></body></html>',
})
cy.visit('http://www.foobar.com')
cy.origin('http://app.foobar.com', () => undefined)
})
it('errors if the url param is same origin as top', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include('`cy.origin()` requires the first argument to be a different origin than top. You passed `https://www.google.com` to the origin command, while top is at `https://www.google.com`.')
done()
})
// Intercept google to keep our tests independent from google.
cy.intercept('https://www.google.com', {
body: '<html><head><title></title></head><body><p></body></html>',
})
cy.visit('https://www.google.com')
cy.origin('https://www.google.com', () => undefined)
})
it('errors and does not hang when throwing a mixed content error creating the spec bridge', { defaultCommandTimeout: 50 }, (done) => {
cy.on('fail', (err) => {
expect(err.message).to.include(`\`cy.origin()\` failed to create a spec bridge to communicate with the specified origin. This can happen when you attempt to create a spec bridge to an insecure (http) frame from a secure (https) frame.`)
done()
})
cy.visit('https://www.foobar.com:3502/fixtures/primary-origin.html')
cy.origin('http://www.foobar.com:3500', () => {})
})
})
})
+16 -14
View File
@@ -38,7 +38,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
communicator.userInvocationStack = userInvocationStack
// this command runs for as long as the commands in the secondary
// origin run, so it can't have its own timeout
// origin run, so it can't have its own timeout except in the case where we're creating the spec bridge.
cy.clearTimeout()
if (!config('experimentalSessionAndOrigin')) {
@@ -47,6 +47,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
let options
let callbackFn
const timeout = Cypress.config('defaultCommandTimeout')
if (fn) {
callbackFn = fn
@@ -64,7 +65,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
name: 'origin',
type: 'parent',
message: urlOrDomain,
timeout: 0,
timeout,
// @ts-ignore TODO: revisit once log-grouping has more implementations
}, (_log) => {
log = _log
@@ -84,7 +85,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
const url = new URL(normalizeOrigin(urlOrDomain)).toString()
const location = $Location.create(url)
validator.validateLocation(location, urlOrDomain)
validator.validateLocation(location, urlOrDomain, window.location.href)
const origin = location.origin
@@ -92,7 +93,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
cy.state('currentActiveOrigin', origin)
return new Bluebird((resolve, reject, onCancel) => {
const cleanup = ({ readyForOriginFailed }: {readyForOriginFailed?: boolean} = {}): void => {
const cleanup = (): void => {
cy.state('currentActiveOrigin', undefined)
communicator.off('queue:finished', onQueueFinished)
@@ -108,8 +109,10 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
resolve(unserializableSubjectType ? createUnserializableSubjectProxy(unserializableSubjectType) : subject)
}
const _reject = (err, cleanupOptions: {readyForOriginFailed?: boolean} = {}) => {
cleanup(cleanupOptions)
const _reject = (err) => {
// Prevent cypress from trying to add the function to the error log
err.onFail = () => {}
cleanup()
log?.error(err)
reject(err)
}
@@ -141,9 +144,6 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
wrappedErr.name = err.name
wrappedErr.stack = $stackUtils.replacedStack(wrappedErr, err.stack)
// Prevent cypress from trying to add the function to the error log
wrappedErr.onFail = () => {}
return _reject(wrappedErr)
}
@@ -168,9 +168,15 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
})
}
// If the spec bridge isn't created in time, it likely failed and we shouldn't hang the test.
const timeoutId = setTimeout(() => {
_reject($errUtils.errByPath('origin.failed_to_create_spec_bridge'))
}, timeout)
// fired once the spec bridge is set up and ready to receive messages
communicator.once('bridge:ready', async (_data, { origin: specBridgeOrigin }) => {
if (specBridgeOrigin === origin) {
clearTimeout(timeoutId)
// now that the spec bridge is ready, instantiate Cypress with the current app config and environment variables for initial sync when creating the instance
communicator.toSpecBridge(origin, 'initialize:cypress', {
config: preprocessConfig(Cypress.config()),
@@ -229,11 +235,7 @@ export default (Commands, Cypress: Cypress.Cypress, cy: Cypress.cy, state: State
// It tries to add a bunch of stuff that's not useful and ends up
// messing up the stack that we want on the error
wrappedErr.__stackCleaned__ = true
// Prevent cypress from trying to add the function to the error log
wrappedErr.onFail = () => {}
_reject(wrappedErr, { readyForOriginFailed: true })
_reject(wrappedErr)
}
}
})
@@ -1,6 +1,8 @@
import $utils from '../../../cypress/utils'
import $errUtils from '../../../cypress/error_utils'
import { difference, isPlainObject, isString } from 'lodash'
import type { LocationObject } from '../../../cypress/location'
import * as cors from '@packages/network/lib/cors'
const validOptionKeys = Object.freeze(['args'])
@@ -67,13 +69,35 @@ export class Validator {
return false
}
validateLocation (location, urlOrDomain) {
/**
* Validates the location parameter of the cy.origin call.
* @param originLocation - the location passed into the cy.origin command.
* @param urlOrDomain - the original string param passed in.
* @param specHref - the address of the current spec.
*/
validateLocation (originLocation: LocationObject, urlOrDomain: string, specHref: string): void {
// we don't support query params
if (location.search.length > 0) {
if (originLocation.search.length > 0) {
$errUtils.throwErrByPath('origin.invalid_url_argument', {
onFail: this.log,
args: { arg: $utils.stringify(urlOrDomain) },
})
}
// Users would be better off not using cy.origin if the origin is part of the same super domain.
if (cors.urlMatchesPolicyBasedOnDomain(originLocation.href, specHref)) {
// this._isSameSuperDomainOriginWithExceptions({ originLocation, specLocation })) {
const policy = cors.policyForDomain(originLocation.href)
$errUtils.throwErrByPath('origin.invalid_url_argument_same_origin', {
onFail: this.log,
args: {
originUrl: $utils.stringify(urlOrDomain),
topOrigin: (window.location.origin),
policy,
},
})
}
}
}
@@ -1191,6 +1191,13 @@ export default {
invalid_url_argument: {
message: `${cmd('origin')} requires the first argument to be either a url (\`https://www.example.com/path\`) or a domain name (\`example.com\`). Query parameters are not allowed. You passed: \`{{arg}}\``,
},
invalid_url_argument_same_origin ({ originUrl, topOrigin, policy }) {
return stripIndent`\
${cmd('origin')} requires the first argument to be a different ${policy === 'same-origin' ? 'origin' : 'domain' } than top. You passed \`${originUrl}\` to the origin command, while top is at \`${topOrigin}\`.
Either the intended page was not visited prior to running the cy.origin block or the cy.origin block may not be needed at all.
`
},
invalid_options_argument: {
message: `${cmd('origin')} requires the 'options' argument to be an object. You passed: \`{{arg}}\``,
},
@@ -1260,6 +1267,13 @@ export default {
message: stripIndent`\
${cmd('origin')} could not serialize the thrown value. Please make sure the value being thrown is supported by the structured clone algorithm.`,
},
failed_to_create_spec_bridge: {
message: stripIndent`\
${cmd('origin')} failed to create a spec bridge to communicate with the specified origin. This can happen when you attempt to create a spec bridge to an insecure (http) frame from a secure (https) frame.
Check your Developer Tools Console for the actual error - it should be printed there.
`,
},
unsupported: {
route: {
message: `${cmd('route')} has been deprecated and its use is not supported in the ${cmd('origin')} callback. Consider using ${cmd('intercept')} (outside of the callback) instead.`,
+69 -53
View File
@@ -11,6 +11,8 @@ const debug = debugModule('cypress:network:cors')
// match IP addresses or anything following the last .
const customTldsRe = /(^[\d\.]+$|\.[^\.]+$)/
const strictSameOriginDomains = Object.freeze(['google.com'])
export function getSuperDomain (url) {
const parsed = parseUrlIntoHostProtocolDomainTldPort(url)
@@ -81,30 +83,30 @@ export function getDomainNameFromParsedHost (parsedHost: ParsedHost) {
* same-super-domain-origin: Whether or not a url's scheme, domain, top-level domain, and port match
* same-site: Whether or not a url's scheme, domain, and top-level domain match. @see https://developer.mozilla.org/en-US/docs/Glossary/Site
* @param {Policy} policy - the policy being used
* @param {string} url - the url being compared
* @param {ParsedHostWithProtocolAndHost} props - the props being compared against the url
* @param {string} frameUrl - the url being compared
* @param {ParsedHostWithProtocolAndHost} topProps - the props being compared against the url
* @returns {boolean} whether or not the props and url fit the policy
*/
function urlMatchesPolicyProps ({ policy, url, props }: {
function urlMatchesPolicyProps ({ policy, frameUrl, topProps }: {
policy: Policy
url: string
props: ParsedHostWithProtocolAndHost
frameUrl: string
topProps: ParsedHostWithProtocolAndHost
}): boolean {
if (!policy || !url || !props) {
if (!policy || !frameUrl || !topProps) {
return false
}
const urlProps = parseUrlIntoHostProtocolDomainTldPort(url)
const urlProps = parseUrlIntoHostProtocolDomainTldPort(frameUrl)
switch (policy) {
case 'same-origin': {
// if same origin, all parts of the props needs to match, including subdomain and scheme
return _.isEqual(urlProps, props)
return _.isEqual(urlProps, topProps)
}
case 'same-super-domain-origin':
case 'schemeful-same-site': {
const { port: port1, subdomain: _unused1, ...parsedUrl } = urlProps
const { port: port2, subdomain: _unused2, ...relevantProps } = props
const { port: port2, subdomain: _unused2, ...relevantProps } = topProps
let doPortsPassSameSchemeCheck: boolean
@@ -124,67 +126,81 @@ function urlMatchesPolicyProps ({ policy, url, props }: {
}
}
function urlMatchesPolicy ({ policy, url1, url2 }: {
function urlMatchesPolicy ({ policy, frameUrl, topUrl }: {
policy: Policy
url1: string
url2: string
frameUrl: string
topUrl: string
}): boolean {
if (!policy || !url1 || !url2) {
if (!policy || !frameUrl || !topUrl) {
return false
}
return urlMatchesPolicyProps({
policy,
url: url1,
props: parseUrlIntoHostProtocolDomainTldPort(url2),
frameUrl,
topProps: parseUrlIntoHostProtocolDomainTldPort(topUrl),
})
}
export function urlMatchesOriginProps (url, props) {
return urlMatchesPolicyProps({
policy: 'same-origin',
url,
props,
})
}
export function urlMatchesSuperDomainOriginProps (url, props) {
return urlMatchesPolicyProps({
policy: 'same-super-domain-origin',
url,
props,
})
}
export function urlMatchesSameSiteProps (url: string, props: ParsedHostWithProtocolAndHost) {
return urlMatchesPolicyProps({
policy: 'schemeful-same-site',
url,
props,
})
}
export function urlOriginsMatch (url1: string, url2: string) {
export function urlOriginsMatch (frameUrl: string, topUrl: string): boolean {
return urlMatchesPolicy({
policy: 'same-origin',
url1,
url2,
frameUrl,
topUrl,
})
}
export function urlsSuperDomainOriginMatch (url1: string, url2: string) {
return urlMatchesPolicy({
policy: 'same-super-domain-origin',
url1,
url2,
})
}
export const urlSameSiteMatch = (url1: string, url2: string) => {
export const urlSameSiteMatch = (frameUrl: string, topUrl: string): boolean => {
return urlMatchesPolicy({
policy: 'schemeful-same-site',
url1,
url2,
frameUrl,
topUrl,
})
}
/**
* Returns the policy that will be used for the specified url.
* @param url - the url to check the policy against.
* @returns a Policy string.
*/
export const policyForDomain = (url: string): Policy => {
const obj = parseUrlIntoHostProtocolDomainTldPort(url)
return strictSameOriginDomains.includes(`${obj.domain}.${obj.tld}`) ? 'same-origin' : 'same-super-domain-origin'
}
/**
* Checks the supplied url's against the determined policy.
* The policy is same-super-domain-origin unless the domain is in the list of strict same origin domains,
* in which case the policy is 'same-origin'
* @param frameUrl - The url you are testing the policy for.
* @param topUrl - The url you are testing the policy in context of.
* @returns boolean, true if matching, false if not.
*/
export const urlMatchesPolicyBasedOnDomain = (frameUrl: string, topUrl: string): boolean => {
return urlMatchesPolicy({
policy: policyForDomain(frameUrl),
frameUrl,
topUrl,
})
}
/**
* Checks the supplied url and props against the determined policy.
* The policy is same-super-domain-origin unless the domain is in the list of strict same origin domains,
* in which case the policy is 'same-origin'
* @param frameUrl - The url you are testing the policy for.
* @param topProps - The props of the url you are testing the policy in context of.
* @returns boolean, true if matching, false if not.
*/
export const urlMatchesPolicyBasedOnDomainProps = (frameUrl: string, topProps: ParsedHostWithProtocolAndHost): boolean => {
const obj = parseUrlIntoHostProtocolDomainTldPort(frameUrl)
const policy = strictSameOriginDomains.includes(`${obj.domain}.${obj.tld}`) ? 'same-origin' : 'same-super-domain-origin'
return urlMatchesPolicyProps({
policy,
frameUrl,
topProps,
})
}
+262 -430
View File
@@ -109,328 +109,6 @@ describe('lib/cors', () => {
})
})
context('.urlMatchesOriginProps', () => {
const assertOriginsDoNotMatch = (url, props) => {
expect(cors.urlMatchesOriginProps(url, props)).to.be.false
}
const assertOriginsDoMatch = (url, props) => {
expect(cors.urlMatchesOriginProps(url, props)).to.be.true
}
describe('domain + subdomain', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://staging.google.com')
it('does not match', function () {
assertOriginsDoNotMatch('https://foo.bar:443', props)
assertOriginsDoNotMatch('http://foo.bar:80', props)
assertOriginsDoNotMatch('http://foo.bar', props)
assertOriginsDoNotMatch('http://staging.google.com', props)
assertOriginsDoNotMatch('http://staging.google.com:80', props)
assertOriginsDoNotMatch('https://staging.google2.com:443', props)
assertOriginsDoNotMatch('https://staging.google.net:443', props)
assertOriginsDoNotMatch('https://google.net:443', props)
assertOriginsDoNotMatch('http://google.com', props)
assertOriginsDoNotMatch('https://google.com:443', props)
assertOriginsDoNotMatch('https://foo.google.com:443', props)
assertOriginsDoNotMatch('https://foo.bar.google.com:443', props)
})
it('matches', function () {
assertOriginsDoMatch('https://staging.google.com:443', props)
})
})
describe('public suffix', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://example.gitlab.io')
it('does not match', function () {
assertOriginsDoNotMatch('http://example.gitlab.io', props)
assertOriginsDoNotMatch('https://foo.gitlab.io:443', props)
assertOriginsDoNotMatch('https://foo.example.gitlab.io:443', props)
})
it('matches', function () {
assertOriginsDoMatch('https://example.gitlab.io:443', props)
})
})
describe('localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://localhost:4200')
it('does not match', function () {
assertOriginsDoNotMatch('http://localhost:4201', props)
assertOriginsDoNotMatch('http://localhoss:4200', props)
})
it('matches', function () {
assertOriginsDoMatch('http://localhost:4200', props)
})
})
describe('app.localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://app.localhost:4200')
it('does not match', function () {
assertOriginsDoNotMatch('http://app.localhost:4201', props)
assertOriginsDoNotMatch('http://app.localhoss:4200', props)
assertOriginsDoNotMatch('http://name.app.localhost:4200', props)
})
it('matches', function () {
assertOriginsDoMatch('http://app.localhost:4200', props)
})
})
describe('local', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://brian.dev.local')
it('does not match', function () {
assertOriginsDoNotMatch('https://brian.dev.local:443', props)
assertOriginsDoNotMatch('https://brian.dev.local', props)
assertOriginsDoNotMatch('http://brian.dev2.local:81', props)
assertOriginsDoNotMatch('http://jennifer.dev.local:80', props)
assertOriginsDoNotMatch('http://jennifer.dev.local', props)
})
it('matches', function () {
assertOriginsDoMatch('http://brian.dev.local:80', props)
})
})
describe('ip address', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://192.168.5.10')
it('does not match', function () {
assertOriginsDoNotMatch('http://192.168.5.10:443', props)
assertOriginsDoNotMatch('https://192.168.5.10', props)
assertOriginsDoNotMatch('http://193.168.5.10', props)
assertOriginsDoNotMatch('http://193.168.5.10:80', props)
})
it('matches', function () {
assertOriginsDoMatch('http://192.168.5.10', props)
assertOriginsDoMatch('http://192.168.5.10:80', props)
})
})
})
context('.urlMatchesSuperDomainOriginProps', () => {
const assertSuperDomainOriginDoesNotMatch = (url, props) => {
expect(cors.urlMatchesSuperDomainOriginProps(url, props)).to.be.false
}
const assertSuperDomainOriginDoesMatch = (url, props) => {
expect(cors.urlMatchesSuperDomainOriginProps(url, props)).to.be.true
}
describe('domain + subdomain', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://staging.google.com')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('https://foo.bar:443', props)
assertSuperDomainOriginDoesNotMatch('http://foo.bar:80', props)
assertSuperDomainOriginDoesNotMatch('http://foo.bar', props)
assertSuperDomainOriginDoesNotMatch('http://staging.google.com', props)
assertSuperDomainOriginDoesNotMatch('http://staging.google.com:80', props)
assertSuperDomainOriginDoesNotMatch('https://staging.google2.com:443', props)
assertSuperDomainOriginDoesNotMatch('https://staging.google.net:443', props)
assertSuperDomainOriginDoesNotMatch('https://google.net:443', props)
assertSuperDomainOriginDoesNotMatch('http://google.com', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('https://staging.google.com:443', props)
assertSuperDomainOriginDoesMatch('https://google.com:443', props)
assertSuperDomainOriginDoesMatch('https://foo.google.com:443', props)
assertSuperDomainOriginDoesMatch('https://foo.bar.google.com:443', props)
})
})
describe('public suffix', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://example.gitlab.io')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('http://example.gitlab.io', props)
assertSuperDomainOriginDoesNotMatch('https://foo.gitlab.io:443', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('https://example.gitlab.io:443', props)
assertSuperDomainOriginDoesMatch('https://foo.example.gitlab.io:443', props)
})
})
describe('localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://localhost:4200')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('http://localhoss:4200', props)
assertSuperDomainOriginDoesNotMatch('http://localhost:4201', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('http://localhost:4200', props)
})
})
describe('app.localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://app.localhost:4200')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('http://app.localhoss:4200', props)
assertSuperDomainOriginDoesNotMatch('http://app.localhost:4201', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('http://app.localhost:4200', props)
assertSuperDomainOriginDoesMatch('http://name.app.localhost:4200', props)
})
})
describe('local', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://brian.dev.local')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('https://brian.dev.local:443', props)
assertSuperDomainOriginDoesNotMatch('https://brian.dev.local', props)
assertSuperDomainOriginDoesNotMatch('http://brian.dev2.local:81', props)
assertSuperDomainOriginDoesNotMatch('http://brian.dev.local:8081', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('http://brian.dev.local:80', props)
assertSuperDomainOriginDoesMatch('http://jennifer.dev.local:80', props)
assertSuperDomainOriginDoesMatch('http://jennifer.dev.local', props)
})
})
describe('ip address', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://192.168.5.10')
it('does not match', function () {
assertSuperDomainOriginDoesNotMatch('http://192.168.5.10:443', props)
assertSuperDomainOriginDoesNotMatch('https://192.168.5.10', props)
assertSuperDomainOriginDoesNotMatch('http://193.168.5.10', props)
assertSuperDomainOriginDoesNotMatch('http://193.168.5.10:80', props)
assertSuperDomainOriginDoesNotMatch('http://192.168.5.10:8081', props)
})
it('matches', function () {
assertSuperDomainOriginDoesMatch('http://192.168.5.10', props)
assertSuperDomainOriginDoesMatch('http://192.168.5.10:80', props)
})
})
})
context('.urlMatchesSameSiteProps', () => {
const assertSameSiteDoesNotMatch = (url, props) => {
expect(cors.urlMatchesSameSiteProps(url, props)).to.be.false
}
const assertSameSiteDoesMatch = (url, props) => {
expect(cors.urlMatchesSameSiteProps(url, props)).to.be.true
}
describe('domain + subdomain', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://staging.google.com')
it('does not match', function () {
assertSameSiteDoesNotMatch('https://foo.bar:443', props)
assertSameSiteDoesNotMatch('http://foo.bar:80', props)
assertSameSiteDoesNotMatch('http://foo.bar', props)
assertSameSiteDoesNotMatch('http://staging.google.com', props)
assertSameSiteDoesNotMatch('http://staging.google.com:80', props)
assertSameSiteDoesNotMatch('https://staging.google2.com:443', props)
assertSameSiteDoesNotMatch('https://staging.google.net:443', props)
assertSameSiteDoesNotMatch('https://google.net:443', props)
assertSameSiteDoesNotMatch('http://google.com', props)
})
it('matches', function () {
assertSameSiteDoesMatch('https://staging.google.com:443', props)
assertSameSiteDoesMatch('https://google.com:443', props)
assertSameSiteDoesMatch('https://foo.google.com:443', props)
assertSameSiteDoesMatch('https://foo.bar.google.com:443', props)
})
})
describe('public suffix', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://example.gitlab.io')
it('does not match', function () {
assertSameSiteDoesNotMatch('http://example.gitlab.io', props)
assertSameSiteDoesNotMatch('https://foo.gitlab.io:443', props)
})
it('matches', function () {
assertSameSiteDoesMatch('https://example.gitlab.io:443', props)
assertSameSiteDoesMatch('https://foo.example.gitlab.io:443', props)
})
})
describe('localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://localhost:4200')
it('does not match', function () {
assertSameSiteDoesNotMatch('http://localhoss:4200', props)
})
it('matches', function () {
assertSameSiteDoesMatch('http://localhost:4201', props)
assertSameSiteDoesMatch('http://localhost:4200', props)
})
})
describe('app.localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://app.localhost:4200')
it('does not match', function () {
assertSameSiteDoesNotMatch('http://app.localhoss:4200', props)
})
it('matches', function () {
assertSameSiteDoesMatch('http://app.localhost:4200', props)
assertSameSiteDoesMatch('http://name.app.localhost:4200', props)
assertSameSiteDoesMatch('http://app.localhost:4201', props)
})
})
describe('local', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://brian.dev.local')
it('does not match', function () {
assertSameSiteDoesNotMatch('https://brian.dev.local:443', props)
assertSameSiteDoesNotMatch('https://brian.dev.local', props)
assertSameSiteDoesNotMatch('http://brian.dev2.local:81', props)
})
it('matches', function () {
assertSameSiteDoesMatch('http://brian.dev.local:80', props)
assertSameSiteDoesMatch('http://jennifer.dev.local:80', props)
assertSameSiteDoesMatch('http://jennifer.dev.local', props)
assertSameSiteDoesMatch('http://brian.dev.local:8081', props)
})
})
describe('ip address', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://192.168.5.10')
it('does not match', function () {
assertSameSiteDoesNotMatch('http://192.168.5.10:443', props)
assertSameSiteDoesNotMatch('https://192.168.5.10', props)
assertSameSiteDoesNotMatch('http://193.168.5.10', props)
assertSameSiteDoesNotMatch('http://193.168.5.10:80', props)
})
it('matches', function () {
assertSameSiteDoesMatch('http://192.168.5.10', props)
assertSameSiteDoesMatch('http://192.168.5.10:80', props)
assertSameSiteDoesMatch('http://192.168.5.10:8081', props)
})
})
})
context('.urlOriginsMatch', () => {
const assertOriginsDoNotMatch = (url1, url2) => {
expect(cors.urlOriginsMatch(url1, url2)).to.be.false
@@ -535,114 +213,6 @@ describe('lib/cors', () => {
})
})
context('.urlsSuperDomainOriginMatch', () => {
const assertsUrlsAreNotASuperDomainOriginMatch = (url1, url2) => {
expect(cors.urlsSuperDomainOriginMatch(url1, url2)).to.be.false
}
const assertsUrlsAreASuperDomainOriginMatch = (url1, url2) => {
expect(cors.urlsSuperDomainOriginMatch(url1, url2)).to.be.true
}
describe('domain + subdomain', () => {
const url = 'https://staging.google.com'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('https://foo.bar:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://foo.bar:80', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://foo.bar', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://staging.google.com', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://staging.google.com:80', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://staging.google2.com:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://staging.google.net:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://google.net:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://google.com', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('https://staging.google.com:443', url)
assertsUrlsAreASuperDomainOriginMatch('https://google.com:443', url)
assertsUrlsAreASuperDomainOriginMatch('https://foo.google.com:443', url)
assertsUrlsAreASuperDomainOriginMatch('https://foo.bar.google.com:443', url)
})
})
describe('public suffix', () => {
const url = 'https://example.gitlab.io'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('http://example.gitlab.io', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://foo.gitlab.io:443', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('https://example.gitlab.io:443', url)
assertsUrlsAreASuperDomainOriginMatch('https://foo.example.gitlab.io:443', url)
})
})
describe('localhost', () => {
const url = 'http://localhost:4200'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('http://localhoss:4200', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://localhost:4201', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('http://localhost:4200', url)
})
})
describe('app.localhost', () => {
const url = 'http://app.localhost:4200'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('http://app.localhoss:4200', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://app.localhost:4201', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('http://app.localhost:4200', url)
assertsUrlsAreASuperDomainOriginMatch('http://name.app.localhost:4200', url)
})
})
describe('local', () => {
const url = 'http://brian.dev.local'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('https://brian.dev.local:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://brian.dev.local', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://brian.dev2.local:81', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://brian.dev.local:8081', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('http://jennifer.dev.local', url)
assertsUrlsAreASuperDomainOriginMatch('http://jennifer.dev.local:80', url)
assertsUrlsAreASuperDomainOriginMatch('http://jennifer.dev.local', url)
})
})
describe('ip address', () => {
const url = 'http://192.168.5.10'
it('does not match', function () {
assertsUrlsAreNotASuperDomainOriginMatch('http://192.168.5.10:443', url)
assertsUrlsAreNotASuperDomainOriginMatch('https://192.168.5.10', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://193.168.5.10', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://193.168.5.10:80', url)
assertsUrlsAreNotASuperDomainOriginMatch('http://192.168.5.10:12345', url)
})
it('matches', function () {
assertsUrlsAreASuperDomainOriginMatch('http://192.168.5.10', url)
assertsUrlsAreASuperDomainOriginMatch('http://192.168.5.10:80', url)
})
})
})
context('.urlSameSiteMatch', () => {
const assertsUrlsAreNotSameSite = (url1, url2) => {
expect(cors.urlSameSiteMatch(url1, url2)).to.be.false
@@ -751,6 +321,268 @@ describe('lib/cors', () => {
})
})
context('.urlMatchesPolicyBasedOnDomain', () => {
const assertsUrlsAreNotAPolicyMatch = (url1, url2) => {
expect(cors.urlMatchesPolicyBasedOnDomain(url1, url2)).to.be.false
}
const assertsUrlsAreAPolicyOriginMatch = (url1, url2) => {
expect(cors.urlMatchesPolicyBasedOnDomain(url1, url2)).to.be.true
}
describe('domain + subdomain', () => {
const url = 'https://staging.gurgle.com'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://foo.bar:443', url)
assertsUrlsAreNotAPolicyMatch('http://foo.bar:80', url)
assertsUrlsAreNotAPolicyMatch('http://foo.bar', url)
assertsUrlsAreNotAPolicyMatch('http://staging.gurgle.com', url)
assertsUrlsAreNotAPolicyMatch('http://staging.gurgle.com:80', url)
assertsUrlsAreNotAPolicyMatch('https://staging.gurgle2.com:443', url)
assertsUrlsAreNotAPolicyMatch('https://staging.gurgle.net:443', url)
assertsUrlsAreNotAPolicyMatch('https://gurgle.net:443', url)
assertsUrlsAreNotAPolicyMatch('http://gurgle.com', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://staging.gurgle.com:443', url)
assertsUrlsAreAPolicyOriginMatch('https://gurgle.com:443', url)
assertsUrlsAreAPolicyOriginMatch('https://foo.gurgle.com:443', url)
assertsUrlsAreAPolicyOriginMatch('https://foo.bar.gurgle.com:443', url)
})
})
describe('google (strict same-origin policy)', () => {
const url = 'https://staging.google.com'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://foo.bar:443', url)
assertsUrlsAreNotAPolicyMatch('http://foo.bar:80', url)
assertsUrlsAreNotAPolicyMatch('http://foo.bar', url)
assertsUrlsAreNotAPolicyMatch('http://staging.google.com', url)
assertsUrlsAreNotAPolicyMatch('http://staging.google.com:80', url)
assertsUrlsAreNotAPolicyMatch('https://staging.google2.com:443', url)
assertsUrlsAreNotAPolicyMatch('https://staging.google.net:443', url)
assertsUrlsAreNotAPolicyMatch('https://google.net:443', url)
assertsUrlsAreNotAPolicyMatch('http://google.com', url)
assertsUrlsAreNotAPolicyMatch('https://google.com:443', url)
assertsUrlsAreNotAPolicyMatch('https://foo.google.com:443', url)
assertsUrlsAreNotAPolicyMatch('https://foo.bar.google.com:443', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://staging.google.com:443', url)
})
})
describe('public suffix', () => {
const url = 'https://example.gitlab.io'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://example.gitlab.io', url)
assertsUrlsAreNotAPolicyMatch('https://foo.gitlab.io:443', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://example.gitlab.io:443', url)
assertsUrlsAreAPolicyOriginMatch('https://foo.example.gitlab.io:443', url)
})
})
describe('localhost', () => {
const url = 'http://localhost:4200'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://localhoss:4200', url)
assertsUrlsAreNotAPolicyMatch('http://localhost:4201', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://localhost:4200', url)
})
})
describe('app.localhost', () => {
const url = 'http://app.localhost:4200'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://app.localhoss:4200', url)
assertsUrlsAreNotAPolicyMatch('http://app.localhost:4201', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://app.localhost:4200', url)
assertsUrlsAreAPolicyOriginMatch('http://name.app.localhost:4200', url)
})
})
describe('local', () => {
const url = 'http://brian.dev.local'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://brian.dev.local:443', url)
assertsUrlsAreNotAPolicyMatch('https://brian.dev.local', url)
assertsUrlsAreNotAPolicyMatch('http://brian.dev2.local:81', url)
assertsUrlsAreNotAPolicyMatch('http://brian.dev.local:8081', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local', url)
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local:80', url)
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local', url)
})
})
describe('ip address', () => {
const url = 'http://192.168.5.10'
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://192.168.5.10:443', url)
assertsUrlsAreNotAPolicyMatch('https://192.168.5.10', url)
assertsUrlsAreNotAPolicyMatch('http://193.168.5.10', url)
assertsUrlsAreNotAPolicyMatch('http://193.168.5.10:80', url)
assertsUrlsAreNotAPolicyMatch('http://192.168.5.10:12345', url)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://192.168.5.10', url)
assertsUrlsAreAPolicyOriginMatch('http://192.168.5.10:80', url)
})
})
})
context('.urlMatchesPolicyBasedOnDomainProps', () => {
const assertsUrlsAreNotAPolicyMatch = (url1, props) => {
expect(cors.urlMatchesPolicyBasedOnDomainProps(url1, props)).to.be.false
}
const assertsUrlsAreAPolicyOriginMatch = (url1, props) => {
expect(cors.urlMatchesPolicyBasedOnDomainProps(url1, props)).to.be.true
}
describe('domain + subdomain', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://staging.gurgle.com')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://foo.bar:443', props)
assertsUrlsAreNotAPolicyMatch('http://foo.bar:80', props)
assertsUrlsAreNotAPolicyMatch('http://foo.bar', props)
assertsUrlsAreNotAPolicyMatch('http://staging.gurgle.com', props)
assertsUrlsAreNotAPolicyMatch('http://staging.gurgle.com:80', props)
assertsUrlsAreNotAPolicyMatch('https://staging.gurgle2.com:443', props)
assertsUrlsAreNotAPolicyMatch('https://staging.gurgle.net:443', props)
assertsUrlsAreNotAPolicyMatch('https://gurgle.net:443', props)
assertsUrlsAreNotAPolicyMatch('http://gurgle.com', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://staging.gurgle.com:443', props)
assertsUrlsAreAPolicyOriginMatch('https://gurgle.com:443', props)
assertsUrlsAreAPolicyOriginMatch('https://foo.gurgle.com:443', props)
assertsUrlsAreAPolicyOriginMatch('https://foo.bar.gurgle.com:443', props)
})
})
describe('google (strict same-origin policy)', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://staging.google.com')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://foo.bar:443', props)
assertsUrlsAreNotAPolicyMatch('http://foo.bar:80', props)
assertsUrlsAreNotAPolicyMatch('http://foo.bar', props)
assertsUrlsAreNotAPolicyMatch('http://staging.google.com', props)
assertsUrlsAreNotAPolicyMatch('http://staging.google.com:80', props)
assertsUrlsAreNotAPolicyMatch('https://staging.google2.com:443', props)
assertsUrlsAreNotAPolicyMatch('https://staging.google.net:443', props)
assertsUrlsAreNotAPolicyMatch('https://google.net:443', props)
assertsUrlsAreNotAPolicyMatch('http://google.com', props)
assertsUrlsAreNotAPolicyMatch('https://google.com:443', props)
assertsUrlsAreNotAPolicyMatch('https://foo.google.com:443', props)
assertsUrlsAreNotAPolicyMatch('https://foo.bar.google.com:443', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://staging.google.com:443', props)
})
})
describe('public suffix', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('https://example.gitlab.io')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://example.gitlab.io', props)
assertsUrlsAreNotAPolicyMatch('https://foo.gitlab.io:443', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('https://example.gitlab.io:443', props)
assertsUrlsAreAPolicyOriginMatch('https://foo.example.gitlab.io:443', props)
})
})
describe('localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://localhost:4200')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://localhoss:4200', props)
assertsUrlsAreNotAPolicyMatch('http://localhost:4201', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://localhost:4200', props)
})
})
describe('app.localhost', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://app.localhost:4200')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://app.localhoss:4200', props)
assertsUrlsAreNotAPolicyMatch('http://app.localhost:4201', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://app.localhost:4200', props)
assertsUrlsAreAPolicyOriginMatch('http://name.app.localhost:4200', props)
})
})
describe('local', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://brian.dev.local')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('https://brian.dev.local:443', props)
assertsUrlsAreNotAPolicyMatch('https://brian.dev.local', props)
assertsUrlsAreNotAPolicyMatch('http://brian.dev2.local:81', props)
assertsUrlsAreNotAPolicyMatch('http://brian.dev.local:8081', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local', props)
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local:80', props)
assertsUrlsAreAPolicyOriginMatch('http://jennifer.dev.local', props)
})
})
describe('ip address', () => {
const props = cors.parseUrlIntoHostProtocolDomainTldPort('http://192.168.5.10')
it('does not match', function () {
assertsUrlsAreNotAPolicyMatch('http://192.168.5.10:443', props)
assertsUrlsAreNotAPolicyMatch('https://192.168.5.10', props)
assertsUrlsAreNotAPolicyMatch('http://193.168.5.10', props)
assertsUrlsAreNotAPolicyMatch('http://193.168.5.10:80', props)
assertsUrlsAreNotAPolicyMatch('http://192.168.5.10:12345', props)
})
it('matches', function () {
assertsUrlsAreAPolicyOriginMatch('http://192.168.5.10', props)
assertsUrlsAreAPolicyOriginMatch('http://192.168.5.10:80', props)
})
})
})
context('.urlMatchesOriginProtectionSpace', () => {
const assertMatchesOriginProtectionSpace = (urlStr, origin) => {
expect(urlStr, `the url: '${urlStr}' did not match origin protection space: '${origin}'`).to.satisfy(() => {
@@ -159,7 +159,7 @@ const MaybeEndRequestWithBufferedResponse: RequestMiddleware = function () {
this.debug('ending request with buffered response')
// NOTE: Only inject fullCrossOrigin here if experimental is on and
// the super domain origins do not match in order to keep parity with cypress application reloads
this.res.wantsInjection = this.config.experimentalSessionAndOrigin && buffer.isCrossSuperDomainOrigin ? 'fullCrossOrigin' : 'full'
this.res.wantsInjection = this.config.experimentalSessionAndOrigin && buffer.urlDoesNotMatchPolicyBasedOnDomain ? 'fullCrossOrigin' : 'full'
return this.onResponse(buffer.response, buffer.stream)
}
@@ -55,9 +55,9 @@ function getNodeCharsetFromResponse (headers: IncomingHttpHeaders, body: Buffer,
return 'latin1'
}
function reqMatchesSuperDomainOrigin (req: CypressIncomingRequest, remoteState) {
function reqMatchesPolicyBasedOnDomain (req: CypressIncomingRequest, remoteState) {
if (remoteState.strategy === 'http') {
return cors.urlMatchesSuperDomainOriginProps(req.proxiedUrl, remoteState.props)
return cors.urlMatchesPolicyBasedOnDomainProps(req.proxiedUrl, remoteState.props)
}
if (remoteState.strategy === 'file') {
@@ -250,7 +250,7 @@ const SetInjectionLevel: ResponseMiddleware = function () {
this.debug('determine injection')
const isReqMatchSuperDomainOrigin = reqMatchesSuperDomainOrigin(this.req, this.remoteStates.current())
const isReqMatchSuperDomainOrigin = reqMatchesPolicyBasedOnDomain(this.req, this.remoteStates.current())
const getInjectionLevel = () => {
if (this.incomingRes.headers['x-cypress-file-server-error'] && !this.res.isInitial) {
this.debug('- partial injection (x-cypress-file-server-error)')
@@ -259,11 +259,11 @@ const SetInjectionLevel: ResponseMiddleware = function () {
}
// NOTE: Only inject fullCrossOrigin if the super domain origins do not match in order to keep parity with cypress application reloads
const isCrossSuperDomainOrigin = !reqMatchesSuperDomainOrigin(this.req, this.remoteStates.getPrimary())
const urlDoesNotMatchPolicyBasedOnDomain = !reqMatchesPolicyBasedOnDomain(this.req, this.remoteStates.getPrimary())
const isAUTFrame = this.req.isAUTFrame
const isHTMLLike = isHTML || isRenderedHTML
if (this.config.experimentalSessionAndOrigin && isCrossSuperDomainOrigin && isAUTFrame && isHTMLLike) {
if (this.config.experimentalSessionAndOrigin && urlDoesNotMatchPolicyBasedOnDomain && isAUTFrame && isHTMLLike) {
this.debug('- cross origin injection')
return 'fullCrossOrigin'
+1 -1
View File
@@ -12,7 +12,7 @@ export type HttpBuffer = {
response: IncomingMessage
stream: Readable
url: string
isCrossSuperDomainOrigin: boolean
urlDoesNotMatchPolicyBasedOnDomain: boolean
}
const stripPort = (url) => {
@@ -548,7 +548,7 @@ describe('http/request-middleware', () => {
it('sets wantsInjection to full when a request is buffered', async () => {
const buffers = new HttpBuffers()
const buffer = { url: 'https://www.cypress.io/', isCrossSuperDomainOrigin: false } as HttpBuffer
const buffer = { url: 'https://www.cypress.io/', urlDoesNotMatchPolicyBasedOnDomain: false } as HttpBuffer
buffers.set(buffer)
@@ -568,7 +568,7 @@ describe('http/request-middleware', () => {
it('sets wantsInjection to fullCrossOrigin when a cross origin request is buffered and experimentalSessionAndOrigin=true', async () => {
const buffers = new HttpBuffers()
const buffer = { url: 'https://www.cypress.io/', isCrossSuperDomainOrigin: true } as HttpBuffer
const buffer = { url: 'https://www.cypress.io/', urlDoesNotMatchPolicyBasedOnDomain: true } as HttpBuffer
buffers.set(buffer)
@@ -591,7 +591,7 @@ describe('http/request-middleware', () => {
it('sets wantsInjection to full when a cross origin request is buffered and experimentalSessionAndOrigin=false', async () => {
const buffers = new HttpBuffers()
const buffer = { url: 'https://www.cypress.io/', isCrossSuperDomainOrigin: true } as HttpBuffer
const buffer = { url: 'https://www.cypress.io/', urlDoesNotMatchPolicyBasedOnDomain: true } as HttpBuffer
buffers.set(buffer)
@@ -614,7 +614,7 @@ describe('http/request-middleware', () => {
it('wantsInjection is not set when the request is not buffered', async () => {
const buffers = new HttpBuffers()
const buffer = { url: 'https://www.cypress.io/', isCrossSuperDomainOrigin: true } as HttpBuffer
const buffer = { url: 'https://www.cypress.io/', urlDoesNotMatchPolicyBasedOnDomain: true } as HttpBuffer
buffers.set(buffer)
+2 -1
View File
@@ -39,13 +39,14 @@ module.exports = {
})
},
handleCrossOriginIframe (req, res) {
handleCrossOriginIframe (req, res, namespace) {
const iframePath = cwd('lib', 'html', 'spec-bridge-iframe.html')
const domain = cors.getSuperDomain(req.proxiedUrl)
const iframeOptions = {
domain,
title: `Cypress for ${domain}`,
namespace,
}
debug('cross origin iframe with options %o', iframeOptions)
@@ -8,6 +8,6 @@
<script type="text/javascript">
document.domain = '{{domain}}';
</script>
<script src="/__cypress/runner/cypress_cross_origin_runner.js"></script>
<script src="/{{namespace}}/runner/cypress_cross_origin_runner.js"></script>
</body>
</html>
+1 -1
View File
@@ -91,7 +91,7 @@ export class RemoteStates {
if (_.isString(urlOrState)) {
const remoteOrigin = uri.origin(urlOrState)
const { subdomain: _unused, ...remoteProps } = cors.parseUrlIntoHostProtocolDomainTldPort(remoteOrigin)
const remoteProps = cors.parseUrlIntoHostProtocolDomainTldPort(remoteOrigin)
if ((urlOrState === '<root>') || !fullyQualifiedRe.test(urlOrState)) {
state = {
+7 -1
View File
@@ -89,10 +89,16 @@ export const createRoutesE2E = ({
res.sendFile(file, { etag: false })
})
// TODO: The below route is not technically correct for cypress in cypress tests.
// We should be using 'config.namespace' to provide the namespace instead of hard coding __cypress, however,
// In the runner when we create the spec bridge we have no knowledge of the namespace used by the server so
// we create a spec bridge for the namespace of the server specified in the config, but that server hasn't been created.
// To fix this I think we need to find a way to listen in the cypress in cypress server for routes from the server the
// cypress instance thinks should exist, but that's outside the current scope.
routesE2E.get('/__cypress/spec-bridge-iframes', (req, res) => {
debug('handling cross-origin iframe for domain: %s', req.hostname)
files.handleCrossOriginIframe(req, res)
files.handleCrossOriginIframe(req, res, config.namespace)
})
return routesE2E
+3 -3
View File
@@ -307,10 +307,10 @@ export class ServerE2E extends ServerBase<SocketE2E> {
// TODO: think about moving this logic back into the frontend so that the driver can be in control
// of when to buffer and set the remote state
if (isOk && details.isHtml) {
const isCrossSuperDomainOrigin = options.hasAlreadyVisitedUrl && !cors.urlsSuperDomainOriginMatch(primaryRemoteState.origin, newUrl || '') || options.isFromSpecBridge
const urlDoesNotMatchPolicyBasedOnDomain = options.hasAlreadyVisitedUrl && !cors.urlMatchesPolicyBasedOnDomain(primaryRemoteState.origin, newUrl || '') || options.isFromSpecBridge
if (!handlingLocalFile) {
this._remoteStates.set(newUrl as string, options, !isCrossSuperDomainOrigin)
this._remoteStates.set(newUrl as string, options, !urlDoesNotMatchPolicyBasedOnDomain)
}
const responseBufferStream = new stream.PassThrough({
@@ -325,7 +325,7 @@ export class ServerE2E extends ServerBase<SocketE2E> {
details,
originalUrl,
response: incomingRes,
isCrossSuperDomainOrigin,
urlDoesNotMatchPolicyBasedOnDomain,
})
} else {
// TODO: move this logic to the driver too for
@@ -2628,7 +2628,7 @@ describe('Routes', () => {
})
})
it('injects document.domain on matching super domains but different subdomain', function () {
it('does not inject document.domain on matching super domains but different subdomain - when the domain is set to strict same origin (google)', function () {
nock('http://mail.google.com')
.get('/iframe')
.reply(200, '<html><head></head></html>', {
@@ -2647,7 +2647,7 @@ describe('Routes', () => {
const body = cleanResponseBody(res.body)
expect(body).to.eq('<html><head> <script type=\'text/javascript\'> document.domain = \'google.com\'; </script></head></html>')
expect(body).to.eq('<html><head></head></html>')
})
})
@@ -2791,6 +2791,35 @@ describe('Routes', () => {
})
})
context('content injection', () => {
beforeEach(function () {
return this.setup('http://www.foo.com')
})
it('injects document.domain on matching super domains but different subdomain - non google domain', function () {
nock('http://mail.foo.com')
.get('/iframe')
.reply(200, '<html><head></head></html>', {
'Content-Type': 'text/html',
})
return this.rp({
url: 'http://mail.foo.com/iframe',
headers: {
'Cookie': '__cypress.initial=false',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
})
.then((res) => {
expect(res.statusCode).to.eq(200)
const body = cleanResponseBody(res.body)
expect(body).to.eq('<html><head> <script type=\'text/javascript\'> document.domain = \'foo.com\'; </script></head></html>')
})
})
})
context('security rewriting', () => {
describe('on by default', () => {
beforeEach(function () {
@@ -606,6 +606,7 @@ describe('Server', () => {
domain: 'go',
tld: 'com',
port: '80',
subdomain: 'espn',
protocol: 'http:',
},
})
@@ -923,6 +924,7 @@ describe('Server', () => {
domain: 'google',
tld: 'com',
port: '80',
subdomain: null,
protocol: 'http:',
},
})
@@ -954,6 +956,7 @@ describe('Server', () => {
domain: 'cypress',
port: '80',
tld: 'io',
subdomain: null,
protocol: 'http:',
},
origin: 'http://cypress.io',
@@ -983,7 +986,7 @@ describe('Server', () => {
const buffer = this.buffers.take('http://www.cypress.io/')
expect(buffer).to.not.be.empty
expect(buffer.isCrossSuperDomainOrigin).to.be.true
expect(buffer.urlDoesNotMatchPolicyBasedOnDomain).to.be.true
// Verify the secondary remote state is returned
expect(this.server.remoteStates.current()).to.deep.eq({
@@ -992,6 +995,7 @@ describe('Server', () => {
domain: 'cypress',
port: '80',
tld: 'io',
subdomain: 'www',
protocol: 'http:',
},
origin: 'http://www.cypress.io',
@@ -1026,6 +1030,7 @@ describe('Server', () => {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
@@ -1057,6 +1062,7 @@ describe('Server', () => {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
@@ -1093,6 +1099,7 @@ describe('Server', () => {
domain: '',
port: '3500',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
origin: 'http://localhost:3500',
@@ -1182,6 +1189,7 @@ describe('Server', () => {
domain: 'google',
tld: 'com',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
@@ -1265,6 +1273,7 @@ describe('Server', () => {
domain: 'google',
tld: 'com',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
@@ -1339,6 +1348,7 @@ describe('Server', () => {
domain: 'google',
tld: 'com',
port: '80',
subdomain: 'www',
protocol: 'http:',
},
})
@@ -1384,6 +1394,7 @@ describe('Server', () => {
domain: 'foobar',
tld: 'com',
port: '8443',
subdomain: 'www',
protocol: 'https:',
},
})
@@ -1458,6 +1469,7 @@ describe('Server', () => {
domain: 'foobar',
tld: 'com',
port: '8443',
subdomain: 'www',
protocol: 'https:',
},
})
@@ -1511,6 +1523,7 @@ describe('Server', () => {
domain: '',
tld: 's3.amazonaws.com',
port: '443',
subdomain: null,
protocol: 'https:',
},
})
@@ -1591,6 +1604,7 @@ describe('Server', () => {
domain: '',
tld: 's3.amazonaws.com',
port: '443',
subdomain: null,
protocol: 'https:',
},
})
@@ -29,6 +29,7 @@ describe('remote states', () => {
port: '3500',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -53,6 +54,7 @@ describe('remote states', () => {
port: '3500',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -71,6 +73,7 @@ describe('remote states', () => {
port: '3500',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -91,6 +94,7 @@ describe('remote states', () => {
port: '3500',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -111,6 +115,7 @@ describe('remote states', () => {
port: '3500',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -161,6 +166,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'https:',
},
})
@@ -183,6 +189,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'https:',
},
})
@@ -207,6 +214,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'https:',
},
})
@@ -232,6 +240,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'https:',
},
})
@@ -250,6 +259,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'prod',
protocol: 'https:',
},
})
@@ -268,6 +278,7 @@ describe('remote states', () => {
port: '443',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'https:',
},
})
@@ -286,6 +297,7 @@ describe('remote states', () => {
port: '80',
domain: 'google',
tld: 'com',
subdomain: 'staging',
protocol: 'http:',
},
})
@@ -304,6 +316,7 @@ describe('remote states', () => {
port: '4200',
domain: '',
tld: 'localhost',
subdomain: null,
protocol: 'http:',
},
})
@@ -346,6 +359,7 @@ describe('remote states', () => {
port: '80',
domain: 'foobar',
tld: 'com',
subdomain: 'www',
protocol: 'http:',
},
}
@@ -2,7 +2,7 @@
// REPLACE THIS COMMENT FOR HOT RELOAD
describe('simple origin', () => {
it('passes', () => {
cy.origin('http://localhost:4455', () => {
cy.origin('http://foobar:4455', () => {
cy.log('log me once')
cy.log('log me twice')
})