diff --git a/CloudronManifest.json b/CloudronManifest.json index 72a8de0..e64d026 100644 --- a/CloudronManifest.json +++ b/CloudronManifest.json @@ -20,10 +20,10 @@ } }, "addons": { - "ldap": {}, "sendmail": { "supportsDisplayName": false }, "localstorage": {}, - "mysql": {} + "mysql": {}, + "oidc": { "loginRedirectUri": "/api/session/openid/callback" } }, "optionalSso": true, "manifestVersion": 2, diff --git a/POSTINSTALL.md b/POSTINSTALL.md index 763911b..d1fa84e 100644 --- a/POSTINSTALL.md +++ b/POSTINSTALL.md @@ -4,3 +4,7 @@ This app is pre-setup with an admin account. The initial credentials are: **Password**: admin
Please change the admin email and password credentials immediately. + + +By default, Cloudron users have regular users permissions. Permissions can be updated on the user profile page in the admin back-end. + diff --git a/start.sh b/start.sh index 7edc378..c9632c6 100755 --- a/start.sh +++ b/start.sh @@ -52,20 +52,29 @@ xmlstarlet ed --inplace \ # origin xmlstarlet ed --inplace --update '//properties/entry[@key="web.url"]' -v "${CLOUDRON_APP_ORIGIN}" /app/data/traccar.xml -# ldap -if [[ -n "${CLOUDRON_LDAP_URL:-}" ]]; then - echo "=> Ensure LDAP settings" +# get rid of ldap, can be removed in the next release +sed -e 's/ldap.url/openid.clientId/g' \ + -e 's/ldap.base/openid.clientSecret/g' \ + -e 's/ldap.idAttribute/openid.issuerUrl/g' \ + -e 's/ldap.searchFilter/openid.authUrl/g' \ + -e 's/ldap.user/openid.tokenUrl/g' \ + -e 's/ldap.password/openid.userInfoUrl/g' \ + -e 's/^.*ldap\..*$//g' \ + -i /app/data/traccar.xml + +# OIDC +if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then + echo "=> Ensure OIDC settings" xmlstarlet ed --inplace \ - --update '//properties/entry[@key="ldap.enable"]' -v "true" \ - --update '//properties/entry[@key="ldap.url"]' -v "${CLOUDRON_LDAP_URL}" \ - --update '//properties/entry[@key="ldap.base"]' -v "${CLOUDRON_LDAP_USERS_BASE_DN}" \ - --update '//properties/entry[@key="ldap.idAttribute"]' -v "username" \ - --update '//properties/entry[@key="ldap.searchFilter"]' -v '(|(username=:login)(mail=:login))' \ - --update '//properties/entry[@key="ldap.user"]' -v "${CLOUDRON_LDAP_BIND_DN}" \ - --update '//properties/entry[@key="ldap.password"]' -v "${CLOUDRON_LDAP_BIND_PASSWORD}" \ + --update '//properties/entry[@key="openid.clientId"]' -v "${CLOUDRON_OIDC_CLIENT_ID}" \ + --update '//properties/entry[@key="openid.clientSecret"]' -v "${CLOUDRON_OIDC_CLIENT_SECRET}" \ + --update '//properties/entry[@key="openid.issuerUrl"]' -v "${CLOUDRON_OIDC_ISSUER}" \ + --update '//properties/entry[@key="openid.authUrl"]' -v "${CLOUDRON_OIDC_AUTH_ENDPOINT}" \ + --update '//properties/entry[@key="openid.tokenUrl"]' -v "${CLOUDRON_OIDC_TOKEN_ENDPOINT}" \ + --update '//properties/entry[@key="openid.userInfoUrl"]' -v "${CLOUDRON_OIDC_PROFILE_ENDPOINT}" \ /app/data/traccar.xml else - xmlstarlet ed --inplace --update '//properties/entry[@key="ldap.enable"]' -v "false" /app/data/traccar.xml + sed -e 's/^.*openid\..*$//g' -i /app/data/traccar.xml fi # email diff --git a/test/test.js b/test/test.js index 7e0cd92..a3ed2f2 100644 --- a/test/test.js +++ b/test/test.js @@ -16,8 +16,8 @@ const execSync = require('child_process').execSync, { Builder, By, Key, until } = require('selenium-webdriver'), { Options } = require('selenium-webdriver/chrome'); -if (!process.env.EMAIL || !process.env.PASSWORD) { - console.log('EMAIL and PASSWORD env vars need to be set'); +if (!process.env.USERNAME || !process.env.EMAIL || !process.env.PASSWORD) { + console.log('USERNAME, EMAIL and PASSWORD env vars need to be set'); process.exit(1); } @@ -25,16 +25,18 @@ describe('Application life cycle test', function () { this.timeout(0); const LOCATION = 'test'; - const TEST_TIMEOUT = 10000; + const TEST_TIMEOUT = 20000; const EXEC_ARGS = { cwd: path.resolve(__dirname, '..'), stdio: 'inherit' }; const DEVICE_NAME = 'FancyDevice'; const DEVICE_IDENTIFIER = 'device1'; const ADMIN_USERNAME = 'admin@cloudron.local'; const ADMIN_PASSWORD = 'admin'; + const USERNAME = process.env.USERNAME; const EMAIL = process.env.EMAIL; const PASSWORD = process.env.PASSWORD; let browser, app; + let athenticated_by_oidc = false; before(function () { const options = new Options().windowSize({ width: 1280, height: 1024 }); @@ -61,10 +63,35 @@ describe('Application life cycle test', function () { async function login(emailOrUsername, password) { await browser.get(`https://${app.fqdn}/login`); await waitForElement(By.xpath('//input[@name="email"]')); - await browser.findElement(By.xpath('//input[@name="email"]')).sendKeys(Key.CONTROL + 'a'); + await browser.findElement(By.xpath('//input[@name="email"]')).sendKeys(Key.CONTROL + 'a' + Key.BACK_SPACE + Key.COMMAND + 'a' + Key.BACK_SPACE); await browser.findElement(By.xpath('//input[@name="email"]')).sendKeys(emailOrUsername); await browser.findElement(By.xpath('//input[@name="password"]')).sendKeys(password); await browser.findElement(By.xpath('//button[text()="Login"]')).click(); + await browser.sleep(3000); + await waitForElement(By.xpath('//span[text()="Account"]')); + } + + async function loginOIDC(username, password) { + browser.manage().deleteAllCookies(); + await browser.get(`https://${app.fqdn}/login`); + await browser.sleep(2000); + + await waitForElement(By.xpath('//button[contains(., "Login with OpenID")]')); + await browser.findElement(By.xpath('//button[contains(., "Login with OpenID")]')).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(3000); await waitForElement(By.xpath('//span[text()="Account"]')); } @@ -93,7 +120,8 @@ describe('Application life cycle test', function () { } xit('build app', function () { execSync('cloudron build', EXEC_ARGS); }); - it('install app', function () { execSync(`cloudron install --location ${LOCATION}`, EXEC_ARGS); }); + // no sso + it('install app (no sso)', function () { execSync(`cloudron install --no-sso --location ${LOCATION}`, EXEC_ARGS); }); it('can get app information', getAppInfo); it('can login as admin', login.bind(null, ADMIN_USERNAME, ADMIN_PASSWORD)); @@ -101,10 +129,22 @@ describe('Application life cycle test', function () { it('device exists', deviceExists); it('can logout', logout); - it('can login as normal user with email', login.bind(null, process.env.EMAIL, process.env.PASSWORD)); + it('uninstall app', async function () { + // ensure we don't hit NXDOMAIN in the mean time + await browser.get('about:blank'); + execSync(`cloudron uninstall --app ${app.id}`, EXEC_ARGS); + }); + + // sso + it('install app (sso)', function () { execSync(`cloudron install --location ${LOCATION}`, EXEC_ARGS); }); + + it('can get app information', getAppInfo); + it('can login as admin', login.bind(null, ADMIN_USERNAME, ADMIN_PASSWORD)); + it('can add device', addDevice); + it('device exists', deviceExists); it('can logout', logout); - it('can login as normal user with username', login.bind(null, process.env.USERNAME, process.env.PASSWORD)); + it('can login as normal user via OIDC', loginOIDC.bind(null, process.env.USERNAME, process.env.PASSWORD)); it('can logout', logout); it('can restart app', function () { execSync(`cloudron restart --app ${app.id}`); }); @@ -126,6 +166,9 @@ describe('Application life cycle test', function () { it('device exists', deviceExists); it('can logout', logout); + it('can login as normal user via OIDC', loginOIDC.bind(null, process.env.USERNAME, process.env.PASSWORD)); + it('can logout', logout); + it('move to different location', async function () { // ensure we don't hit NXDOMAIN in the mean time await browser.get('about:blank'); @@ -137,6 +180,9 @@ describe('Application life cycle test', function () { it('device exists', deviceExists); it('can logout', logout); + it('can login as normal user via OIDC', loginOIDC.bind(null, process.env.USERNAME, process.env.PASSWORD)); + it('can logout', logout); + it('uninstall app', async function () { // ensure we don't hit NXDOMAIN in the mean time await browser.get('about:blank'); @@ -145,19 +191,25 @@ describe('Application life cycle test', function () { // test update it('can install app for update', function () { execSync(`cloudron install --appstore-id org.traccar.cloudronapp --location ${LOCATION}`, EXEC_ARGS); }); + it('can get app information', getAppInfo); it('can login', login.bind(null, ADMIN_USERNAME, ADMIN_PASSWORD)); it('can add device', addDevice); it('device exists', deviceExists); it('can logout', logout); + // LDAP login + it('can login as normal user with email', login.bind(null, process.env.EMAIL, process.env.PASSWORD)); + it('can logout', logout); + it('can update', function () { execSync(`cloudron update --app ${app.id}`, EXEC_ARGS); }); it('can login', login.bind(null, ADMIN_USERNAME, ADMIN_PASSWORD)); it('device exists', deviceExists); it('can logout', logout); - it('can login as normal user with username', login.bind(null, process.env.USERNAME, process.env.PASSWORD)); + // OIDC login + it('can login as normal user via OIDC', loginOIDC.bind(null, process.env.USERNAME, process.env.PASSWORD)); it('can logout', logout); it('uninstall app', async function () { diff --git a/traccar.xml.template b/traccar.xml.template index 38d32ac..4495d30 100644 --- a/traccar.xml.template +++ b/traccar.xml.template @@ -25,13 +25,12 @@ ##CLOUDRON_APP_ORIGIN## - true - ##CLOUDRON_LDAP_URL## - ##CLOUDRON_LDAP_USERS_BASE_DN## - username - username=:login - ##CLOUDRON_LDAP_BIND_DN## - ##CLOUDRON_LDAP_BIND_PASSWORD## + ##CLOUDRON_OIDC_CLIENT_ID## + ##CLOUDRON_OIDC_CLIENT_SECRET## + ##CLOUDRON_OIDC_ISSUER## + ##CLOUDRON_OIDC_AUTH_ENDPOINT## + ##CLOUDRON_OIDC_TOKEN_ENDPOINT## + ##CLOUDRON_OIDC_PROFILE_ENDPOINT## smtp.gmail.com 587