From 69ad7cd7e1e222548a706405f6d452b9f87c55a6 Mon Sep 17 00:00:00 2001 From: Tom J Date: Wed, 9 Sep 2020 00:16:23 +0800 Subject: [PATCH] issue #2934 fix enterprise CORS issue (#2935) * issue 2934 fix enterprise CORS issue * fix 0auth login button * refactored build process * add manifest tests * fix 0auth login Co-authored-by: Tom J --- build.sh | 3 +- conf/tsconfig.tooling.json | 5 +- test/source/patterns.ts | 37 +++++++- .../tests/page-recipe/oauth-page-recipe.ts | 7 +- tooling/build-manifests.ts | 39 -------- tooling/build-types-and-manifests.ts | 92 +++++++++++++++++++ tooling/build-types.ts | 43 --------- tooling/fill-values.ts | 2 +- 8 files changed, 132 insertions(+), 96 deletions(-) delete mode 100644 tooling/build-manifests.ts create mode 100644 tooling/build-types-and-manifests.ts delete mode 100644 tooling/build-types.ts diff --git a/build.sh b/build.sh index 6e6281d0d1d..e5e51a0b06c 100755 --- a/build.sh +++ b/build.sh @@ -75,5 +75,4 @@ node ./build/tooling/bundle-content-scripts cp -r $OUTDIR ./build/chrome-enterprise cp -r $OUTDIR ./build/chrome-consumer cp -r $OUTDIR ./build/firefox-consumer -node ./build/tooling/build-manifests -node ./build/tooling/build-types +node ./build/tooling/build-types-and-manifests diff --git a/conf/tsconfig.tooling.json b/conf/tsconfig.tooling.json index 3566107849a..fe30b850b60 100644 --- a/conf/tsconfig.tooling.json +++ b/conf/tsconfig.tooling.json @@ -13,12 +13,11 @@ "outDir": "../build/tooling" }, "files": [ - "../tooling/build-manifests.ts", + "../tooling/build-types-and-manifests.ts", "../tooling/resolve-modules.ts", "../tooling/bundle-content-scripts.ts", "../tooling/fill-values.ts", - "../tooling/tsc-compiler.ts", - "../tooling/build-types.ts" + "../tooling/tsc-compiler.ts" ], "include": [ "../tooling/tslint-rules/*.ts" diff --git a/test/source/patterns.ts b/test/source/patterns.ts index 100bc51c98f..93007b02fec 100644 --- a/test/source/patterns.ts +++ b/test/source/patterns.ts @@ -4,6 +4,10 @@ import * as path from 'path'; import { readFileSync, readdirSync, statSync } from 'fs'; +/** + * This test looks for petterns in the source code, as well as in the built product to look for issues. + */ + let errsFound = 0; const getAllFilesInDir = (dir: string, filePattern: RegExp): string[] => { @@ -29,7 +33,7 @@ const hasErrHandledComment = (line: string) => { return /\/\/ error-handled/.test(line); }; -const validateLine = (line: string, location: string) => { +const validateTypeScriptLine = (line: string, location: string) => { if (line.match(/\.(innerHTML|outerHTML) ?= ?/) && !hasXssComment(line)) { console.error(`unchecked xss in ${location}:\n${line}\n`); errsFound++; @@ -60,12 +64,35 @@ const validateLine = (line: string, location: string) => { } }; -const srcFilePaths = getAllFilesInDir('./extension', /\.ts$/); - -for (const srcFilePath of srcFilePaths) { +/** + * lint problems in TS files - the type of issues that we don't have a linter for + */ +for (const srcFilePath of getAllFilesInDir('./extension', /\.ts$/)) { const lines = readFileSync(srcFilePath).toString().split('\n'); for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) { - validateLine(lines[lineIndex], `${srcFilePath}:${lineIndex + 1}`); + validateTypeScriptLine(lines[lineIndex], `${srcFilePath}:${lineIndex + 1}`); + } +} + +/** + * check for problems in manifest file (because dynamically generated) + * https://github.com/FlowCrypt/flowcrypt-browser/issues/2934 + */ +const expectedPermissions = ["storage", "tabs", "https://*.google.com/*", "https://www.googleapis.com/*", "https://flowcrypt.com/*", "unlimitedStorage"]; +for (const buildType of ['chrome-consumer', 'chrome-enterprise', 'firefox-consumer']) { + const manifest = JSON.parse(readFileSync(`./build/${buildType}/manifest.json`).toString()); + for (const expectedPermission of expectedPermissions) { + if (!manifest.permissions.includes(expectedPermission)) { + if (!(expectedPermission === 'unlimitedStorage' && buildType === 'firefox-consumer')) { + console.error(`Missing permission '${expectedPermission}' in ${buildType}/manifest.json`); + errsFound++; + } + } + } + const gmailCs = manifest.content_scripts.find((cs: any) => cs.matches.includes('https://mail.google.com/*')); + if (!gmailCs || !gmailCs.css.length || !gmailCs.js.length) { + console.error(`Missing content_scripts declaration for Gmail in ${buildType}/manifest.json`); + errsFound++; } } diff --git a/test/source/tests/page-recipe/oauth-page-recipe.ts b/test/source/tests/page-recipe/oauth-page-recipe.ts index 362f8b604c9..7dc2ca7ee59 100644 --- a/test/source/tests/page-recipe/oauth-page-recipe.ts +++ b/test/source/tests/page-recipe/oauth-page-recipe.ts @@ -20,7 +20,7 @@ export class OauthPageRecipe extends PageRecipe { email_confirm_btn: '#identifierNext', auth0_username: '#username', auth0_password: '#password', - auth0_login_btn: '._button-login', + auth0_login_btn: 'button', // old: ._button-login }; try { await oauthPage.waitAny('#Email, #submit_approve_access, #identifierId, .w6VTHd, #profileIdentifier', { timeout: 45 }); @@ -47,9 +47,10 @@ export class OauthPageRecipe extends PageRecipe { } throw new Error('Oauth page didnt close after login. Should increase timeout or await close event'); } - await oauthPage.waitAny([selectors.approve_button, selectors.auth0_login_btn]); - if (await oauthPage.isElementPresent(selectors.auth0_login_btn)) { + await oauthPage.waitAny([selectors.approve_button, selectors.auth0_username]); + if (await oauthPage.isElementPresent(selectors.auth0_username)) { await oauthPage.waitAndType(selectors.auth0_username, auth.email); + console.log(oauthPage.target.url()); await oauthPage.waitAndType(selectors.auth0_password, auth.password!); await oauthPage.waitAndClick(selectors.auth0_login_btn); await oauthPage.waitForNavigationIfAny(); diff --git a/tooling/build-manifests.ts b/tooling/build-manifests.ts deleted file mode 100644 index d5e9da6b90e..00000000000 --- a/tooling/build-manifests.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { readFileSync, writeFileSync } from 'fs'; - -// tslint:disable:no-unsafe-any - -const DIR = './build'; -const version: string = JSON.parse(readFileSync('./package.json').toString()).version; - -const addManifest = (toBuildType: string, transform: (manifest: { [k: string]: any }) => void) => { - const manifest = JSON.parse(readFileSync(`${DIR}/generic-extension-wip/manifest.json`).toString()); - transform(manifest); - writeFileSync(`${DIR}/${toBuildType}/manifest.json`, JSON.stringify(manifest, undefined, 2)); -}; - -addManifest('chrome-consumer', manifest => { - manifest.version = version; -}); - -addManifest('firefox-consumer', manifest => { - manifest.version = version; - manifest.applications = { gecko: { id: 'firefox@cryptup.io', update_url: 'https://flowcrypt.com/api/update/firefox', strict_min_version: '60.0' } }; - manifest.permissions = manifest.permissions.filter((p: string) => p !== 'unlimitedStorage'); - delete manifest.minimum_chrome_version; -}); - -addManifest('chrome-enterprise', manifest => { - manifest.version = version; - manifest.name = 'FlowCrypt for Enterprise'; - manifest.description = 'FlowCrypt Chrome Extension for Enterprise clients (stable)'; - // do not change!! or all user extensions will be disabled in their browser waiting for a new prompt - manifest.permissions = ["storage", "tabs", "https://*.google.com/*", "https://*.flowcrypt.com/*", "unlimitedStorage"]; - for (const csDef of manifest.content_scripts) { - // do not change!! - csDef.matches = csDef.matches.filter((host: string) => host === 'https://mail.google.com/*' || host === '*://accounts.google.com/o/oauth2/approval*'); - } - manifest.content_scripts = manifest.content_scripts.filter((csDef: { matches: string[] }) => csDef.matches.length); // remove empty defs - if (!manifest.content_scripts.length) { - throw new Error('Content script defs ended up empty in enterprise manifest'); - } -}); diff --git a/tooling/build-types-and-manifests.ts b/tooling/build-types-and-manifests.ts new file mode 100644 index 00000000000..8b46a4b0de3 --- /dev/null +++ b/tooling/build-types-and-manifests.ts @@ -0,0 +1,92 @@ +import { readFileSync, writeFileSync } from 'fs'; +import { execSync as exec } from 'child_process'; + +// tslint:disable:no-unsafe-any + +/** + * This file was originally two files: one that edited manifests, and one that copied build folders and edited mock versions + * These steps somewhat overlap, so the two scripts were joined into one below. However, work was not yet done to assimilate them. + */ + +/** + * first, modify appropriate manifests + */ + +const DIR = './build'; +const version: string = JSON.parse(readFileSync('./package.json').toString()).version; + +const addManifest = (toBuildType: string, transform: (manifest: { [k: string]: any }) => void) => { + const manifest = JSON.parse(readFileSync(`${DIR}/generic-extension-wip/manifest.json`).toString()); + transform(manifest); + writeFileSync(`${DIR}/${toBuildType}/manifest.json`, JSON.stringify(manifest, undefined, 2)); +}; + +addManifest('chrome-consumer', manifest => { + manifest.version = version; +}); + +addManifest('firefox-consumer', manifest => { + manifest.version = version; + manifest.applications = { gecko: { id: 'firefox@cryptup.io', update_url: 'https://flowcrypt.com/api/update/firefox', strict_min_version: '60.0' } }; + manifest.permissions = manifest.permissions.filter((p: string) => p !== 'unlimitedStorage'); + delete manifest.minimum_chrome_version; +}); + +addManifest('chrome-enterprise', manifest => { + manifest.version = version; + manifest.name = 'FlowCrypt for Enterprise'; + manifest.description = 'FlowCrypt Chrome Extension for Enterprise clients (stable)'; + // careful - changing this will likely cause all extensions to be disabled in their user's browsers + manifest.permissions = ["storage", "tabs", "https://*.google.com/*", "https://www.googleapis.com/*", "https://flowcrypt.com/*", "unlimitedStorage"]; + for (const csDef of manifest.content_scripts) { + csDef.matches = csDef.matches.filter((host: string) => host === 'https://mail.google.com/*'); + } + manifest.content_scripts = manifest.content_scripts.filter((csDef: { matches: string[] }) => csDef.matches.length); // remove empty defs + if (!manifest.content_scripts.length) { + throw new Error('Content script defs ended up empty in enterprise manifest'); + } +}); + +/** + * second, build copy and edit enterprise and mock versions + */ + +const CHROME_CONSUMER = 'chrome-consumer'; +const CHROME_ENTERPRISE = 'chrome-enterprise'; +const MOCK_HOST: { [buildType: string]: string } = { 'chrome-consumer': 'http://localhost:8001', 'chrome-enterprise': 'http://google.mock.flowcryptlocal.com:8001' }; + +const buildDir = (buildType: string) => `./build/${buildType}`; + +const edit = (filepath: string, editor: (content: string) => string) => { + writeFileSync(filepath, editor(readFileSync(filepath, { encoding: 'utf-8' }))); +}; + +const makeMockBuild = (buildType: string) => { + const mockBuildType = `${buildType}-mock`; + exec(`cp -r ${buildDir(buildType)} ${buildDir(mockBuildType)}`); + const editor = (code: string) => { + return code + .replace(/const (GOOGLE_API_HOST|GOOGLE_OAUTH_SCREEN_HOST) = [^;]+;/g, `const $1 = '${MOCK_HOST[buildType]}';`) + .replace(/const (BACKEND_API_HOST) = [^;]+;/g, `const $1 = 'http://localhost:8001/api/';`) + .replace(/const (ATTESTER_API_HOST) = [^;]+;/g, `const $1 = 'http://localhost:8001/attester/';`) + .replace(/https:\/\/flowcrypt.com\/api\/help\/error/g, 'http://localhost:8001/api/help/error'); + }; + edit(`${buildDir(mockBuildType)}/js/common/core/const.js`, editor); + edit(`${buildDir(mockBuildType)}/js/common/platform/catch.js`, editor); + edit(`${buildDir(mockBuildType)}/js/content_scripts/webmail_bundle.js`, editor); +}; + +const updateEnterpriseBuild = () => { + const constFilepath = `${buildDir(CHROME_ENTERPRISE)}/js/common/core/const.js`; + edit(constFilepath, (code: string) => { + const flavorPattern = /export const FLAVOR = 'consumer';/g; + if (!flavorPattern.test(code)) { + throw new Error(`Expecting to find FLAVOR in ${constFilepath}`); + } + return code.replace(flavorPattern, `export const FLAVOR = 'enterprise';`); + }); +}; + +updateEnterpriseBuild(); +makeMockBuild(CHROME_CONSUMER); +makeMockBuild(CHROME_ENTERPRISE); diff --git a/tooling/build-types.ts b/tooling/build-types.ts deleted file mode 100644 index cadb588e900..00000000000 --- a/tooling/build-types.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { readFileSync, writeFileSync } from 'fs'; - -import { execSync as exec } from 'child_process'; - -const CHROME_CONSUMER = 'chrome-consumer'; -const CHROME_ENTERPRISE = 'chrome-enterprise'; -const MOCK_HOST: { [buildType: string]: string } = { 'chrome-consumer': 'http://localhost:8001', 'chrome-enterprise': 'http://google.mock.flowcryptlocal.com:8001' }; - -const buildDir = (buildType: string) => `./build/${buildType}`; - -const edit = (filepath: string, editor: (content: string) => string) => { - writeFileSync(filepath, editor(readFileSync(filepath, { encoding: 'utf-8' }))); -}; - -const makeMockBuild = (buildType: string) => { - const mockBuildType = `${buildType}-mock`; - exec(`cp -r ${buildDir(buildType)} ${buildDir(mockBuildType)}`); - const editor = (code: string) => { - return code - .replace(/const (GOOGLE_API_HOST|GOOGLE_OAUTH_SCREEN_HOST) = [^;]+;/g, `const $1 = '${MOCK_HOST[buildType]}';`) - .replace(/const (BACKEND_API_HOST) = [^;]+;/g, `const $1 = 'http://localhost:8001/api/';`) - .replace(/const (ATTESTER_API_HOST) = [^;]+;/g, `const $1 = 'http://localhost:8001/attester/';`) - .replace(/https:\/\/flowcrypt.com\/api\/help\/error/g, 'http://localhost:8001/api/help/error'); - }; - edit(`${buildDir(mockBuildType)}/js/common/core/const.js`, editor); - edit(`${buildDir(mockBuildType)}/js/common/platform/catch.js`, editor); - edit(`${buildDir(mockBuildType)}/js/content_scripts/webmail_bundle.js`, editor); -}; - -const updateEnterpriseBuild = () => { - const constFilepath = `${buildDir(CHROME_ENTERPRISE)}/js/common/core/const.js`; - edit(constFilepath, (code: string) => { - const flavorPattern = /export const FLAVOR = 'consumer';/g; - if (!flavorPattern.test(code)) { - throw new Error(`Expecting to find FLAVOR in ${constFilepath}`); - } - return code.replace(flavorPattern, `export const FLAVOR = 'enterprise';`); - }); -}; - -updateEnterpriseBuild(); -makeMockBuild(CHROME_CONSUMER); -makeMockBuild(CHROME_ENTERPRISE); diff --git a/tooling/fill-values.ts b/tooling/fill-values.ts index 6e7d02e8f7c..556c5bce625 100644 --- a/tooling/fill-values.ts +++ b/tooling/fill-values.ts @@ -10,7 +10,7 @@ const { compilerOptions: { outDir: targetDirExtension } } = JSON.parse(readFileS const { compilerOptions: { outDir: targetDirContentScripts } } = JSON.parse(readFileSync('./conf/tsconfig.content_scripts.json').toString()); const { version } = JSON.parse(readFileSync(`./package.json`).toString()); -// mock values for a consumer-mock or enterprise-mock test builds are regex-replaced later in `build-types.ts` +// mock values for a consumer-mock or enterprise-mock test builds are regex-replaced later in `build-types-and-manifests.ts` const replaceables: { needle: RegExp, val: string }[] = [ { needle: /\[BUILD_REPLACEABLE_VERSION\]/g, val: version }, { needle: /\[BUILD_REPLACEABLE_FLAVOR\]/g, val: 'consumer' },