From c00b6551ca46e6239ac39982844da09b0f25e5d7 Mon Sep 17 00:00:00 2001 From: Marcin Rataj Date: Wed, 5 Dec 2018 16:20:33 +0100 Subject: [PATCH] fix: Origin in API requests under Chromium 72 Chromium Beta 72 sends Origin: chrome-extension://{uid} which triggers false-positive Forbidden 403 in go-ipfs Closes https://github.com/ipfs-shipyard/ipfs-companion/issues/630 --- add-on/src/lib/ipfs-request.js | 15 ++++-- .../lib/ipfs-request-workarounds.test.js | 51 +++++++++++++++++-- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/add-on/src/lib/ipfs-request.js b/add-on/src/lib/ipfs-request.js index 3c665ec83..0b4f1289f 100644 --- a/add-on/src/lib/ipfs-request.js +++ b/add-on/src/lib/ipfs-request.js @@ -122,15 +122,22 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru // by js-ipfs-api running in WebExtension context to remove need // for manual whitelisting Access-Control-Allow-Origin at go-ipfs // More info: - // Chrome: https://github.com/ipfs-shipyard/ipfs-companion/pull/616 // Firefox: https://github.com/ipfs-shipyard/ipfs-companion/issues/622 + // Chromium 71: https://github.com/ipfs-shipyard/ipfs-companion/pull/616 + // Chromium 72: https://github.com/ipfs-shipyard/ipfs-companion/issues/630 const isWebExtensionOrigin = (origin) => { - // Chromium + // console.log(`origin=${origin}, webExtensionOrigin=${webExtensionOrigin}`) + // Chromium <= 71 returns opaque Origin as defined in + // https://html.spec.whatwg.org/multipage/origin.html#ascii-serialisation-of-an-origin if (origin == null || origin === 'null') { return true } - // Firefox Nightly 65 sets moz-extension://{extension-installation-id} - if (origin && origin.startsWith('moz-extension://') && new URL(origin).origin === webExtensionOrigin) { + // Firefox Nightly 65 sets moz-extension://{extension-installation-id} + // Chromium Beta 72 sets chrome-extension://{uid} + if (origin && + (origin.startsWith('moz-extension://') || + origin.startsWith('chrome-extension://')) && + new URL(origin).origin === webExtensionOrigin) { return true } return false diff --git a/test/functional/lib/ipfs-request-workarounds.test.js b/test/functional/lib/ipfs-request-workarounds.test.js index 250e4f01e..477450778 100644 --- a/test/functional/lib/ipfs-request-workarounds.test.js +++ b/test/functional/lib/ipfs-request-workarounds.test.js @@ -13,17 +13,25 @@ const { optionDefaults } = require('../../../add-on/src/lib/options') // const nodeTypes = ['external', 'embedded'] describe('modifyRequest processing', function () { - let state, dnslinkResolver, ipfsPathValidator, modifyRequest, runtime + let state, getState, dnslinkResolver, ipfsPathValidator, modifyRequest, runtime before(function () { + // stub URL.origin in test context to return something other than null + Object.defineProperty(URL.prototype, 'origin', { + get: function () { + let fakeOrigin = this.href.split('/') + if (fakeOrigin.length >= 3) { + return fakeOrigin.slice(0, 3).join('/') + } + } + }) global.URL = URL global.browser = browser - browser.runtime.getURL.withArgs('/').returns('moz-extension://0f334731-19e3-42f8-85e2-03dbf50026df/') }) beforeEach(async function () { state = initState(optionDefaults) - const getState = () => state + getState = () => state dnslinkResolver = createDnslinkResolver(getState) runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests ipfsPathValidator = createIpfsPathValidator(getState, dnslinkResolver) @@ -47,7 +55,13 @@ describe('modifyRequest processing', function () { }) describe('a request to /api/v0/ with Origin=moz-extension://{extension-installation-id}', function () { - it('should remove Origin header ', function () { + it('should remove Origin header with moz-extension://', async function () { + // set vendor-specific Origin for WebExtension context + browser.runtime.getURL.withArgs('/').returns('moz-extension://0f334731-19e3-42f8-85e2-03dbf50026df/') + // ensure clean modifyRequest + runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests + modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime) + // test const bogusOriginHeader = { name: 'Origin', value: 'moz-extension://0f334731-19e3-42f8-85e2-03dbf50026df' } const request = { requestHeaders: [ bogusOriginHeader ], @@ -60,8 +74,34 @@ describe('modifyRequest processing', function () { }) }) + describe('a request to /api/v0/ with Origin=chrome-extension://{extension-installation-id}', function () { + it('should remove Origin header with chrome-extension://', async function () { + // set vendor-specific Origin for WebExtension context + browser.runtime.getURL.withArgs('/').returns('chrome-extension://trolrorlrorlrol/') + // ensure clean modifyRequest + runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests + modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime) + // test + const bogusOriginHeader = { name: 'Origin', value: 'chrome-extension://trolrorlrorlrol' } + const request = { + requestHeaders: [ bogusOriginHeader ], + type: 'xmlhttprequest', + url: `${state.apiURLString}api/v0/id` + } + modifyRequest.onBeforeRequest(request) // executes before onBeforeSendHeaders, may mutate state + expect(modifyRequest.onBeforeSendHeaders(request).requestHeaders).to.not.include(bogusOriginHeader) + browser.runtime.getURL.flush() + }) + }) + describe('a request to /api/v0/ with Origin=null', function () { - it('should remove Origin header ', function () { + it('should remove Origin header ', async function () { + // set vendor-specific Origin for WebExtension context + browser.runtime.getURL.withArgs('/').returns(undefined) + // ensure clean modifyRequest + runtime = Object.assign({}, await createRuntimeChecks(browser)) // make it mutable for tests + modifyRequest = createRequestModifier(getState, dnslinkResolver, ipfsPathValidator, runtime) + // test const bogusOriginHeader = { name: 'Origin', value: 'null' } const request = { requestHeaders: [ bogusOriginHeader ], @@ -70,6 +110,7 @@ describe('modifyRequest processing', function () { } modifyRequest.onBeforeRequest(request) // executes before onBeforeSendHeaders, may mutate state expect(modifyRequest.onBeforeSendHeaders(request).requestHeaders).to.not.include(bogusOriginHeader) + browser.runtime.getURL.flush() }) })