diff --git a/CloudronManifest.json b/CloudronManifest.json index 6068887..db61b78 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -24,7 +24,7 @@ "postgresql": {}, "redis": {}, "sendmail": { "supportsDisplayName": false }, - "ldap": {} + "oidc": { "loginRedirectUri": "/plugins/auth-openid-connect/router/code-cb" } }, "memoryLimit": 1073741824, "forumUrl": "https://forum.cloudron.io/category/91/peertube", diff --git a/start.sh b/start.sh index 7b77a91..888f887 100755 --- a/start.sh +++ b/start.sh @@ -7,11 +7,45 @@ mkdir -p /app/data/storage /run/peertube/cache /run/peertube/npm /tmp/peertube # do not rely on WORKDIR cd /app/code/server -update_ldap() { - echo "==> Updating Ldap credentials" +migrate_ldap_to_oidc() { + if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then + OIDC_PLUIGIN_INSTALLED=$(PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} -AXqtc "SELECT count(*) FROM \"plugin\" WHERE name='auth-openid-connect'") + if [ ${OIDC_PLUIGIN_INSTALLED} -eq 0 ]; then + install_oidc + fi + + USERS_TO_MIGRATE=$(PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} -AXqtc "SELECT count(*) FROM \"user\" u WHERE \"pluginAuth\"='peertube-plugin-auth-ldap'") + echo "==> Migrating users to OIDC"; + echo " Users to migrate: ${USERS_TO_MIGRATE}" + if [ ${USERS_TO_MIGRATE} -gt 0 ]; then + PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} -c "UPDATE \"user\" SET \"pluginAuth\"='peertube-plugin-auth-openid-connect' WHERE \"pluginAuth\"='peertube-plugin-auth-ldap'" + fi + + LDAP_PLUGIN_INSTALLED=$(PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} -AXqtc "SELECT count(*) FROM \"plugin\" WHERE name='auth-ldap'") + if [ ${LDAP_PLUGIN_INSTALLED} -gt 0 ]; then + echo "==> Uninstalling LDAP plugin" + gosu cloudron:cloudron npm run plugin:uninstall -- -n peertube-plugin-auth-ldap + + echo "==> Deleting LDAP settings" + PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} -c "DELETE FROM \"plugin\" WHERE name='auth-ldap'" + fi + fi +} + +install_oidc() { + # https://docs.joinpeertube.org/maintain-tools?id=cli-wrapper . Note that we have to restart peertube when installed this way + if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then + echo "==> Installing OIDC plugin" + gosu cloudron:cloudron npm run plugin:install -- -n peertube-plugin-auth-openid-connect -v 0.1.1 + update_oidc + fi +} + +update_oidc() { + echo "==> Updating OIDC config" PGPASSWORD=${CLOUDRON_POSTGRESQL_PASSWORD} psql -h ${CLOUDRON_POSTGRESQL_HOST} -p ${CLOUDRON_POSTGRESQL_PORT} -U ${CLOUDRON_POSTGRESQL_USERNAME} -d ${CLOUDRON_POSTGRESQL_DATABASE} \ - -c "UPDATE plugin SET settings='{\"url\": \"${CLOUDRON_LDAP_URL}\", \"weight\": 100, \"insecure-tls\": false, \"bind-dn\": \"${CLOUDRON_LDAP_BIND_DN}\", \"bind-credentials\": \"${CLOUDRON_LDAP_BIND_PASSWORD}\", \"search-base\": \"${CLOUDRON_LDAP_USERS_BASE_DN}\", \"mail-property\": \"mail\", \"search-filter\": \"(|(mail={{username}})(username={{username}}))\", \"username-property\": \"username\"}' WHERE name='auth-ldap'" + -c "UPDATE plugin SET settings='{\"scope\": \"openid email profile\", \"client-id\": \"${CLOUDRON_OIDC_CLIENT_ID}\", \"discover-url\": \"${CLOUDRON_OIDC_DISCOVERY_URL}\", \"client-secret\": \"${CLOUDRON_OIDC_CLIENT_SECRET}\", \"mail-property\": \"email\", \"auth-display-name\": \"Cloudron\", \"username-property\": \"preferred_username\", \"signature-algorithm\": \"RS256\", \"display-name-property\": \"name\"}' WHERE name='auth-openid-connect'" } first_time_setup() { @@ -31,12 +65,7 @@ first_time_setup() { echo "changeme" | gosu cloudron:cloudron npm run reset-password -- -u root sleep 5 # the above command seems to spawn a separate process to change password in background - # https://docs.joinpeertube.org/maintain-tools?id=cli-wrapper . Note that we have to restart peertube when installed this way - if [[ -n "${CLOUDRON_LDAP_SERVER:-}" ]]; then - echo "==> Installing LDAP plugin" - gosu cloudron:cloudron npm run plugin:install -- -n peertube-plugin-auth-ldap -v 0.0.10 - update_ldap - fi + install_oidc echo "==> First time setup complete" } @@ -109,8 +138,9 @@ if [[ ! -f "/app/data/production.yaml" ]]; then first_time_setup else update_config + migrate_ldap_to_oidc - [[ -n "${CLOUDRON_LDAP_SERVER:-}" ]] && update_ldap + [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]] && update_oidc fi echo "==> Configuring nginx" diff --git a/test/test.js b/test/test.js index b7afe9e..97c4bc7 100644 --- a/test/test.js +++ b/test/test.js @@ -38,6 +38,7 @@ describe('Application life cycle test', function () { let username = process.env.USERNAME; let password = process.env.PASSWORD; let email = process.env.EMAIL; + let athenticated_by_oidc = false; before(function () { browser = new Builder().forBrowser('chrome').setChromeOptions(new Options().windowSize({ width: 1280, height: 1024 })).build(); @@ -78,6 +79,29 @@ describe('Application life cycle test', function () { await waitForElement(By.xpath('//a[contains(@href, "/my-library")]')); } + async function loginOIDC(username, password) { + browser.manage().deleteAllCookies(); + await browser.get(`https://${app.fqdn}/login`); + await browser.sleep(2000); + + await browser.wait(until.elementLocated(By.xpath('//a[contains(., "Cloudron")]')), TEST_TIMEOUT); + await browser.findElement(By.xpath('//a[contains(., "Cloudron")]')).click(); + await browser.sleep(2000); + + if (!athenticated_by_oidc) { + await waitForElement(By.xpath('//input[@name="username"]')); + await browser.findElement(By.xpath('//input[@name="username"]')).sendKeys(username); + await browser.findElement(By.xpath('//input[@name="password"]')).sendKeys(password); + await browser.sleep(2000); + await browser.findElement(By.xpath('//button[@type="submit" and contains(text(), "Sign in")]')).click(); + await browser.sleep(2000); + + athenticated_by_oidc = true; + } + await browser.sleep(20000); + await waitForElement(By.xpath('//a[contains(@href, "/my-library")]')); + } + async function closeAccountSetupDialog() { await browser.get('https://' + app.fqdn); @@ -99,14 +123,19 @@ describe('Application life cycle test', function () { async function completeSetup() { let button; +// await browser.executeScript('localStorage.clear();'); await browser.get(`https://${app.fqdn}`); - await waitForElement(By.xpath('//a[contains(text(), "Configure my instance")]')); - await browser.findElement(By.xpath('//a[contains(text(), "Configure my instance")]')).click(); // this opens a new window + await browser.sleep(2000); - let say_cmd = await getOS() == "Darwin" ? "say" : "spd-say"; - execSync(`${say_cmd} "Close the newly opened configuration tab."`); + if (await browser.findElements(By.xpath('//a[contains(text(), "Configure my instance")]')).then(found => !!found.length)) { +// await waitForElement(By.xpath('//a[contains(text(), "Configure my instance")]')); + await browser.findElement(By.xpath('//a[contains(text(), "Configure my instance")]')).click(); // this opens a new window - await rl.question('Is tab closed? '); + let say_cmd = await getOS() == "Darwin" ? "say" : "spd-say"; + execSync(`${say_cmd} "Close the newly opened configuration tab."`); + + await rl.question('Is tab closed? '); + } } async function uploadVideo() { @@ -129,19 +158,19 @@ describe('Application life cycle test', function () { } xit('build app', function () { execSync('cloudron build', EXEC_ARGS); }); - xit('install app', async function () { + it('install app', async function () { execSync('cloudron install --location ' + LOCATION, EXEC_ARGS); await sleep(40000); // takes a bit to create root user in background }); it('can get app information', getAppInfo); it('can root login', login.bind(null, 'root', 'changeme')); - xit('can complete setup', completeSetup); + it('can complete setup', completeSetup); it('can upload video', uploadVideo); it('video exists', videoExists); it('logout', logout); - it('can login', login.bind(null, username, password)); + it('can OIDC login', loginOIDC.bind(null, username, password)); it('can close account setup dialog', closeAccountSetupDialog); it('logout', logout); @@ -159,7 +188,7 @@ describe('Application life cycle test', function () { it('video exists', videoExists); it('logout', logout); - it('can login', login.bind(null, username, password)); + it('can OIDC login', loginOIDC.bind(null, username, password)); it('logout', logout); it('can restart app', function () { @@ -169,7 +198,7 @@ describe('Application life cycle test', function () { it('video exists', videoExists); it('logout', logout); - it('can login', login.bind(null, username, password)); + it('can OIDC login', loginOIDC.bind(null, username, password)); it('logout', logout); // this is not supported for federation @@ -180,7 +209,7 @@ describe('Application life cycle test', function () { it('video exists', videoExists); it('logout', logout); - it('can login', login.bind(null, username, password)); + it('can OIDC login', loginOIDC.bind(null, username, password)); it('logout', logout); it('uninstall app', async function () { @@ -222,7 +251,7 @@ describe('Application life cycle test', function () { it('video exists', videoExists); it('logout', logout); - it('can login', login.bind(null, username, password)); + it('can OIDC login', loginOIDC.bind(null, username, password)); it('can close account setup dialog', closeAccountSetupDialog); it('logout', logout);