diff --git a/cypress/plugins/dex/index.ts b/cypress/plugins/dex/index.ts new file mode 100644 index 00000000..b07a78e8 --- /dev/null +++ b/cypress/plugins/dex/index.ts @@ -0,0 +1,132 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// + +import * as path from "path"; +import * as os from "os"; +import * as fse from "fs-extra"; + +import PluginEvents = Cypress.PluginEvents; +import PluginConfigOptions = Cypress.PluginConfigOptions; +import {dockerRun, dockerStop } from "../docker"; + +// A cypress plugins to add command to start & stop dex instances + +interface DexConfig { + configDir: string; + baseUrl: string; + port: number; + host: string; +} + +export interface DexInstance extends DexConfig { + dexId: string; +} + +const dexConfigs = new Map(); +let env; + +async function produceConfigWithSynapseURLAdded(): Promise { + const templateDir = path.join(__dirname, "template"); + + const stats = await fse.stat(templateDir); + if (!stats?.isDirectory) { + throw new Error(`Template directory at ${templateDir} not found!`); + } + const tempDir = await fse.mkdtemp(path.join(os.tmpdir(), 'hydrogen-testing-dex-')); + + // copy the contents of the template dir, omitting config.yaml as we'll template that + console.log(`Copy ${templateDir} -> ${tempDir}`); + await fse.copy(templateDir, tempDir, { filter: f => path.basename(f) !== 'config.yaml' }); + + // now copy config.yaml, applying substitutions + console.log(`Gen ${path.join(templateDir, "config.yaml")}`); + let hsYaml = await fse.readFile(path.join(templateDir, "config.yaml"), "utf8"); + const synapseAddress = `${env.SYNAPSE_IP_ADDRESS}:${env.SYNAPSE_PORT}`; + hsYaml = hsYaml.replace(/{{SYNAPSE_ADDRESS}}/g, synapseAddress); + const host = env.DEX_IP_ADDRESS; + const port = env.DEX_PORT; + const dexAddress = `${host}:${port}`; + hsYaml = hsYaml.replace(/{{DEX_ADDRESS}}/g, dexAddress); + await fse.writeFile(path.join(tempDir, "config.yaml"), hsYaml); + + const baseUrl = `http://${host}:${port}`; + return { + host, + port, + baseUrl, + configDir: tempDir, + }; +} + +async function dexStart(): Promise { + const dexCfg = await produceConfigWithSynapseURLAdded(); + console.log(`Starting dex with config dir ${dexCfg.configDir}...`); + const dexId = await dockerRun({ + image: "bitnami/dex:latest", + containerName: "dex", + dockerParams: [ + "--rm", + "-v", `${dexCfg.configDir}:/data`, + `--ip=${dexCfg.host}`, + "-p", `${dexCfg.port}:5556/tcp`, + "--network=hydrogen" + ], + applicationParams: [ + "serve", + "data/config.yaml", + ] + }); + + console.log(`Started dex with id ${dexId} on port ${dexCfg.port}.`); + + const dex: DexInstance = { dexId, ...dexCfg }; + dexConfigs.set(dexId, dex); + return dex; +} + +async function dexStop(id: string): Promise { + const dexCfg = dexConfigs.get(id); + if (!dexCfg) throw new Error("Unknown dex ID"); + await dockerStop({ containerId: id, }); + await fse.remove(dexCfg.configDir); + dexConfigs.delete(id); + console.log(`Stopped dex id ${id}.`); + // cypress deliberately fails if you return 'undefined', so + // return null to signal all is well, and we've handled the task. + return null; +} + +/** + * @type {Cypress.PluginConfig} + */ +export function dexDocker(on: PluginEvents, config: PluginConfigOptions) { + env = config.env; + + on("task", { + dexStart, + dexStop, + }); + + on("after:spec", async (spec) => { + for (const dexId of dexConfigs.keys()) { + console.warn(`Cleaning up dex ID ${dexId} after ${spec.name}`); + await dexStop(dexId); + } + }); +} + diff --git a/cypress/plugins/dex/template/config.yaml b/cypress/plugins/dex/template/config.yaml new file mode 100755 index 00000000..f773eb76 --- /dev/null +++ b/cypress/plugins/dex/template/config.yaml @@ -0,0 +1,56 @@ +issuer: http://{{DEX_ADDRESS}}/dex + +storage: + type: sqlite3 + config: + file: data/dev.db + + +# Configuration for the HTTP endpoints. +web: + http: 0.0.0.0:5556 + # Uncomment for HTTPS options. + # https: 127.0.0.1:5554 + # tlsCert: /etc/dex/tls.crt + # tlsKey: /etc/dex/tls.key + +# Configuration for telemetry +telemetry: + http: 0.0.0.0:5558 + # enableProfiling: true + +staticClients: +- id: synapse + secret: secret + redirectURIs: + - 'http://{{SYNAPSE_ADDRESS}}/_synapse/client/oidc/callback' + name: 'Synapse' +connectors: +- type: mockCallback + id: mock + name: Example +# - type: google +# id: google +# name: Google +# config: +# issuer: https://accounts.google.com +# # Connector config values starting with a "$" will read from the environment. +# clientID: $GOOGLE_CLIENT_ID +# clientSecret: $GOOGLE_CLIENT_SECRET +# redirectURI: http://127.0.0.1:5556/dex/callback +# hostedDomains: +# - $GOOGLE_HOSTED_DOMAIN + +# Let dex keep a list of passwords which can be used to login to dex. +enablePasswordDB: true + +# A static list of passwords to login the end user. By identifying here, dex +# won't look in its underlying storage for passwords. +# +# If this option isn't chosen users may be added through the gRPC API. +staticPasswords: +- email: "admin@example.com" + # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2) + hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" + username: "admin" + userID: "08a8684b-db88-4b73-90a9-3cd1661f5466" diff --git a/cypress/plugins/dex/template/dev.db b/cypress/plugins/dex/template/dev.db new file mode 100755 index 00000000..95a42aed Binary files /dev/null and b/cypress/plugins/dex/template/dev.db differ diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts index 44dd93b8..27c8b9a3 100644 --- a/cypress/plugins/index.ts +++ b/cypress/plugins/index.ts @@ -20,6 +20,7 @@ import PluginEvents = Cypress.PluginEvents; import PluginConfigOptions = Cypress.PluginConfigOptions; import { performance } from "./performance"; import { synapseDocker } from "./synapsedocker"; +import { dexDocker } from "./dex"; import { webserver } from "./webserver"; import { docker } from "./docker"; import { log } from "./log"; @@ -31,6 +32,7 @@ export default function(on: PluginEvents, config: PluginConfigOptions) { docker(on, config); performance(on, config); synapseDocker(on, config); + dexDocker(on, config); webserver(on, config); log(on, config); } diff --git a/cypress/support/dex.ts b/cypress/support/dex.ts new file mode 100644 index 00000000..599eee26 --- /dev/null +++ b/cypress/support/dex.ts @@ -0,0 +1,51 @@ +/* +Copyright 2022 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/// + +import Chainable = Cypress.Chainable; +import AUTWindow = Cypress.AUTWindow; +import { DexInstance } from "../plugins/dex"; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Cypress { + interface Chainable { + /** + * Start the dex server + */ + startDex(): Chainable; + + /** + * Stop the dex server + * @param dex the dex instance returned by startSynapse + */ + stopDex(dex: DexInstance): Chainable; + } + } +} + +function startDex(): Chainable { + return cy.task("dexStart"); +} + +function stopDex(dex?: DexInstance): Chainable { + if (!dex) return; + cy.task("dexStop", dex.dexId); +} + +Cypress.Commands.add("startDex", startDex); +Cypress.Commands.add("stopDex", stopDex); diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts index 289319fe..d186c5a0 100644 --- a/cypress/support/e2e.ts +++ b/cypress/support/e2e.ts @@ -17,3 +17,4 @@ limitations under the License. /// import "./synapse"; +import "./dex";