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