LDAP to OIDC auth migration, tests refactored

This commit is contained in:
Vladimir D 2023-09-26 15:30:53 +04:00
parent dd44f81d04
commit e176d6c705
6 changed files with 94 additions and 32 deletions

View file

@ -13,7 +13,7 @@
"mysql": { }, "mysql": { },
"sendmail": { "supportsDisplayName": true }, "sendmail": { "supportsDisplayName": true },
"localstorage": { }, "localstorage": { },
"ldap": { } "oidc": { "loginRedirectUri": "/user/oauth2/cloudron/callback" }
}, },
"tcpPorts": { "tcpPorts": {
"SSH_PORT": { "SSH_PORT": {

View file

@ -48,6 +48,11 @@ ENABLED = true
; APP_DATA_PATH/attachments ; APP_DATA_PATH/attachments
PATH = PATH =
[oauth2_client]
ENABLE_AUTO_REGISTRATION = true
USERNAME = sub
UPDATE_AVATAR = false
ACCOUNT_LINKING = auto
[mailer] [mailer]
ENABLED = true ENABLED = true

View file

@ -25,6 +25,25 @@ setup_ldap_source() {
fi fi
} }
migrate_ldap_users_to_oidc() {
set -eu
echo "==> migrate LDAP to OIDC"
mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -N -B -e \
"UPDATE user u, (select id from login_source WHERE name='cloudron' and type='6') ls SET u.login_type=6, u.login_source=u.id WHERE u.login_type=2 AND u.login_source=1"
}
setup_oidc_source() {
set -eu
echo "==> Setup OIDC source"
now=$(date +%s)
mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -e \
"REPLACE INTO login_source (id, type, name, is_active, cfg, created_unix, updated_unix) VALUES (1,6,'cloudron', 1,'{\"Provider\":\"openidConnect\",\"ClientID\":\"${CLOUDRON_OIDC_CLIENT_ID}\",\"ClientSecret\":\"${CLOUDRON_OIDC_CLIENT_SECRET}\",\"OpenIDConnectAutoDiscoveryURL\":\"${CLOUDRON_OIDC_ISSUER}/.well-known/openid-configuration\",\"CustomURLMapping\":null,\"IconURL\":\"\",\"Scopes\":[\"openid email profile\"],\"RequiredClaimName\":\"\",\"RequiredClaimValue\":\"\",\"GroupClaimName\":\"\",\"AdminGroup\":\"\",\"GroupTeamMap\":\"\",\"GroupTeamMapRemoval\":false,\"RestrictedGroup\":\"\"}','${now}','${now}')"
}
setup_root_user() { setup_root_user() {
set -eu set -eu
@ -51,7 +70,15 @@ setup_auth() {
setup_ldap_source setup_ldap_source
fi fi
user_count=$(mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -N -B -e "SELECT count(*) FROM user;") if [[ -n "${CLOUDRON_OIDC_ISSUER:-}" ]]; then
setup_oidc_source
ldap_users_to_migrate=$(mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -N -B -e "select count(*) from user WHERE login_type=2 AND login_source=1")
if [ "${ldap_users_to_migrate:0}" -gt 0 ]; then
migrate_ldap_users_to_oidc
fi
fi
user_count=$(mysql -u"${CLOUDRON_MYSQL_USERNAME}" -p"${CLOUDRON_MYSQL_PASSWORD}" -h mysql --database="${CLOUDRON_MYSQL_DATABASE}" -N -B -e "SELECT count(*) FROM user")
# be careful, not to create root user for existing LDAP based installs # be careful, not to create root user for existing LDAP based installs
if [[ "${user_count}" == "0" ]]; then if [[ "${user_count}" == "0" ]]; then
echo "==> Setting up root user for first run" echo "==> Setting up root user for first run"

44
test/package-lock.json generated
View file

@ -9,11 +9,11 @@
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"chromedriver": "^115.0.0", "chromedriver": "^117.0.3",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"selenium-webdriver": "^4.10.0", "selenium-webdriver": "^4.13.0",
"superagent": "^8.0.9" "superagent": "^8.1.2"
} }
}, },
"node_modules/@testim/chrome-version": { "node_modules/@testim/chrome-version": {
@ -236,9 +236,9 @@
} }
}, },
"node_modules/chromedriver": { "node_modules/chromedriver": {
"version": "115.0.0", "version": "117.0.3",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-115.0.0.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-117.0.3.tgz",
"integrity": "sha512-mkPL+sXMLMUenoXCiKREw+cBl3ibfhiWxkiv9ByIPpqtrrInCt9zKdOolAsbmN/ndlH51WtT5ukUKbeRdrpikg==", "integrity": "sha512-c2rk2eGK5zZFBJMdviUlAJfQEBuPNIKfal4+rTFVYAmrWbMPYAqPozB+rIkc1lDP/Ryw44lPiqKglrI01ILhTQ==",
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@testim/chrome-version": "^1.1.3", "@testim/chrome-version": "^1.1.3",
@ -253,7 +253,7 @@
"chromedriver": "bin/chromedriver" "chromedriver": "bin/chromedriver"
}, },
"engines": { "engines": {
"node": ">=16" "node": ">=18"
} }
}, },
"node_modules/cliui": { "node_modules/cliui": {
@ -1171,9 +1171,9 @@
] ]
}, },
"node_modules/selenium-webdriver": { "node_modules/selenium-webdriver": {
"version": "4.10.0", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.13.0.tgz",
"integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", "integrity": "sha512-8JS0h5E0Sq7gNfbGg8LVaQ+Eqek97tvOONn3Jmy+NiWfb12WYpftz4VTC4D2JT4wakdG6VUzGKpA8cFGg0IjkA==",
"dependencies": { "dependencies": {
"jszip": "^3.10.1", "jszip": "^3.10.1",
"tmp": "^0.2.1", "tmp": "^0.2.1",
@ -1272,9 +1272,9 @@
} }
}, },
"node_modules/superagent": { "node_modules/superagent": {
"version": "8.0.9", "version": "8.1.2",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
"integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==", "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
"dependencies": { "dependencies": {
"component-emitter": "^1.3.0", "component-emitter": "^1.3.0",
"cookiejar": "^2.1.4", "cookiejar": "^2.1.4",
@ -1657,9 +1657,9 @@
} }
}, },
"chromedriver": { "chromedriver": {
"version": "115.0.0", "version": "117.0.3",
"resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-115.0.0.tgz", "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-117.0.3.tgz",
"integrity": "sha512-mkPL+sXMLMUenoXCiKREw+cBl3ibfhiWxkiv9ByIPpqtrrInCt9zKdOolAsbmN/ndlH51WtT5ukUKbeRdrpikg==", "integrity": "sha512-c2rk2eGK5zZFBJMdviUlAJfQEBuPNIKfal4+rTFVYAmrWbMPYAqPozB+rIkc1lDP/Ryw44lPiqKglrI01ILhTQ==",
"requires": { "requires": {
"@testim/chrome-version": "^1.1.3", "@testim/chrome-version": "^1.1.3",
"axios": "^1.4.0", "axios": "^1.4.0",
@ -2323,9 +2323,9 @@
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
}, },
"selenium-webdriver": { "selenium-webdriver": {
"version": "4.10.0", "version": "4.13.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.10.0.tgz", "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.13.0.tgz",
"integrity": "sha512-hSQPw6jgc+ej/UEcdQPG/iBwwMeCEgZr9HByY/J8ToyXztEqXzU9aLsIyrlj1BywBcStO4JQK/zMUWWrV8+riA==", "integrity": "sha512-8JS0h5E0Sq7gNfbGg8LVaQ+Eqek97tvOONn3Jmy+NiWfb12WYpftz4VTC4D2JT4wakdG6VUzGKpA8cFGg0IjkA==",
"requires": { "requires": {
"jszip": "^3.10.1", "jszip": "^3.10.1",
"tmp": "^0.2.1", "tmp": "^0.2.1",
@ -2402,9 +2402,9 @@
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
}, },
"superagent": { "superagent": {
"version": "8.0.9", "version": "8.1.2",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
"integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==", "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
"requires": { "requires": {
"component-emitter": "^1.3.0", "component-emitter": "^1.3.0",
"cookiejar": "^2.1.4", "cookiejar": "^2.1.4",

View file

@ -9,10 +9,10 @@
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"chromedriver": "^115.0.0", "chromedriver": "^117.0.3",
"expect.js": "^0.3.1", "expect.js": "^0.3.1",
"mocha": "^10.2.0", "mocha": "^10.2.0",
"selenium-webdriver": "^4.10.0", "selenium-webdriver": "^4.13.0",
"superagent": "^8.0.9" "superagent": "^8.1.2"
} }
} }

View file

@ -33,6 +33,7 @@ describe('Application life cycle test', function () {
const SSH_PORT = 29420; const SSH_PORT = 29420;
let app, browser; let app, browser;
var athenticated_by_oidc = false;
const repodir = '/tmp/testrepo'; const repodir = '/tmp/testrepo';
const reponame = 'testrepo'; const reponame = 'testrepo';
@ -56,6 +57,11 @@ describe('Application life cycle test', function () {
expect(app).to.be.an('object'); expect(app).to.be.an('object');
} }
async function waitForElement(elem) {
await browser.wait(until.elementLocated(elem), TIMEOUT);
await browser.wait(until.elementIsVisible(browser.findElement(elem)), TIMEOUT);
}
function sleep(millis) { function sleep(millis) {
return new Promise(resolve => setTimeout(resolve, millis)); return new Promise(resolve => setTimeout(resolve, millis));
} }
@ -93,6 +99,29 @@ describe('Application life cycle test', function () {
await login('root', 'changeme'); await login('root', 'changeme');
} }
async function loginOIDC(username, password) {
browser.manage().deleteAllCookies();
await browser.get(`https://${app.fqdn}/user/login`);
await browser.sleep(2000);
await browser.findElement(By.xpath('//a[contains(@class, "openidConnect") and contains(., "Sign in with 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 waitForElement(By.xpath('//img[contains(@class, "avatar")]'));
}
async function logout() { async function logout() {
await browser.get('https://' + app.fqdn); await browser.get('https://' + app.fqdn);
@ -182,7 +211,7 @@ describe('Application life cycle test', function () {
it('can send mail', sendMail); it('can send mail', sendMail);
it('can logout', logout); it('can logout', logout);
it('can login', login.bind(null, username, password)); it('can login', loginOIDC.bind(null, username, password));
it('can set avatar', setAvatar); it('can set avatar', setAvatar);
it('can get avatar', checkAvatar); it('can get avatar', checkAvatar);
@ -198,7 +227,7 @@ describe('Application life cycle test', function () {
it('can restart app', function () { execSync('cloudron restart --app ' + app.id); }); it('can restart app', function () { execSync('cloudron restart --app ' + app.id); });
xit('can login', login.bind(null, username, password)); // no need to relogin since session persists xit('can login', loginOIDC.bind(null, username, password)); // no need to relogin since session persists
it('displays correct clone url', checkCloneUrl); it('displays correct clone url', checkCloneUrl);
it('can clone the url', cloneRepo); it('can clone the url', cloneRepo);
it('file exists in repo', fileExists); it('file exists in repo', fileExists);
@ -206,7 +235,7 @@ describe('Application life cycle test', function () {
it('backup app', function () { execSync('cloudron backup create --app ' + app.id, EXEC_ARGS); }); it('backup app', function () { execSync('cloudron backup create --app ' + app.id, EXEC_ARGS); });
it('restore app', function () { execSync('cloudron restore --app ' + app.id, EXEC_ARGS); }); it('restore app', function () { execSync('cloudron restore --app ' + app.id, EXEC_ARGS); });
it('can login', login.bind(null, username, password)); it('can login', loginOIDC.bind(null, username, password));
it('can get avatar', checkAvatar); it('can get avatar', checkAvatar);
it('can clone the url', cloneRepo); it('can clone the url', cloneRepo);
it('file exists in repo', function () { expect(fs.existsSync(repodir + '/newfile')).to.be(true); }); it('file exists in repo', function () { expect(fs.existsSync(repodir + '/newfile')).to.be(true); });
@ -220,7 +249,7 @@ describe('Application life cycle test', function () {
}); });
it('can get app information', getAppInfo); it('can get app information', getAppInfo);
it('can login', login.bind(null, username, password)); it('can login', loginOIDC.bind(null, username, password));
it('can get avatar', checkAvatar); it('can get avatar', checkAvatar);
it('displays correct clone url', checkCloneUrl); it('displays correct clone url', checkCloneUrl);
it('can clone the url', cloneRepo); it('can clone the url', cloneRepo);
@ -248,6 +277,7 @@ describe('Application life cycle test', function () {
it('can install app', function () { execSync(`cloudron install --appstore-id ${app.manifest.id} --location ${LOCATION} -p SSH_PORT=${SSH_PORT}`, EXEC_ARGS); }); it('can install app', function () { execSync(`cloudron install --appstore-id ${app.manifest.id} --location ${LOCATION} -p SSH_PORT=${SSH_PORT}`, EXEC_ARGS); });
it('can get app information', getAppInfo); it('can get app information', getAppInfo);
// to be replaced with loginOIDC in the next release
it('can login', login.bind(null, username, password)); it('can login', login.bind(null, username, password));
it('can set avatar', setAvatar); it('can set avatar', setAvatar);
it('can get avatar', checkAvatar); it('can get avatar', checkAvatar);
@ -263,7 +293,7 @@ describe('Application life cycle test', function () {
it('can send mail', sendMail); it('can send mail', sendMail);
it('can logout', logout); it('can logout', logout);
it('can login', login.bind(null, username, password)); it('can login', loginOIDC.bind(null, username, password));
it('can get avatar', checkAvatar); it('can get avatar', checkAvatar);
it('can clone the url', cloneRepo); it('can clone the url', cloneRepo);
it('file exists in cloned repo', fileExists); it('file exists in cloned repo', fileExists);