From d8796bc11b9016317613a2af325c4faebc00cc6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hernawan=20Fa=C3=AFz=20Abdillah?= <3030950+Abdillah@users.noreply.github.com> Date: Wed, 8 Sep 2021 01:40:59 +0700 Subject: [PATCH] fix(server): Fix EACCES on extension installation if copied from read-only location (#17800) Co-authored-by: Jennifer Shehane Co-authored-by: Zach Bloomquist Co-authored-by: Chris Breiding --- packages/server/lib/browsers/chrome.ts | 1 + packages/server/lib/browsers/utils.ts | 4 ++ .../server/test/unit/browsers/chrome_spec.js | 41 +++++++++++++++++++ .../server/test/unit/browsers/firefox_spec.ts | 35 ++++++++++++++++ 4 files changed, 81 insertions(+) diff --git a/packages/server/lib/browsers/chrome.ts b/packages/server/lib/browsers/chrome.ts index 955119d02e..266d91b1c5 100644 --- a/packages/server/lib/browsers/chrome.ts +++ b/packages/server/lib/browsers/chrome.ts @@ -375,6 +375,7 @@ export = { // copy the extension src to the extension dist await utils.copyExtension(pathToExtension, extensionDest) + await fs.chmod(extensionBg, 0o0644) await fs.writeFileAsync(extensionBg, str) return extensionDest diff --git a/packages/server/lib/browsers/utils.ts b/packages/server/lib/browsers/utils.ts index 0aef5c5f0c..283f6a04b5 100644 --- a/packages/server/lib/browsers/utils.ts +++ b/packages/server/lib/browsers/utils.ts @@ -224,6 +224,10 @@ export = { .then(() => { debug('copied extension') + // ensure write access before overwriting + return fs.chmod(extensionBg, 0o0644) + }) + .then(() => { // and overwrite background.js with the final string bytes return fs.writeFileAsync(extensionBg, str) }) diff --git a/packages/server/test/unit/browsers/chrome_spec.js b/packages/server/test/unit/browsers/chrome_spec.js index da7398848c..dead82ad20 100644 --- a/packages/server/test/unit/browsers/chrome_spec.js +++ b/packages/server/test/unit/browsers/chrome_spec.js @@ -1,6 +1,9 @@ require('../../spec_helper') const os = require('os') +const mockfs = require('mock-fs') +const path = require('path') +const _ = require('lodash') const extension = require('@packages/extension') const launch = require('@packages/launcher/lib/browsers') @@ -59,6 +62,7 @@ describe('lib/browsers/chrome', () => { }) afterEach(function () { + mockfs.restore() expect(this.criClient.ensureMinimumProtocolVersion).to.be.calledOnce }) @@ -228,6 +232,43 @@ describe('lib/browsers/chrome', () => { }) }) + it('install extension and ensure write access', function () { + mockfs({ + [path.resolve(`${__dirname }../../../../../extension/dist`)]: { + 'background.js': mockfs.file({ + mode: 0o0444, + }), + }, + }) + + const getFile = function (path) { + return _.reduce(_.compact(_.split(path, '/')), (acc, item) => { + return acc.getItem(item) + }, mockfs.getMockRoot()) + } + + chrome._writeExtension.restore() + utils.getProfileDir.restore() + + const profilePath = '/home/foo/snap/chromium/current' + const fullPath = `${profilePath}/Cypress/chromium-stable/interactive` + + this.readJson.withArgs(`${fullPath}/Default/Preferences`).rejects({ code: 'ENOENT' }) + this.readJson.withArgs(`${fullPath}/Default/Secure Preferences`).rejects({ code: 'ENOENT' }) + this.readJson.withArgs(`${fullPath}/Local State`).rejects({ code: 'ENOENT' }) + + return chrome.open({ + isHeadless: false, + isHeaded: false, + profilePath, + name: 'chromium', + channel: 'stable', + }, 'http://', {}, this.automation) + .then(() => { + expect((getFile(fullPath).getMode()) & 0o0700).to.be.above(0o0500) + }) + }) + it('cleans up an unclean browser profile exit status', function () { this.readJson.withArgs('/profile/dir/Default/Preferences').resolves({ profile: { diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index b17113bcb3..68e7cfaed9 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -10,6 +10,8 @@ import sinon from 'sinon' import * as firefox from '../../../lib/browsers/firefox' import firefoxUtil from '../../../lib/browsers/firefox-util' +const path = require('path') +const _ = require('lodash') const mockfs = require('mock-fs') const FirefoxProfile = require('firefox-profile') const launch = require('@packages/launcher/lib/browsers') @@ -213,6 +215,39 @@ describe('lib/browsers/firefox', () => { }) }) + it('writes extension and ensure write access', function () { + mockfs({ + [path.resolve(`${__dirname }../../../../../extension/dist`)]: { + 'background.js': mockfs.file({ + mode: 0o444, + }), + }, + [`${process.env.HOME }/.config/Cypress/cy/test/browsers/firefox-stable/interactive/CypressExtension`]: { + 'background.js': mockfs.file({ + content: 'abcn', + mode: 0o444, + }), + }, + [path.resolve(`${__dirname }/../../extension`)]: { 'abc': 'test' }, + '/path/to/appData/firefox-stable/interactive': { + 'xulstore.json': '[foo xulstore.json]', + 'chrome': { 'userChrome.css': '[foo userChrome.css]' }, + }, + }) + + utils.writeExtension.restore() + + const getFile = function (path) { + return _.reduce(_.compact(_.split(path, '/')), (acc, item) => { + return acc.getItem(item) + }, mockfs.getMockRoot()) + } + + return firefox.open(this.browser, 'http://', this.options).then(() => { + expect(getFile(`${process.env.HOME }/.config/Cypress/cy/test/browsers/firefox-stable/interactive/CypressExtension/background.js`).getMode()).to.be.equals(0o644) + }) + }) + // TODO: pick open port for debugger it.skip('finds remote port for firefox debugger', function () { return firefox.open(this.browser, 'http://', this.options).then(() => {