diff --git a/extension/chrome/dev/ci_unit_test.ts b/extension/chrome/dev/ci_unit_test.ts index 0ec442b6fe7..ed0a3e56f1d 100644 --- a/extension/chrome/dev/ci_unit_test.ts +++ b/extension/chrome/dev/ci_unit_test.ts @@ -9,6 +9,7 @@ import { ApiErr } from '../../js/common/api/shared/api-error.js'; import { WellKnownHostMeta } from '../../js/common/api/account-servers/well-known-host-meta.js'; import { Mime } from '../../js/common/core/mime.js'; import { Att } from '../../js/common/core/att.js'; +import { Wkd } from '../../js/common/api/key-server/wkd.js'; /** * importing all libs that are tested in ci tests @@ -21,7 +22,8 @@ const libs: any[] = [ AttUI, Buf, KeyUtil, - Mime + Mime, + Wkd ]; // add them to global scope so ci can use them diff --git a/extension/js/common/api/key-server/wkd.ts b/extension/js/common/api/key-server/wkd.ts index bffa9916e73..17b990ebd49 100644 --- a/extension/js/common/api/key-server/wkd.ts +++ b/extension/js/common/api/key-server/wkd.ts @@ -19,16 +19,23 @@ export class Wkd extends Api { // https://www.sektioneins.de/en/blog/18-11-23-gnupg-wkd.html // https://metacode.biz/openpgp/web-key-directory - constructor(private myOwnDomain: string) { + public port: number | undefined; + private protocol: string; + + constructor(private myOwnDomain: string, protocol = 'https') { super(); + this.protocol = protocol; } public lookupEmail = async (email: string): Promise => { - const parts = email.toLowerCase().split('@'); - if (parts.length > 2) { + const parts = email.split('@'); + if (parts.length !== 2) { return { pubkey: null, pgpClient: null }; } const [user, recipientDomain] = parts; + if (!user || !recipientDomain) { + return { pubkey: null, pgpClient: null }; + } if (!opgp) { // pgp_block.htm does not have openpgp loaded // the particular usecase (auto-loading pubkeys to verify signatures) is not that important, @@ -36,33 +43,56 @@ export class Wkd extends Api { // the proper fix would be to run encodeZBase32 through background scripts return { pubkey: null, pgpClient: null }; } - const hu = opgp.util.encodeZBase32(await opgp.crypto.hash.digest(opgp.enums.hash.sha1, Buf.fromUtfStr(user))); - // todo - could also search on `https://openpgpkey.{domain}/.well-known/openpgpkey/{domain}/hu/{hu}?l={user}` - const url = `https://${recipientDomain}/.well-known/openpgpkey/hu/${hu}?l=${encodeURIComponent(user)}`; - let binary: Buf; + const directDomain = recipientDomain.toLowerCase(); + const advancedDomainPrefix = (directDomain === 'localhost') ? '' : 'openpgpkey.'; + const hu = opgp.util.encodeZBase32(await opgp.crypto.hash.digest(opgp.enums.hash.sha1, Buf.fromUtfStr(user.toLowerCase()))); + const directHost = (typeof this.port === 'undefined') ? directDomain : `${directDomain}:${this.port}`; + const advancedHost = `${advancedDomainPrefix}${directHost}`; + const userPart = `hu/${hu}?l=${encodeURIComponent(user)}`; + const advancedUrl = `${this.protocol}://${advancedHost}/.well-known/openpgpkey/${directDomain}`; + const directUrl = `${this.protocol}://${directHost}/.well-known/openpgpkey`; + let response = await this.urlLookup(advancedUrl, userPart); + if (!response.buf && response.hasPolicy) { + return { pubkey: null, pgpClient: null }; // do not retry direct if advanced had a policy file + } + if (!response.buf) { + response = await this.urlLookup(directUrl, userPart); + } + if (!response.buf) { + return { pubkey: null, pgpClient: null }; // do not retry direct if advanced had a policy file + } + const { keys: [key], errs } = await KeyUtil.readMany(response.buf); + if (errs.length || !key || !key.emails.some(x => x.toLowerCase() === email.toLowerCase())) { + return { pubkey: null, pgpClient: null }; + } + // if recipient uses same domain, we assume they use flowcrypt + const pgpClient = this.myOwnDomain === recipientDomain ? 'flowcrypt' : 'pgp-other'; try { - binary = await Wkd.download(url, undefined, 4); + const pubkey = KeyUtil.armor(key); + return { pubkey, pgpClient }; } catch (e) { - if (ApiErr.isNotFound(e) || ApiErr.isNetErr(e)) { - return { pubkey: null, pgpClient: null }; - } - Catch.report(`Wkd.lookupEmail err: ${String(e)}`); return { pubkey: null, pgpClient: null }; } - const { keys: [key], errs } = await KeyUtil.readMany(binary); - if (errs.length || !key) { - return { pubkey: null, pgpClient: null }; + } + + private urlLookup = async (methodUrlBase: string, userPart: string): Promise<{ hasPolicy: boolean, buf?: Buf }> => { + try { + await Wkd.download(`${methodUrlBase}/policy`, undefined, 4); + } catch (e) { + return { hasPolicy: false }; } - console.info(`Loaded Public Key from WKD for ${email}: ${url}`); - let pubkey: string; try { - pubkey = KeyUtil.armor(key); + const buf = await Wkd.download(`${methodUrlBase}/${userPart}`, undefined, 4); + if (buf.length) { + console.info(`Loaded WKD url ${methodUrlBase}/${userPart} and will try to extract Public Keys`); + } + return { hasPolicy: true, buf }; } catch (e) { - return { pubkey: null, pgpClient: null }; + if (!ApiErr.isNotFound(e)) { + Catch.report(`Wkd.lookupEmail error retrieving key ${methodUrlBase}/${userPart}: ${String(e)}`); + } + return { hasPolicy: true }; } - // if recipient uses same domain, we assume they use flowcrypt - const pgpClient = this.myOwnDomain === recipientDomain ? 'flowcrypt' : 'pgp-other'; - return { pubkey, pgpClient }; } } diff --git a/test/source/browser/browser-pool.ts b/test/source/browser/browser-pool.ts index 0b5f2ba150e..7ad9cdbca5f 100644 --- a/test/source/browser/browser-pool.ts +++ b/test/source/browser/browser-pool.ts @@ -124,11 +124,11 @@ export class BrowserPool { }); } - public withNewBrowserTimeoutAndRetry = async (cb: (t: AvaContext, browser: BrowserHandle) => void, t: AvaContext, consts: Consts) => { + public withNewBrowserTimeoutAndRetry = async (cb: (t: AvaContext, browser: BrowserHandle) => void, t: AvaContext, consts: Consts, flag?: 'FAILING') => { const withTimeouts = newWithTimeoutsFunc(consts); const attemptDebugHtmls: string[] = []; - t.totalAttempts = consts.ATTEMPTS; - for (let attemptNumber = 1; attemptNumber <= consts.ATTEMPTS; attemptNumber++) { + t.totalAttempts = flag === 'FAILING' ? 1 : consts.ATTEMPTS; + for (let attemptNumber = 1; attemptNumber <= t.totalAttempts; attemptNumber++) { t.attemptNumber = attemptNumber; t.attemptText = `(attempt ${t.attemptNumber} of ${t.totalAttempts})`; try { @@ -136,7 +136,7 @@ export class BrowserPool { try { await withTimeouts(this.cbWithTimeout(async () => await cb(t, browser), consts.TIMEOUT_EACH_RETRY)); await this.throwOnRetryFlagAndReset(t); - if (attemptDebugHtmls.length) { + if (attemptDebugHtmls.length && flag !== 'FAILING') { // don't debug known failures addDebugHtml(`

Test (later succeeded): ${Util.htmlEscape(t.title)}

${attemptDebugHtmls.join('')}`); } return; @@ -148,17 +148,19 @@ export class BrowserPool { await browser.close(); } } catch (err) { - this.processTestError(err, t, attemptDebugHtmls); + this.processTestError(err, t, attemptDebugHtmls, flag); } } } - private processTestError = (err: any, t: AvaContext, attemptHtmls: string[]) => { + private processTestError = (err: any, t: AvaContext, attemptHtmls: string[], flag?: 'FAILING') => { t.retry = undefined; if (t.attemptNumber! < t.totalAttempts!) { t.log(`${t.attemptText} Retrying: ${String(err)}`); } else { - addDebugHtml(`

Test: ${Util.htmlEscape(t.title)}

${attemptHtmls.join('')}`); + if (flag !== 'FAILING') { // don't debug known failures + addDebugHtml(`

Test: ${Util.htmlEscape(t.title)}

${attemptHtmls.join('')}`); + } t.log(`${t.attemptText} Failed: ${err instanceof Error ? err.stack : String(err)}`); t.fail(`[ALL RETRIES FAILED for ${t.title}]`); } diff --git a/test/source/mock/all-apis-mock.ts b/test/source/mock/all-apis-mock.ts index 1701e3522b9..08d01e83407 100644 --- a/test/source/mock/all-apis-mock.ts +++ b/test/source/mock/all-apis-mock.ts @@ -11,6 +11,7 @@ import { mockBackendEndpoints } from './backend/backend-endpoints'; import { mockGoogleEndpoints } from './google/google-endpoints'; import { mockKeyManagerEndpoints } from './key-manager/key-manager-endpoints'; import { mockWellKnownHostMetaEndpoints } from './host-meta/host-meta-endpoints'; +import { mockWkdEndpoints } from './wkd/wkd-endpoints'; export type HandlersDefinition = Handlers<{ query: { [k: string]: string; }; body?: unknown; }, unknown>; @@ -29,6 +30,7 @@ export const startAllApisMock = async (logger: (line: string) => void) => { ...mockAttesterEndpoints, ...mockKeyManagerEndpoints, ...mockWellKnownHostMetaEndpoints, + ...mockWkdEndpoints, '/favicon.ico': async () => '', }); await api.listen(8001); diff --git a/test/source/mock/wkd/wkd-endpoints.ts b/test/source/mock/wkd/wkd-endpoints.ts new file mode 100644 index 00000000000..74629abe3c9 --- /dev/null +++ b/test/source/mock/wkd/wkd-endpoints.ts @@ -0,0 +1,129 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +import { HandlersDefinition } from '../all-apis-mock'; + +const alice = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +mDMEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mP +hgfIgj+0EUFsaWNlIDxhbGljZUBybnA+iJAEExYIADgWIQRz7cyRGa/I4tu9zeUE +UUCWaf/ePAUCXgS/LwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAEUUCW +af/ePCSdAP9OWq8uOk5B5LUtPvFnxqGkrZlAHt+tgR271QSggRV3MAEAvtL/ru5o +ss9jx26EqYj2GUgHGtsYqsz8j1y97S5lMQo= +=H16D +-----END PGP PUBLIC KEY BLOCK-----`; + +const johnDoe = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBF+IiY4BCADiCm145EEu8QALcex4OY+K7AEof4w2ZFW0Xj9wRodRj5WMEk0h5TOXlCr9Fzah +N75pl7W7jkYJUSUWucSga2tiIphR3JkPVDod6QUgwKSw/ZbXef9IY10bg4k+jobWToy4FmhOxUoZ +MfYGRMJKAwWBXajygbFp3XLV08BTToK4VRzA5/jZqjTmBnQ4Dut+F+TtdEve4WII/vtKYdpt7uSp +C3beifG0GD+bwoHit1hlsawN7iHGNjvszSGm9gXSRvF2dBskSgR2mtMC2HkgkhxR8Xrc5lBJAqPd +V05wvzjg26BXBEhe8AhJbqmnwUuEt40MleWuQPUMM0MBHtWfDIMxABEBAAHNEmpvaG4uZG9lQGxv +Y2FsaG9zdMLAiQQTAQgAMxYhBMsbekG6fBy6oIphZrkCy2XGlNa0BQJfiImUAhsDBQsJCAcCBhUI +CQoLAgUWAgMBAAAKCRC5AstlxpTWtCOMCACS/BgBU+/11SCzKcyjXHECPoJC2zxjBHZELaA8YRAP +CX9SIdWiaP06F7HTRrvBUqFuPtebA62GmF6PCyWv3wM7iY8HWrNNWnRgclfn203s6LpkIO8myzPi +itH15+2+CoKRD9QnGbkLZqOe+20ZG8AalciSnQm2QNMMd5RXyUhI8YxWFwzmd9rw18yA5r+P2g2y +uwC0PHbJDeRPzVsX5ZPVty00MCCOtJxcjTMszEVrndae78i2X7lI4WllGXvouw5nYJ4QCJxnE7IN +trwEW2/oqt1deeKAzDqtBlaz5eKameQhzCfIQ9yIvIxmS4GS25FScIBCqQktncKhujI2HfPbzsBN +BF+IiZQBCADAyDpTiMghfyWWqdqM09afztzzePk02xJJYRO/p09wgmqPaWgxfe//NqiFPDfiTyvc +nDLCDKuFktU5uD+I6UGVj7511r9veDh9Q+YB81qria0NONJtYN+bJ0T+depUAlOfNRSf9U79iuck +EHQR0j+4WLXHfqNR+CP9uobXZeAuyU47/KGXLvpO0GV1f8oDIxHW/ZnArFjXFHCbEwFg2Gbdedd6 +7gFMc18Q4brfhA4XbMyN0rybipmuylWGIXM30O7cJDBg7+wM3CGRU/aiPCuFkGyknJvRMcjd2XnK +JxqRhvtIF4ZFoXMTi3U2ZqBjkrInECEIVxo0ImNmugzPFyCHABEBAAHCwHYEGAEIACAWIQTLG3pB +unwcuqCKYWa5AstlxpTWtAUCX4iJmQIbDAAKCRC5AstlxpTWtBJiB/9HkI+URaTsMYA+Jh98Ia9J +U57naEB/iugXGiStORK/QQfa3/aSnVeCI/NU2Ja0QKwb/QkjeTA8J6pSXjz5BqQs6Ydgww/cTHfZ +eqx9TuETieW/vzdBtkEHPNAWk4h6uB3KlE+WiaErLqMp/ibp3XlDizDzIanxBGwJH3n0xbsII2c4 +Sk0/2pKhkHZeombiFhL4Ius6Ym82DsaHg/ngq0Alm9lUrZd2bhKWuOlUXF/kl1BtFuxQSNhAD13r +GOcKZI9WfvN8IV5P8oh/ldHcF8WUumQEFIAR3WGk8P9v1XfHtBFeDQ70ZoE+KV7Uy7UKAzcVni4O +AwMwBxWpyLaH6xjb +=mswb +-----END PGP PUBLIC KEY BLOCK-----`; + +const johnDoe1 = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBF+Jz04BCACYEg0Fkg4NLnTxzuyF3gnz2pMfp+BelG/IHvIg9mls4U8OJcjGyq3bVpPDKDDQ +4ZuhRtrMy5K0ZnyiGsmzMKXvVS3FUPQ5EJD2Laveo5ohtM6t49jlloQvv7aMUBVpjTskaY06lJuV +2f1CCCDteh3G8qNpKvziUc4a8PzeIItktHTCa0MNdDRxq19DUs2eQGGXqBDm/mQjVMZ1Zgt55Ymu +BpPLytJXOjRoDYsTj+OdOv+aQ4Td7JnicTDVQR4Uc9x02lMPsBF3K0N0cnITyiKCV/QdgdSQkL8N +zFVgvt5QPGhlGA0sAl1wiBFeNZrrd+VXg4FmuGrWd9YLS01AyRSJABEBAAHNEmpvaG4uZG9lQDEy +Ny4wLjAuMcLAiQQTAQgAMxYhBGWfN6kiReeNpwCbUy2oGOH1OKOdBQJfic9SAhsDBQsJCAcCBhUI +CQoLAgUWAgMBAAAKCRAtqBjh9TijnU1JB/wPcImnOmo42gu8YBKe18d9HyCQNs4q4pw/bYXshLe7 +aMjx+XgUvWHBcoTtz+4g2QjmhmL3ROkGs/vBriZZqhZz4636s7RAlyU0yyzcH4rvNq/ByNm+Ol7x +Bwo3EbaRh46Q4mbctslfQ/jd42cUzwEOnY2HSPH+tgfTjd8xciBL5tH0+YinCuqSi/sAOkBoNAkv +kgiA+cRH4scVsW2KwWuZPOJINO8nJVu0at64sZib8/UJ4Zku5w7i1dnmWu6C9aqz+Ddu/xcDUW8j +2GFXECJX0deXZmhEZ8wLt5P+ke3Wt1gttGCigVzPvpXHkpZpC7zlykcMeE2ywRBqypauZUjPzsBN +BF+Jz1IBCACjRVxKy2aU49H95g8LM1Dsqmc+KCSOl9/kC1pyzKl4xUEYQ+ok5OKHt9tf42a1z9SM +oUicFkSOwBMtjOhWjrghxVBHIc4NtDewFXY6IifG6BvewOiNat2mYzfLF852idBbhnN82p4NWd2g +oUwDcqOvZ3Z5qg4eKiH2l+UzlHuFXmSfFP3g7Pm3UTaYhOWRBwOO/u7JN9rMHBcLxfp7T745C8dT +UmrZEpfTEGgwhmYfBFyMZFxzgtZwt4UUTnyLFtqhROPx6ji4ecSDNAD7yVjd9uJSNbxIRV4SUPCG +/vR39Lm8dXiFEek47DYGPaPyid/zfFkfhLndQ70W2GkhGdLnABEBAAHCwHYEGAEIACAWIQRlnzep +IkXnjacAm1MtqBjh9TijnQUCX4nPVwIbDAAKCRAtqBjh9Tijna+wB/4tsj016eBsQGb3xGs/BBa6 +06VLL9tjrTV9TdZj3DfpE9xSPiNC+Jah09uW//VNRzNTDYctGXEGIYm2tx2q8QAX/rWBC/UYi16l +4lHnvlHa4NNA+O22uQpMlwmbTVSPTs4kVUSpNyuOtE6SXY6UyzcyvwTpffXiQkfICW7BXmykUXHx +HZ8Ddq/syp3eBitlmNZHGwRWKD5ihd96o5dCw/Dgpz/p6dxka1627j013wov65p1NbXiV1kM7G7s +ZccLrjYxOL/1Rc1ac9TdfXX7lVeZtbpSVhSl9Z1YMhth+oWVJ79iQQ8OPKEvt471Qwk1LH7k4a9O +nT5+WNnpAkcxrjMd +=fISM +-----END PGP PUBLIC KEY BLOCK----- +`; + +const jackAdvanced = `-----BEGIN PGP PUBLIC KEY BLOCK----- + +xsBNBF+J56IBCADSvEei1mAEQCEXHxaOKzsxvM8Qvgjj0vr+zHE+hUFg1znRAyjQ9XSDGwJ4+dut +kKh8P2Ibu8hhX01eHiqW85IXO2yvLDo19+8TnRHQGfn895Ptxs150Xcg4gwXPzvY9tIXV4+z+vm2 +ek7tM3zI4IXpT/VCasVHHn34C6qupkJSviZwC+SoXPS5flFXNSGktALC9OIpDzW1qqtIpl2niG2I +4Df4yn3iiRVM8Zt4SGxsvcSXbsAAVEev7BQTUgxin5DlWkA7R9zaiBQ/DkO1JCEXJVvqpK1ETxi3 +K/3rkmmob3mICp0QXpgpllik6jH5YXGtK0Dbly9IXPH03smrpBS7ABEBAAHNF2phY2suYWR2YW5j +ZWRAbG9jYWxob3N0wsCJBBMBCAAzFiEEFpnGj5ZL0Um8kGZee64HZY9qWHgFAl+J56cCGwMFCwkI +BwIGFQgJCgsCBRYCAwEAAAoJEHuuB2WPalh4frQH/jWdjU1OPy0mMToZFRF81j4v1eyziyBSPjWV +MxvqNMSeBxHBmDWsfcWesvUi24kodiAYnJW8zAN9N99BATgGgvGNkghq4AFneOWBXDwQi8t+5H0v +c9JXSUbE/NH1JuqkPoF6p2flcR3VIhmfOEKMfKcCSyLRykiDQxGjkGwBMo/Dmv6iC0qJu95u/8PG +C8hnLmkORJFs0Ql0lTTqKrZQdhdyS34Ad99YwbNjtZvZcfIRljiMaJlwsj9e/kj9Ppn7PZWgs20X +/reafSKWyPFsgQyNzWnmXggrodvVKrj2NHlFlAqYyKNujbz0NdpwBx4U4WBFIpSZ2HLNT+chHdnr +NlfOwE0EX4nnpwEIALRviWiViluglkUGltBX6rWwYjY8niPbBaXC+xus4uH08IlDO/aDD2t4efkP +t0QMeQE9WYsRk8hHUC+isJRF+jbwVhAW1fkv4R3WWkwXQFsr0wXW4Krp30m9JaOW9EolDwv8/Fik +XmkBhWviByqBDvZ9CPtEt5Nwd6XT49Cf4HycQvrkKC3ytYohs2iyf+T7BLKJr6y3zDlaBtXaoqpA +wIXBduoccL01MyrGxLeLF56W75SZmtE4CK0G6g/zjjwmawKDSfK6DZHARYC/ZguUocnpB3Ui7B8d +sprqB1QmQZJQt7By6EUvhnw1WYAiDD05xryFPNhXYt3+Ypid/N/A2eEAEQEAAcLAdgQYAQgAIBYh +BBaZxo+WS9FJvJBmXnuuB2WPalh4BQJfieetAhsMAAoJEHuuB2WPalh4TvgIAIRkAuvvl+9p8xPg +pfMp5muIpVFjWmMUBIT8MEXZvVSr3YUpDmHxTgbL78Hf9Fk7rAw7tkFFz+cZltnIDITUCyGdIJaF +dow3a+ImNZ/eo6zcftWJiyH1zM5w5aEcWNUnHpy8TDRIFgomt1K1DcAo2zoutpGylCF5ZeL6vqFw +afV6xkjQ9+neZ2LahN5cYNOKAeeI73hvoGFSCYp9Ih8JaEhG6seU9lskg2qQDvVEJaHHp0nPxbU/ +mhejIVi+pinqouXEqSb+84n2dNQ7HdgGe1YBM8kvNzq9SLfcYWjpnkchoL+5KGksjgbOvKFDz1Ek +nmusEeYtrrMytL4oUohBVZk= +=bbav +-----END PGP PUBLIC KEY BLOCK----- +`; + +export const mockWkdEndpoints: HandlersDefinition = { + '/.well-known/openpgpkey/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=john.doe': async () => { + return johnDoe1; // direct for john.doe@127.0.0.1 + }, + '/.well-known/openpgpkey/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=John.Doe': async () => { + return johnDoe1; // direct for John.Doe@127.0.0.1 + }, + '/.well-known/openpgpkey/hu/cb53pfqmbzc8mm3ecbjxyen65fdxos56?l=jack.advanced': async () => { + return jackAdvanced; // direct for jack.advanced@localhost + }, + '/.well-known/openpgpkey/127.0.0.1/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=john.doe': async () => { + return alice; // shouldn't be returned + }, + '/.well-known/openpgpkey/127.0.0.1/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=John.Doe': async () => { + return alice; // shouldn't be returned + }, + '/.well-known/openpgpkey/localhost/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=john.doe': async () => { + return johnDoe; // advanced for john.doe@localhost + }, + '/.well-known/openpgpkey/localhost/hu/ihyath4noz8dsckzjbuyqnh4kbup6h4i?l=John.Doe': async () => { + return johnDoe; // advanced for John.Doe@localhost + }, + '/.well-known/openpgpkey/localhost/hu/pob4adi8roqdsmtmxikx68pi6ij35oca?l=incorrect': async () => { + return alice; // advanced for incorrect@localhost + }, + '/.well-known/openpgpkey/localhost/policy': async () => { + return ''; // allow advanced for localhost + }, + '/.well-known/openpgpkey/policy': async () => { + return ''; // allow direct for 127.0.0.1 + }, +}; diff --git a/test/source/test.ts b/test/source/test.ts index 4b1d55f51ac..e857bedcf76 100644 --- a/test/source/test.ts +++ b/test/source/test.ts @@ -57,14 +57,14 @@ ava.before('set config and mock api', async t => { t.pass(); }); -const testWithBrowser = (acct: CommonAcct | undefined, cb: (t: AvaContext, browser: BrowserHandle) => Promise): ava.Implementation<{}> => { +const testWithBrowser = (acct: CommonAcct | undefined, cb: (t: AvaContext, browser: BrowserHandle) => Promise, flag?: 'FAILING'): ava.Implementation<{}> => { return async (t: AvaContext) => { await browserPool.withNewBrowserTimeoutAndRetry(async (t, browser) => { if (acct) { await BrowserRecipe.setUpCommonAcct(t, browser, acct); } await cb(t, browser); - }, t, consts); + }, t, consts, flag); t.pass(); }; }; diff --git a/test/source/tests/browser-unit-tests/unit-Wkd.js b/test/source/tests/browser-unit-tests/unit-Wkd.js new file mode 100644 index 00000000000..3541fc5ce76 --- /dev/null +++ b/test/source/tests/browser-unit-tests/unit-Wkd.js @@ -0,0 +1,85 @@ +/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */ + +/** + * These tests use JavaScript instead of TypeScript to avoid dealing with types in cross-environment setup. + * (tests are injected from NodeJS through puppeteer into a browser environment) + * While this makes them less convenient to write, the result is more flexible. + * + * Import your lib to `ci_unit_test.ts` to resolve `ReferenceError: SomeClass is not defined` + * + * Each test must return "pass" to pass. To reject, throw an Error. + * + * Each test must start with one of (depending on which flavors you want it to run): + * - BROWSER_UNIT_TEST_NAME(`some test name`); + * - BROWSER_UNIT_TEST_NAME(`some test name`).enterprise; + * - BROWSER_UNIT_TEST_NAME(`some test name`).consumer; + * + * This is not a JavaScript file. It's a text file that gets parsed, split into chunks, and + * parts of it executed as javascript. The structure is very rigid. The only flexible place is inside + * the async functions. For the rest, do not change the structure or our parser will get confused. + * Do not put any code whatsoever outside of the async functions. + */ + +BROWSER_UNIT_TEST_NAME(`Wkd direct method`); +(async () => { + const wkd = new Wkd('flowcrypt.com', 'http'); + wkd.port = 8001; + let email; + email = 'john.doe@127.0.0.1'; + if (!(await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't return a pubkey`); + } + email = 'John.Doe@127.0.0.1'; + if (!(await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't return a pubkey`); + } + return 'pass'; +})(); + +BROWSER_UNIT_TEST_NAME(`Wkd advanced method`); +(async () => { + const wkd = new Wkd('flowcrypt.com', 'http'); + wkd.port = 8001; + let email; + email = 'john.doe@localhost'; + if (!(await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't return a pubkey`); + } + email = 'John.Doe@localHOST'; + if (!(await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't return a pubkey`); + } + return 'pass'; +})(); + +BROWSER_UNIT_TEST_NAME(`Wkd advanced shouldn't fall back on direct if advanced policy file is present`); +(async () => { + const wkd = new Wkd('flowcrypt.com', 'http'); + wkd.port = 8001; + const email = 'jack.advanced@localhost'; + if ((await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't expect a pubkey`); + } + return 'pass'; +})(); + +BROWSER_UNIT_TEST_NAME(`Wkd incorrect UID should fail`); +(async () => { + const wkd = new Wkd('flowcrypt.com', 'http'); + wkd.port = 8001; + const email = 'incorrect@localhost'; + if ((await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't expect a pubkey`); + } + return 'pass'; +})(); + +BROWSER_UNIT_TEST_NAME(`Wkd should extract key for human@flowcrypt.com`); +(async () => { + const wkd = new Wkd('flowcrypt.com'); + const email = 'human@flowcrypt.com'; + if (!(await wkd.lookupEmail(email)).pubkey) { + throw Error(`Wkd for ${email} didn't return a pubkey`); + } + return 'pass'; +})(); diff --git a/test/source/tests/compose.ts b/test/source/tests/compose.ts index 7d4b30bdb68..a478d11d668 100644 --- a/test/source/tests/compose.ts +++ b/test/source/tests/compose.ts @@ -634,12 +634,18 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te expect(await composePage.attr('.email_address.has_pgp', 'title')).to.contain('00B0 1158 0796 9D75'); })); - ava.default('can lookup public key from WKD directly', testWithBrowser('compose', async (t, browser) => { + /** + * Waiting for wkd /policy file to have CORS + * Until then, for testing it would be possible to create a mock domain with custom OrgRules, + * which forbid lookup on Attester. Then we can switch test to human@flowcrypt.com knowing + * that the key must be retrieved from WKD. + */ + ava.default.failing('can lookup public key from WKD directly', testWithBrowser('compose', async (t, browser) => { const composePage = await ComposePageRecipe.openStandalone(t, browser, 'compose'); await ComposePageRecipe.fillMsg(composePage, { to: 'test-wkd@metacode.biz' }, 'should find pubkey from WKD directly'); await composePage.waitForContent('.email_address.has_pgp', 'test-wkd@metacode.biz'); expect(await composePage.attr('.email_address.has_pgp', 'title')).to.contain('92C4 E784 1B3A FF74'); - })); + }, 'FAILING')); ava.default('timeouts when searching WKD - used to never time out', testWithBrowser('compose', async (t, browser) => { const composePage = await ComposePageRecipe.openStandalone(t, browser, 'compose'); diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts index 212bddf2031..c24cd22f6e6 100644 --- a/test/source/tests/setup.ts +++ b/test/source/tests/setup.ts @@ -137,7 +137,7 @@ CciXuhqnLwoVF5/uXMYffVtfl/OU+w== longid: '123', } }); - })); + }, 'FAILING')); ava.default('setup - import key - two e-mails on the screen', testWithBrowser(undefined, async (t, browser) => { const settingsPage = await BrowserRecipe.openSettingsLoginApprove(t, browser, 'flowcrypt.test.key.imported@gmail.com');