Compare commits

..

34 Commits

Author SHA1 Message Date
Johannes Zellner 9f03a9a6e2 Do first a patch release 2019-07-03 19:14:00 +02:00
Johannes Zellner ce406c7088 Add groupCount to backendSettings 2019-07-03 13:47:23 +02:00
Girish Ramakrishnan e7127df30d remove app ownerId
this is unused
2019-07-02 21:23:51 -07:00
Girish Ramakrishnan 10e2817257 Add 4.2 changes 2019-07-02 19:24:49 -07:00
Girish Ramakrishnan 337a47c62b typo 2019-07-02 16:46:14 -07:00
Girish Ramakrishnan 14bdac20ef cloudron-support: add --admin-login 2019-07-02 16:37:46 -07:00
Girish Ramakrishnan 88e2b3f9aa Add userCount to backend settings 2019-07-01 23:26:43 -07:00
Girish Ramakrishnan 22d731f06d Fix LDAP not accessible via scheduler containers
Check the IP address against scheduler containers as well
2019-06-27 16:12:09 -07:00
Girish Ramakrishnan e3d288ef7d Add MONGODB_OPLOG_URL for apps that require oplog access
remove the replicaSet arg (it causes problems in tests but not in apps).
it causes some issues because of hostname not being set properly/docker network.
this only prevents the client from using replicaSet features which doesn't apply
to us since it is single instance.
2019-06-27 13:19:59 -07:00
Girish Ramakrishnan 455f597543 Add changes 2019-06-26 21:40:03 -07:00
Girish Ramakrishnan 8c9e626920 Remove twitter and chat link from the login footer 2019-06-26 21:39:07 -07:00
Girish Ramakrishnan 5a000c1ff4 Add MONGODB_REPLICA_SET for mongodb addon
This can be useful for constructing the ?replSet= part of the URI.
replicaSet is used by the client to discover the secondaries and fallback
automatically. if not provided, they just talk to primary.
2019-06-26 21:29:43 -07:00
Girish Ramakrishnan ddf634bfb2 o2 has stopped working 2019-06-26 18:40:07 -07:00
Girish Ramakrishnan 89d3b8cc6a Make hostname more explicit 2019-06-26 14:21:47 -07:00
Girish Ramakrishnan 49af6d09a2 CLOUDRON_APP_HOSTNAME should be the app id always
name is the container name which is "unique"
2019-06-26 14:21:43 -07:00
Girish Ramakrishnan e5b0cac284 Clarify comment 2019-06-26 14:13:26 -07:00
Girish Ramakrishnan 6f33900f85 Fix failing test 2019-06-21 15:05:28 -07:00
Girish Ramakrishnan 514823af7d More changes 2019-06-21 13:34:24 -07:00
Girish Ramakrishnan 65b058f563 More changes 2019-06-21 11:12:25 -07:00
Girish Ramakrishnan 7c8560deff Ensure redis addon vars are replaced with manifest v2 2019-06-20 23:43:18 -07:00
Girish Ramakrishnan 6bbe2613b4 Return 412 for bad password 2019-06-20 16:44:53 -07:00
Girish Ramakrishnan 5771478e4b Use 412 for invalid token, otherwise user gets logged out 2019-06-20 16:37:16 -07:00
Girish Ramakrishnan e13030bc89 fontawesome location has changed 2019-06-20 16:27:27 -07:00
Girish Ramakrishnan 0a0ac93a55 Use pattern match instead for handling v1 to v2 upgrades 2019-06-20 11:59:02 -07:00
Girish Ramakrishnan 214fb50e74 Add 4.1.5 changes 2019-06-20 11:49:39 -07:00
Girish Ramakrishnan 959f8ee31e Ensure passwords are preserved with v2 manifest 2019-06-20 11:46:50 -07:00
Girish Ramakrishnan cb0d75be37 Add changes 2019-06-19 09:19:05 -07:00
Girish Ramakrishnan 11353e9e3a DO DNS API break means this value must atleast be 30
(cherry picked from commit c0c5561aac)
2019-06-17 20:13:32 -07:00
Girish Ramakrishnan 8cd5c15c2b Fix mail auth with manifest v2 2019-06-17 11:13:59 -07:00
Girish Ramakrishnan b86b8b8ee1 4.1.4 changes
(cherry picked from commit 73a44d1fb2)
2019-06-16 17:59:18 -07:00
Girish Ramakrishnan c5f6e6b028 Prefix mysql url/database variables 2019-06-15 10:06:51 -07:00
Girish Ramakrishnan 592d8abc58 Roll back async package
something is broken, not sure what
2019-06-14 16:24:41 -07:00
Girish Ramakrishnan d93068fc62 Update package lock 2019-06-14 15:32:11 -07:00
Girish Ramakrishnan a864af52df Update packages 2019-06-14 15:31:13 -07:00
31 changed files with 709 additions and 680 deletions
+24
View File
@@ -1625,3 +1625,27 @@
* Fix name.com DNS backend issue for naked domains
* Add DigitalOcean Frankfurt (fra1) region for backup storage
[4.1.3]
* Update manifest format package
[4.1.4]
* Add CLOUDRON_ prefix to MySQL addon variables
[4.1.5]
* Make the terminal addon button inject variables based on manifest version
* Preserve addon passwords correctly when using v2 manifest
* Show error message instead of logging out user when invalid 2FA token is provided
* Ensure redis vars are renamed with manifest v2
* Add missing Scaleway Object Storage to restore UI
* Fix Exoscale endpoints in restore UI
* Reset the app icon when showing the configure UI
[4.1.6]
* Fix issue where CLOUDRON_APP_HOSTNAME was incorrectly set
* Remove chat link from the footer of login screen
* Add support for oplog tailing in mongodb
* Fix LDAP not accessible via scheduler containers
[4.1.7]
* Fix issue where login looped when admin bit was removed
@@ -0,0 +1,14 @@
'use strict';
var async = require('async');
exports.up = function(db, callback) {
async.series([
db.runSql.bind(db, 'ALTER TABLE apps DROP FOREIGN KEY apps_owner_constraint'),
db.runSql.bind(db, 'ALTER TABLE apps DROP COLUMN ownerId')
], callback);
};
exports.down = function(db, callback) {
callback();
};
-3
View File
@@ -93,9 +93,6 @@ CREATE TABLE IF NOT EXISTS apps(
oldConfigJson TEXT, // used to pass old config to apptask (configure, restore)
updateConfigJson TEXT, // used to pass new config to apptask (update)
ownerId VARCHAR(128),
FOREIGN KEY(ownerId) REFERENCES users(id),
PRIMARY KEY(id));
CREATE TABLE IF NOT EXISTS appPortBindings(
+452 -293
View File
File diff suppressed because it is too large Load Diff
+18 -18
View File
@@ -14,40 +14,40 @@
"node": ">=4.0.0 <=4.1.1"
},
"dependencies": {
"@google-cloud/dns": "^0.9.2",
"@google-cloud/dns": "^1.1.0",
"@google-cloud/storage": "^2.5.0",
"@sindresorhus/df": "^3.1.0",
"async": "^2.6.2",
"aws-sdk": "^2.441.0",
"body-parser": "^1.18.3",
"aws-sdk": "^2.476.0",
"body-parser": "^1.19.0",
"cloudron-manifestformat": "^2.15.0",
"connect": "^3.6.6",
"connect": "^3.7.0",
"connect-ensure-login": "^0.1.1",
"connect-lastmile": "^1.0.2",
"connect-timeout": "^1.9.0",
"cookie-parser": "^1.4.4",
"cookie-session": "^1.3.3",
"cron": "^1.7.0",
"csurf": "^1.9.0",
"db-migrate": "^0.11.5",
"cron": "^1.7.1",
"csurf": "^1.10.0",
"db-migrate": "^0.11.6",
"db-migrate-mysql": "^1.1.10",
"debug": "^4.1.1",
"dockerode": "^2.5.8",
"ejs": "^2.6.1",
"ejs-cli": "^2.0.1",
"express": "^4.16.4",
"express-session": "^1.16.1",
"express": "^4.17.1",
"express-session": "^1.16.2",
"js-yaml": "^3.13.1",
"json": "^9.0.6",
"ldapjs": "^1.0.2",
"lodash": "^4.17.11",
"lodash.chunk": "^4.2.0",
"mime": "^2.4.2",
"mime": "^2.4.4",
"moment-timezone": "^0.5.25",
"morgan": "^1.9.1",
"multiparty": "^4.2.1",
"mysql": "^2.17.1",
"nodemailer": "^6.1.1",
"nodemailer": "^6.2.1",
"nodemailer-smtp-transport": "^2.7.4",
"oauth2orize": "^1.11.0",
"once": "^1.4.0",
@@ -60,25 +60,25 @@
"progress-stream": "^2.0.0",
"proxy-middleware": "^0.15.0",
"qrcode": "^1.3.3",
"readdirp": "^3.0.0",
"readdirp": "^3.0.2",
"request": "^2.88.0",
"rimraf": "^2.6.3",
"s3-block-read-stream": "^0.5.0",
"safetydance": "^0.7.1",
"semver": "^6.0.0",
"semver": "^6.1.1",
"showdown": "^1.9.0",
"speakeasy": "^2.0.0",
"split": "^1.0.1",
"superagent": "^5.0.2",
"superagent": "^5.0.9",
"supererror": "^0.7.2",
"tar-fs": "github:cloudron-io/tar-fs#ignore_stat_error",
"tar-stream": "^2.0.1",
"tar-stream": "^2.1.0",
"tldjs": "^2.3.1",
"underscore": "^1.9.1",
"uuid": "^3.3.2",
"valid-url": "^1.0.9",
"validator": "^10.11.0",
"ws": "^6.2.1",
"validator": "^11.0.0",
"ws": "^7.0.0",
"xml2js": "^0.4.19"
},
"devDependencies": {
@@ -88,7 +88,7 @@
"mocha": "^6.1.4",
"mock-aws-s3": "git+https://github.com/cloudron-io/mock-aws-s3.git",
"nock": "^10.0.6",
"node-sass": "^4.11.0",
"node-sass": "^4.12.0",
"recursive-readdir": "^2.2.2"
},
"scripts": {
+9 -1
View File
@@ -13,6 +13,7 @@ HELP_MESSAGE="
This script collects diagnostic information to help debug server related issues
Options:
--admin-login Login as administrator
--enable-ssh Enable SSH access for the Cloudron support team
--help Show this message
"
@@ -25,13 +26,20 @@ fi
enableSSH="false"
args=$(getopt -o "" -l "help,enable-ssh" -n "$0" -- "$@")
args=$(getopt -o "" -l "help,enable-ssh,admin-login" -n "$0" -- "$@")
eval set -- "${args}"
while true; do
case "$1" in
--help) echo -e "${HELP_MESSAGE}"; exit 0;;
--enable-ssh) enableSSH="true"; shift;;
--admin-login)
admin_username=$(mysql -NB -uroot -ppassword -e "SELECT username FROM box.users WHERE admin=1 LIMIT 1" 2>/dev/null)
admin_password=$(pwgen -1s 12)
printf '{"%s":"%s"}\n' "${admin_username}" "${admin_password}" > /tmp/cloudron_ghost.json
echo "Login as ${admin_username} / ${admin_password} . Remove /tmp/cloudron_ghost.json when done."
exit 0
;;
--) break;;
*) echo "Unknown option $1"; exit 1;;
esac
+73 -67
View File
@@ -906,7 +906,7 @@ function setupSendMail(app, options, callback) {
debugApp(app, 'Setting up SendMail');
appdb.getAddonConfigByName(app.id, 'sendmail', 'MAIL_SMTP_PASSWORD', function (error, existingPassword) {
appdb.getAddonConfigByName(app.id, 'sendmail', '%MAIL_SMTP_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
var password = error ? hat(4 * 48) : existingPassword; // see box#565 for password length
@@ -944,7 +944,7 @@ function setupRecvMail(app, options, callback) {
debugApp(app, 'Setting up recvmail');
appdb.getAddonConfigByName(app.id, 'recvmail', 'MAIL_IMAP_PASSWORD', function (error, existingPassword) {
appdb.getAddonConfigByName(app.id, 'recvmail', '%MAIL_IMAP_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
var password = error ? hat(4 * 48) : existingPassword; // see box#565 for password length
@@ -1040,7 +1040,7 @@ function setupMySql(app, options, callback) {
debugApp(app, 'Setting up mysql');
appdb.getAddonConfigByName(app.id, 'mysql', 'MYSQL_PASSWORD', function (error, existingPassword) {
appdb.getAddonConfigByName(app.id, 'mysql', '%MYSQL_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
const tmp = mysqlDatabaseName(app.id);
@@ -1069,11 +1069,11 @@ function setupMySql(app, options, callback) {
];
if (options.multipleDatabases) {
env = env.concat({ name: 'MYSQL_DATABASE_PREFIX', value: `${data.prefix}_` });
env = env.concat({ name: `${envPrefix}MYSQL_DATABASE_PREFIX`, value: `${data.prefix}_` });
} else {
env = env.concat(
{ name: 'MYSQL_URL', value: `mysql://${data.username}:${data.password}@mysql/${data.database}` },
{ name: 'MYSQL_DATABASE', value: data.database }
{ name: `${envPrefix}MYSQL_URL`, value: `mysql://${data.username}:${data.password}@mysql/${data.database}` },
{ name: `${envPrefix}MYSQL_DATABASE`, value: data.database }
);
}
@@ -1256,7 +1256,7 @@ function setupPostgreSql(app, options, callback) {
const { database, username } = postgreSqlNames(app.id);
appdb.getAddonConfigByName(app.id, 'postgresql', 'POSTGRESQL_PASSWORD', function (error, existingPassword) {
appdb.getAddonConfigByName(app.id, 'postgresql', '%POSTGRESQL_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
const data = {
@@ -1431,13 +1431,14 @@ function setupMongoDb(app, options, callback) {
debugApp(app, 'Setting up mongodb');
appdb.getAddonConfigByName(app.id, 'mongodb', 'MONGODB_PASSWORD', function (error, existingPassword) {
appdb.getAddonConfigByName(app.id, 'mongodb', '%MONGODB_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
const data = {
database: app.id,
username: app.id,
password: error ? hat(4 * 128) : existingPassword
password: error ? hat(4 * 128) : existingPassword,
oplog: !!options.oplog
};
getServiceDetails('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error, result) {
@@ -1450,7 +1451,7 @@ function setupMongoDb(app, options, callback) {
const envPrefix = app.manifest.manifestVersion <= 1 ? '' : 'CLOUDRON_';
var env = [
{ name: `${envPrefix}MONGODB_URL`, value : `mongodb://${data.username}:${data.password}@mongodb/${data.database}` },
{ name: `${envPrefix}MONGODB_URL`, value : `mongodb://${data.username}:${data.password}@mongodb:27017/${data.database}` },
{ name: `${envPrefix}MONGODB_USERNAME`, value : data.username },
{ name: `${envPrefix}MONGODB_PASSWORD`, value: data.password },
{ name: `${envPrefix}MONGODB_HOST`, value : 'mongodb' },
@@ -1458,6 +1459,10 @@ function setupMongoDb(app, options, callback) {
{ name: `${envPrefix}MONGODB_DATABASE`, value : data.database }
];
if (options.oplog) {
env.push({ name: `${envPrefix}MONGODB_OPLOG_URL`, value : `mongodb://${data.username}:${data.password}@mongodb:27017/local?authSource=${data.database}` });
}
debugApp(app, 'Setting mongodb addon config to %j', env);
appdb.setAddonConfig(app.id, 'mongodb', env, callback);
});
@@ -1564,68 +1569,69 @@ function setupRedis(app, options, callback) {
const redisName = 'redis-' + app.id;
docker.inspect(redisName, function (error, result) {
if (!error) {
debug(`Re-using existing redis container with state: ${result.State}`);
return callback();
appdb.getAddonConfigByName(app.id, 'redis', '%REDIS_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
const redisPassword = error ? hat(4 * 48) : existingPassword; // see box#362 for password length
const redisServiceToken = hat(4 * 48);
// Compute redis memory limit based on app's memory limit (this is arbitrary)
var memoryLimit = app.memoryLimit || app.manifest.memoryLimit || 0;
if (memoryLimit === -1) { // unrestricted (debug mode)
memoryLimit = 0;
} else if (memoryLimit === 0 || memoryLimit <= (2 * 1024 * 1024 * 1024)) { // less than 2G (ram+swap)
memoryLimit = 150 * 1024 * 1024; // 150m
} else {
memoryLimit = 600 * 1024 * 1024; // 600m
}
appdb.getAddonConfigByName(app.id, 'redis', 'REDIS_PASSWORD', function (error, existingPassword) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return callback(error);
const tag = infra.images.redis.tag;
const label = app.fqdn;
// note that we do not add appId label because this interferes with the stop/start app logic
const cmd = `docker run --restart=always -d --name=${redisName} \
--hostname ${redisName} \
--label=location=${label} \
--net cloudron \
--net-alias ${redisName} \
--log-driver syslog \
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag="${redisName}" \
-m ${memoryLimit/2} \
--memory-swap ${memoryLimit} \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_REDIS_PASSWORD="${redisPassword}" \
-e CLOUDRON_REDIS_TOKEN="${redisServiceToken}" \
-v "${paths.PLATFORM_DATA_DIR}/redis/${app.id}:/var/lib/redis" \
--label isCloudronManaged=true \
--read-only -v /tmp -v /run ${tag}`;
const redisPassword = error ? hat(4 * 48) : existingPassword; // see box#362 for password length
const redisServiceToken = hat(4 * 48);
const envPrefix = app.manifest.manifestVersion <= 1 ? '' : 'CLOUDRON_';
// Compute redis memory limit based on app's memory limit (this is arbitrary)
var memoryLimit = app.memoryLimit || app.manifest.memoryLimit || 0;
var env = [
{ name: `${envPrefix}REDIS_URL`, value: 'redis://redisuser:' + redisPassword + '@redis-' + app.id },
{ name: `${envPrefix}REDIS_PASSWORD`, value: redisPassword },
{ name: `${envPrefix}REDIS_HOST`, value: redisName },
{ name: `${envPrefix}REDIS_PORT`, value: '6379' }
];
if (memoryLimit === -1) { // unrestricted (debug mode)
memoryLimit = 0;
} else if (memoryLimit === 0 || memoryLimit <= (2 * 1024 * 1024 * 1024)) { // less than 2G (ram+swap)
memoryLimit = 150 * 1024 * 1024; // 150m
} else {
memoryLimit = 600 * 1024 * 1024; // 600m
}
const tag = infra.images.redis.tag;
const label = app.fqdn;
// note that we do not add appId label because this interferes with the stop/start app logic
const cmd = `docker run --restart=always -d --name=${redisName} \
--hostname ${redisName} \
--label=location=${label} \
--net cloudron \
--net-alias ${redisName} \
--log-driver syslog \
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag="${redisName}" \
-m ${memoryLimit/2} \
--memory-swap ${memoryLimit} \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_REDIS_PASSWORD="${redisPassword}" \
-e CLOUDRON_REDIS_TOKEN="${redisServiceToken}" \
-v "${paths.PLATFORM_DATA_DIR}/redis/${app.id}:/var/lib/redis" \
--label isCloudronManaged=true \
--read-only -v /tmp -v /run ${tag}`;
const envPrefix = app.manifest.manifestVersion <= 1 ? '' : 'CLOUDRON_';
var env = [
{ name: `${envPrefix}REDIS_URL`, value: 'redis://redisuser:' + redisPassword + '@redis-' + app.id },
{ name: `${envPrefix}REDIS_PASSWORD`, value: redisPassword },
{ name: `${envPrefix}REDIS_HOST`, value: redisName },
{ name: `${envPrefix}REDIS_PORT`, value: '6379' }
];
async.series([
shell.exec.bind(null, 'startRedis', cmd),
appdb.setAddonConfig.bind(null, app.id, 'redis', env),
waitForService.bind(null, 'redis-' + app.id, 'CLOUDRON_REDIS_TOKEN')
], function (error) {
if (error) debug('Error setting up redis: ', error);
callback(error);
});
async.series([
(next) => {
docker.inspect(redisName, function (inspectError, result) {
if (!inspectError) {
debug(`Re-using existing redis container with state: ${JSON.stringify(result.State)}`);
return next();
}
shell.exec('startRedis', cmd, next);
});
},
appdb.setAddonConfig.bind(null, app.id, 'redis', env),
waitForService.bind(null, 'redis-' + app.id, 'CLOUDRON_REDIS_TOKEN')
], function (error) {
if (error) debug('Error setting up redis: ', error);
callback(error);
});
});
}
+11 -43
View File
@@ -25,9 +25,6 @@ exports = module.exports = {
setRunCommand: setRunCommand,
getAppStoreIds: getAppStoreIds,
setOwner: setOwner,
transferOwnership: transferOwnership,
// installation codes (keep in sync in UI)
ISTATE_PENDING_INSTALL: 'pending_install', // installs and fresh reinstalls
ISTATE_PENDING_CLONE: 'pending_clone', // clone
@@ -70,7 +67,7 @@ var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationSta
'apps.accessRestrictionJson', 'apps.restoreConfigJson', 'apps.oldConfigJson', 'apps.updateConfigJson', 'apps.memoryLimit',
'apps.label', 'apps.tagsJson',
'apps.sso', 'apps.debugModeJson', 'apps.robotsTxt', 'apps.enableBackup',
'apps.creationTime', 'apps.updateTime', 'apps.ownerId', 'apps.mailboxName', 'apps.enableAutomaticUpdate',
'apps.creationTime', 'apps.updateTime', 'apps.mailboxName', 'apps.enableAutomaticUpdate',
'apps.dataDir', 'apps.ts', 'apps.healthTime' ].join(',');
var PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(',');
@@ -257,14 +254,13 @@ function getAll(callback) {
});
}
function add(id, appStoreId, manifest, location, domain, ownerId, portBindings, data, callback) {
function add(id, appStoreId, manifest, location, domain, portBindings, data, callback) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof appStoreId, 'string');
assert(manifest && typeof manifest === 'object');
assert.strictEqual(typeof manifest.version, 'string');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof ownerId, 'string');
assert.strictEqual(typeof portBindings, 'object');
assert(data && typeof data === 'object');
assert.strictEqual(typeof callback, 'function');
@@ -290,10 +286,10 @@ function add(id, appStoreId, manifest, location, domain, ownerId, portBindings,
queries.push({
query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, '
+ 'restoreConfigJson, sso, debugModeJson, robotsTxt, ownerId, mailboxName, label, tagsJson) '
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
+ 'restoreConfigJson, sso, debugModeJson, robotsTxt, mailboxName, label, tagsJson) '
+ ' VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
args: [ id, appStoreId, manifestJson, installationState, accessRestrictionJson, memoryLimit, restoreConfigJson,
sso, debugModeJson, robotsTxt, ownerId, mailboxName, label, tagsJson ]
sso, debugModeJson, robotsTxt, mailboxName, label, tagsJson ]
});
queries.push({
@@ -621,13 +617,13 @@ function getAddonConfigByAppId(appId, callback) {
});
}
function getAppIdByAddonConfigValue(addonId, name, value, callback) {
function getAppIdByAddonConfigValue(addonId, namePattern, value, callback) {
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof namePattern, 'string');
assert.strictEqual(typeof value, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT appId FROM appAddonConfigs WHERE addonId = ? AND name = ? AND value = ?', [ addonId, name, value ], function (error, results) {
database.query('SELECT appId FROM appAddonConfigs WHERE addonId = ? AND name LIKE ? AND value = ?', [ addonId, namePattern, value ], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
@@ -635,44 +631,16 @@ function getAppIdByAddonConfigValue(addonId, name, value, callback) {
});
}
function getAddonConfigByName(appId, addonId, name, callback) {
function getAddonConfigByName(appId, addonId, namePattern, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof addonId, 'string');
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof namePattern, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ? AND name = ?', [ appId, addonId, name ], function (error, results) {
database.query('SELECT value FROM appAddonConfigs WHERE appId = ? AND addonId = ? AND name LIKE ?', [ appId, addonId, namePattern ], function (error, results) {
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND));
callback(null, results[0].value);
});
}
function setOwner(appId, ownerId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof ownerId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('UPDATE apps SET ownerId=? WHERE appId=?', [ ownerId, appId ], function (error, results) {
if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such user'));
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
if (results.length === 0) return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such app'));
callback(null);
});
}
function transferOwnership(oldOwnerId, newOwnerId, callback) {
assert.strictEqual(typeof oldOwnerId, 'string');
assert.strictEqual(typeof newOwnerId, 'string');
assert.strictEqual(typeof callback, 'function');
database.query('UPDATE apps SET ownerId=? WHERE ownerId=?', [ newOwnerId, oldOwnerId ], function (error) {
if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new DatabaseError(DatabaseError.NOT_FOUND, 'No such user'));
if (error) return callback(new DatabaseError(DatabaseError.INTERNAL_ERROR, error));
callback(null);
});
}
+13 -36
View File
@@ -46,9 +46,6 @@ exports = module.exports = {
downloadFile: downloadFile,
uploadFile: uploadFile,
setOwner: setOwner,
transferOwnership: transferOwnership,
PORT_TYPE_TCP: 'tcp',
PORT_TYPE_UDP: 'udp',
@@ -384,13 +381,13 @@ function removeInternalFields(app) {
'location', 'domain', 'fqdn', 'mailboxName',
'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit',
'sso', 'debugMode', 'robotsTxt', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags',
'label', 'alternateDomains', 'ownerId', 'env', 'enableAutomaticUpdate', 'dataDir');
'label', 'alternateDomains', 'env', 'enableAutomaticUpdate', 'dataDir');
}
// non-admins can only see these
function removeRestrictedFields(app) {
return _.pick(app,
'id', 'appStoreId', 'installationState', 'installationProgress', 'runState', 'health', 'ownerId',
'id', 'appStoreId', 'installationState', 'installationProgress', 'runState', 'health',
'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label');
}
@@ -486,6 +483,7 @@ function getByContainerId(containerId, callback) {
});
}
// returns the app associated with this IP (app or scheduler)
function getByIpAddress(ip, callback) {
assert.strictEqual(typeof ip, 'string');
assert.strictEqual(typeof callback, 'function');
@@ -493,7 +491,14 @@ function getByIpAddress(ip, callback) {
docker.getContainerIdByIp(ip, function (error, containerId) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
getByContainerId(containerId, callback);
docker.inspect(containerId, function (error, result) {
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
const appId = safe.query(result, 'Config.Labels.appId', null);
if (!appId) return callback(new AppsError(AppsError.NOT_FOUND, 'No such app'));
get(appId, callback);
});
});
}
@@ -585,7 +590,6 @@ function install(data, user, auditSource, callback) {
enableAutomaticUpdate = 'enableAutomaticUpdate' in data ? data.enableAutomaticUpdate : true,
backupId = data.backupId || null,
backupFormat = data.backupFormat || 'tgz',
ownerId = data.ownerId,
alternateDomains = data.alternateDomains || [],
env = data.env || {},
mailboxName = data.mailboxName || '',
@@ -679,7 +683,7 @@ function install(data, user, auditSource, callback) {
env: env
};
appdb.add(appId, appStoreId, manifest, location, domain, ownerId, translatePortBindings(portBindings, manifest), data, function (error) {
appdb.add(appId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings, data.alternateDomains));
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
@@ -1072,14 +1076,12 @@ function clone(appId, data, user, auditSource, callback) {
domain = data.domain.toLowerCase(),
portBindings = data.portBindings || null,
backupId = data.backupId,
ownerId = data.ownerId,
mailboxName = data.mailboxName || '';
assert.strictEqual(typeof backupId, 'string');
assert.strictEqual(typeof location, 'string');
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof portBindings, 'object');
assert(ownerId === null || typeof ownerId === 'string');
get(appId, function (error, app) {
if (error) return callback(error);
@@ -1128,7 +1130,7 @@ function clone(appId, data, user, auditSource, callback) {
env: app.env
};
appdb.add(newAppId, app.appStoreId, manifest, location, domain, ownerId, translatePortBindings(portBindings, manifest), data, function (error) {
appdb.add(newAppId, app.appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error, location, domainObject, portBindings, []));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
@@ -1519,28 +1521,3 @@ function uploadFile(appId, sourceFilePath, destFilePath, callback) {
readFile.pipe(stream);
});
}
function setOwner(appId, ownerId, callback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof callback, 'function');
appdb.setOwner(appId, ownerId, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
callback();
});
}
function transferOwnership(oldOwnerId, newOwnerId, callback) {
assert.strictEqual(typeof oldOwnerId, 'string');
assert.strictEqual(typeof newOwnerId, 'string');
assert.strictEqual(typeof callback, 'function');
appdb.transferOwnership(oldOwnerId, newOwnerId, function (error) {
if (error && error.reason === DatabaseError.NOT_FOUND) return callback(new AppsError(AppsError.NOT_FOUND, error.message));
if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error));
callback();
});
}
+19 -1
View File
@@ -32,12 +32,14 @@ var apps = require('./apps.js'),
debug = require('debug')('box:appstore'),
domains = require('./domains.js'),
eventlog = require('./eventlog.js'),
groups = require('./groups.js'),
mail = require('./mail.js'),
os = require('os'),
safe = require('safetydance'),
semver = require('semver'),
settings = require('./settings.js'),
superagent = require('superagent'),
users = require('./users.js'),
util = require('util');
function AppstoreError(reason, errorOrMessage) {
@@ -206,7 +208,7 @@ function unpurchaseApp(appId, data, callback) {
function sendAliveStatus(callback) {
callback = callback || NOOP_CALLBACK;
var allSettings, allDomains, mailDomains, loginEvents;
let allSettings, allDomains, mailDomains, loginEvents, userCount, groupCount;
async.series([
function (callback) {
@@ -236,6 +238,20 @@ function sendAliveStatus(callback) {
loginEvents = result;
callback();
});
},
function (callback) {
users.count(function (error, result) {
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
userCount = result;
callback();
});
},
function (callback) {
groups.count(function (error, result) {
if (error) return callback(new AppstoreError(AppstoreError.INTERNAL_ERROR, error));
groupCount = result;
callback();
});
}
], function (error) {
if (error) return callback(error);
@@ -255,6 +271,8 @@ function sendAliveStatus(callback) {
catchAllCount: mailDomains.filter(function (d) { return d.catchAll.length !== 0; }).length,
relayProviders: Array.from(new Set(mailDomains.map(function (d) { return d.relay.provider; })))
},
userCount: userCount,
groupCount: groupCount,
appAutoupdatePattern: allSettings[settings.APP_AUTOUPDATE_PATTERN_KEY],
boxAutoupdatePattern: allSettings[settings.BOX_AUTOUPDATE_PATTERN_KEY],
timeZone: allSettings[settings.TIME_ZONE_KEY],
+1 -1
View File
@@ -111,7 +111,7 @@ function upsert(domainObject, location, type, values, callback) {
name: name,
data: value,
priority: priority,
ttl: 1
ttl: 30 // Recent DO DNS API break means this value must atleast be 30
};
if (i >= result.length) {
+4 -3
View File
@@ -176,18 +176,19 @@ function createSubcontainer(app, name, cmd, options, callback) {
assert.strictEqual(typeof callback, 'function');
var docker = exports.connection,
isAppContainer = !cmd; // non app-containers are like scheduler containers
isAppContainer = !cmd; // non app-containers are like scheduler and exec (terminal) containers
var manifest = app.manifest;
var exposedPorts = {}, dockerPortBindings = { };
var domain = app.fqdn;
const hostname = isAppContainer ? app.id : name;
const envPrefix = manifest.manifestVersion <= 1 ? '' : 'CLOUDRON_';
let stdEnv = [
'CLOUDRON=1',
'CLOUDRON_PROXY_IP=172.18.0.1',
`CLOUDRON_APP_HOSTNAME=${name}`,
`CLOUDRON_APP_HOSTNAME=${app.id}`,
`${envPrefix}WEBADMIN_ORIGIN=${config.adminOrigin()}`,
`${envPrefix}API_ORIGIN=${config.adminOrigin()}`,
`${envPrefix}APP_ORIGIN=https://${domain}`,
@@ -240,7 +241,7 @@ function createSubcontainer(app, name, cmd, options, callback) {
var containerOptions = {
name: name, // for referencing containers
Tty: isAppContainer,
Hostname: name,
Hostname: hostname,
Image: app.manifest.dockerImage,
Cmd: (isAppContainer && app.debugMode && app.debugMode.cmd) ? app.debugMode.cmd : cmd,
Env: stdEnv.concat(addonEnv).concat(portEnv).concat(appEnv),
+13 -1
View File
@@ -20,7 +20,9 @@ exports = module.exports = {
getGroups: getGroups,
setMembership: setMembership,
getMembership: getMembership
getMembership: getMembership,
count: count
};
var assert = require('assert'),
@@ -268,3 +270,13 @@ function getGroups(userId, callback) {
callback(null, results);
});
}
function count(callback) {
assert.strictEqual(typeof callback, 'function');
groupdb.count(function (error, count) {
if (error) return callback(new GroupsError(GroupsError.INTERNAL_ERROR, error));
callback(null, count);
});
}
+1 -1
View File
@@ -17,7 +17,7 @@ exports = module.exports = {
'images': {
'mysql': { repo: 'cloudron/mysql', tag: 'cloudron/mysql:2.0.2@sha256:a28320f313785816be60e3f865e09065504170a3d20ed37de675c719b32b01eb' },
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:2.0.2@sha256:6dcee0731dfb9b013ed94d56205eee219040ee806c7e251db3b3886eaa4947ff' },
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:2.0.2@sha256:95e006390ddce7db637e1672eb6f3c257d3c2652747424f529b1dee3cbe6728c' },
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:2.1.0@sha256:6d1bf221cfe6124957e2c58b57c0a47214353496009296acb16adf56df1da9d5' },
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.0.0@sha256:8a88dd334b62b578530a014ca1a2425a54cb9df1e475f5d3a36806e5cfa22121' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.3.1@sha256:9693e3ae42a12a7ac8cf5df94d828d46f5b22b4e2e1c7d1bc614d6ee2a22c365' },
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.0.2@sha256:454f035d60b768153d4f31210380271b5ba1c09367c9d95c7fa37f9e39d2f59c' },
+8 -8
View File
@@ -135,7 +135,7 @@ function userSearch(req, res, next) {
var dn = ldap.parseDN('cn=' + entry.id + ',ou=users,dc=cloudron');
var groups = [ GROUP_USERS_DN ];
if (entry.admin || req.app.ownerId === entry.id) groups.push(GROUP_ADMINS_DN);
if (entry.admin) groups.push(GROUP_ADMINS_DN);
var displayName = entry.displayName || entry.username || ''; // displayName can be empty and username can be null
var nameParts = displayName.split(' ');
@@ -155,7 +155,7 @@ function userSearch(req, res, next) {
givenName: firstName,
username: entry.username,
samaccountname: entry.username, // to support ActiveDirectory clients
isadmin: (entry.admin || req.app.ownerId === entry.id) ? 1 : 0,
isadmin: entry.admin,
memberof: groups
}
};
@@ -195,7 +195,7 @@ function groupSearch(req, res, next) {
groups.forEach(function (group) {
var dn = ldap.parseDN('cn=' + group.name + ',ou=groups,dc=cloudron');
var members = group.admin ? result.filter(function (entry) { return entry.admin || req.app.ownerId === entry.id; }) : result;
var members = group.admin ? result.filter(function (entry) { return entry.admin; }) : result;
var obj = {
dn: dn.toString(),
@@ -244,7 +244,7 @@ function groupAdminsCompare(req, res, next) {
// we only support memberuid here, if we add new group attributes later add them here
if (req.attribute === 'memberuid') {
var found = result.find(function (u) { return u.id === req.value; });
if (found && (found.admin || req.app.ownerId == found.id)) return res.end(true);
if (found && found.admin) return res.end(true);
}
res.end(false);
@@ -560,13 +560,13 @@ function authenticateMailAddon(req, res, next) {
if (addonId === 'recvmail' && !domain.enabled) return next(new ldap.NoSuchObjectError(req.dn.toString()));
let name;
if (addonId === 'sendmail') name = 'MAIL_SMTP_PASSWORD';
else if (addonId === 'recvmail') name = 'MAIL_IMAP_PASSWORD';
let namePattern; // manifest v2 has a CLOUDRON_ prefix for names
if (addonId === 'sendmail') namePattern = '%MAIL_SMTP_PASSWORD';
else if (addonId === 'recvmail') namePattern = '%MAIL_IMAP_PASSWORD';
else return next(new ldap.OperationsError('Invalid DN'));
// note: with sendmail addon, apps can send mail without a mailbox (unlike users)
appdb.getAppIdByAddonConfigValue(addonId, name, req.credentials || '', function (error, appId) {
appdb.getAppIdByAddonConfigValue(addonId, namePattern, req.credentials || '', function (error, appId) {
if (error && error.reason !== DatabaseError.NOT_FOUND) return next(new ldap.OperationsError(error.message));
if (appId) { // matched app password
eventlog.add(eventlog.ACTION_APP_LOGIN, { authType: 'ldap', mailboxId: email }, { appId: appId, addonId: addonId });
-1
View File
@@ -126,7 +126,6 @@ function checkOutboundPort25(callback) {
'smtp.gmail.com',
'smtp.live.com',
'smtp.mail.yahoo.com',
'smtp.o2.ie',
'smtp.comcast.net',
'smtp.1und1.de',
]);
-2
View File
@@ -1,8 +1,6 @@
<footer class="text-center">
<span class="text-muted">&copy; 2016-19 <a href="https://cloudron.io" target="_blank">Cloudron</a></span>
<span class="text-muted"><a href="https://twitter.com/cloudron_io" target="_blank">Twitter <i class="fa fa-twitter"></i></a></span>
<span class="text-muted"><a href="https://chat.cloudron.io" target="_blank">Chat <i class="fa fa-comments"></i></a></span>
</footer>
</div>
+1 -1
View File
@@ -13,7 +13,7 @@
<link href="<%= adminOrigin %>/theme.css" rel="stylesheet">
<!-- Custom Fonts -->
<link href="<%= adminOrigin %>/3rdparty/css/font-awesome.min.css" rel="stylesheet" rel="stylesheet" type="text/css">
<link href="<%= adminOrigin %>/3rdparty/fontawesome/css/all.min.css" rel="stylesheet" rel="stylesheet" type="text/css">
<!-- jQuery-->
<script src="<%= adminOrigin %>/3rdparty/js/jquery.min.js"></script>
-18
View File
@@ -21,8 +21,6 @@ exports = module.exports = {
cloneApp: cloneApp,
setOwner: setOwner,
uploadFile: uploadFile,
downloadFile: downloadFile
};
@@ -80,7 +78,6 @@ function installApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
var data = req.body;
data.ownerId = req.user.id;
// atleast one
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
@@ -233,7 +230,6 @@ function cloneApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
var data = req.body;
data.ownerId = req.user.id;
debug('Clone app id:%s', req.params.id);
@@ -584,17 +580,3 @@ function downloadFile(req, res, next) {
stream.pipe(res);
});
}
function setOwner(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string'));
apps.setOwner(req.params.id, req.body.ownerId, function (error) {
if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, error.message));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, { }));
});
}
+1 -1
View File
@@ -85,7 +85,7 @@ function enableTwoFactorAuthentication(req, res, next) {
users.enableTwoFactorAuthentication(req.user.id, req.body.totpToken, function (error) {
if (error && error.reason === UsersError.NOT_FOUND) return next(new HttpError(404, 'User not found'));
if (error && error.reason === UsersError.BAD_TOKEN) return next(new HttpError(403, 'Invalid token'));
if (error && error.reason === UsersError.BAD_TOKEN) return next(new HttpError(412, 'Invalid token'));
if (error && error.reason === UsersError.ALREADY_EXISTS) return next(new HttpError(409, 'TwoFactor Authentication is already enabled'));
if (error) return next(new HttpError(500, error));
+2 -3
View File
@@ -31,7 +31,7 @@ const DOMAIN_0 = {
let AUDIT_SOURCE = { ip: '1.2.3.4' };
var token = null, ownerId = null;
var token = null;
function setup(done) {
nock.cleanAll();
@@ -51,7 +51,6 @@ function setup(done) {
expect(result.statusCode).to.eql(201);
// stash token for further use
ownerId = result.body.userId;
token = result.body.token;
callback();
@@ -60,7 +59,7 @@ function setup(done) {
function addApp(callback) {
var manifest = { version: '0.0.1', manifestVersion: 1, dockerImage: 'foo', healthCheckPath: '/', httpPort: 3, title: 'ok', addons: { } };
appdb.add('appid', 'appStoreId', manifest, 'location', DOMAIN_0.domain, ownerId, [ ] /* portBindings */, { }, callback);
appdb.add('appid', 'appStoreId', manifest, 'location', DOMAIN_0.domain, [ ] /* portBindings */, { }, callback);
},
function createSettings(callback) {
+8 -13
View File
@@ -64,8 +64,7 @@ describe('OAuth2', function () {
domain: DOMAIN_0.domain,
portBindings: {},
accessRestriction: null,
memoryLimit: 0,
ownerId: USER_0.id
memoryLimit: 0
};
var APP_1 = {
@@ -76,8 +75,7 @@ describe('OAuth2', function () {
domain: DOMAIN_0.domain,
portBindings: {},
accessRestriction: { users: [ 'foobar' ] },
memoryLimit: 0,
ownerId: USER_0.id
memoryLimit: 0
};
var APP_2 = {
@@ -88,8 +86,7 @@ describe('OAuth2', function () {
domain: DOMAIN_0.domain,
portBindings: {},
accessRestriction: { users: [ USER_0.id ] },
memoryLimit: 0,
ownerId: USER_0.id
memoryLimit: 0
};
var APP_3 = {
@@ -100,8 +97,7 @@ describe('OAuth2', function () {
domain: DOMAIN_0.domain,
portBindings: {},
accessRestriction: { groups: [ 'someothergroup', 'admin', 'anothergroup' ] },
memoryLimit: 0,
ownerId: USER_0.id
memoryLimit: 0
};
// unknown app
@@ -225,14 +221,13 @@ describe('OAuth2', function () {
expect(error).to.not.be.ok();
// update the global objects to reflect the new user id
USER_0.id = APP_0.ownerId = APP_1.ownerId = APP_2.ownerId = APP_3.ownerId = userObject.id;
APP_2.accessRestriction = { users: [ 'foobar', userObject.id ] };
async.series([
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.ownerId, APP_1.portBindings, APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.ownerId, APP_2.portBindings, APP_2),
appdb.add.bind(null, APP_3.id, APP_3.appStoreId, APP_3.manifest, APP_3.location, APP_3.domain, APP_3.ownerId, APP_3.portBindings, APP_3),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.portBindings, APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.portBindings, APP_2),
appdb.add.bind(null, APP_3.id, APP_3.appStoreId, APP_3.manifest, APP_3.location, APP_3.domain, APP_3.portBindings, APP_3),
appdb.update.bind(null, APP_2.id, APP_2)
], callback);
+1 -1
View File
@@ -248,7 +248,7 @@ describe('Profile API', function () {
.query({ access_token: token_0 })
.send({ password: 'some wrong password', newPassword: 'MOre#$%34' })
.end(function (err, res) {
expect(res.statusCode).to.equal(400);
expect(res.statusCode).to.equal(412);
done();
});
});
+2 -17
View File
@@ -10,8 +10,7 @@ exports = module.exports = {
verifyPassword: verifyPassword,
createInvite: createInvite,
sendInvite: sendInvite,
setGroups: setGroups,
transferOwnership: transferOwnership
setGroups: setGroups
};
var assert = require('assert'),
@@ -132,7 +131,7 @@ function verifyPassword(req, res, next) {
if (typeof req.body.password !== 'string') return next(new HttpError(400, 'API call requires user password'));
users.verifyWithUsername(req.user.username, req.body.password, function (error) {
if (error && error.reason === UsersError.WRONG_PASSWORD) return next(new HttpError(400, 'Password incorrect'));
if (error && error.reason === UsersError.WRONG_PASSWORD) return next(new HttpError(412, 'Password incorrect'));
if (error && error.reason === UsersError.NOT_FOUND) return next(new HttpError(404, 'No such user'));
if (error) return next(new HttpError(500, error));
@@ -179,20 +178,6 @@ function setGroups(req, res, next) {
});
}
function transferOwnership(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.userId, 'string');
if (typeof req.body.ownerId !== 'string') return next(new HttpError(400, 'ownerId must be a string'));
users.transferOwnership(req.params.userId, req.body.ownerId, auditSource.fromRequest(req), function (error) {
if (error && error.reason === UsersError.NOT_FOUND) return next(new HttpError(404, 'No such user'));
if (error) return next(new HttpError(500, error));
next(new HttpSuccess(200, {}));
});
}
function changePassword(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.userId, 'string');
-2
View File
@@ -173,7 +173,6 @@ function initializeExpressSync() {
router.put ('/api/v1/users/:userId/groups', usersManageScope, routes.users.setGroups);
router.post('/api/v1/users/:userId/send_invite', usersManageScope, routes.users.sendInvite);
router.post('/api/v1/users/:userId/create_invite', usersManageScope, routes.users.createInvite);
router.post('/api/v1/users/:userId/transfer', usersManageScope, routes.users.transferOwnership);
// Group management
router.get ('/api/v1/groups', usersReadScope, routes.groups.list);
@@ -240,7 +239,6 @@ function initializeExpressSync() {
router.post('/api/v1/apps/:id/clone', appsManageScope, routes.apps.cloneApp);
router.get ('/api/v1/apps/:id/download', appsManageScope, routes.apps.downloadFile);
router.post('/api/v1/apps/:id/upload', appsManageScope, multipart, routes.apps.uploadFile);
router.post('/api/v1/apps/:id/owner', appsManageScope, routes.apps.setOwner);
// settings routes (these are for the settings tab - avatar & name have public routes for normal users. see above)
router.get ('/api/v1/settings/:setting', settingsScope, routes.settings.get);
+3 -6
View File
@@ -115,7 +115,6 @@ describe('Apps', function () {
memoryLimit: 0,
robotsTxt: null,
sso: false,
ownerId: USER_0.id,
env: {
'CUSTOM_KEY': 'CUSTOM_VALUE'
},
@@ -135,7 +134,6 @@ describe('Apps', function () {
portBindings: {},
accessRestriction: { users: [ 'someuser' ], groups: [ GROUP_0.id ] },
memoryLimit: 0,
ownerId: USER_0.id,
env: {},
dataDir: ''
};
@@ -155,7 +153,6 @@ describe('Apps', function () {
memoryLimit: 0,
robotsTxt: null,
sso: false,
ownerId: USER_0.id,
env: {},
dataDir: ''
};
@@ -177,9 +174,9 @@ describe('Apps', function () {
groupdb.add.bind(null, GROUP_0.id, GROUP_0.name),
groupdb.add.bind(null, GROUP_1.id, GROUP_1.name),
groups.addMember.bind(null, GROUP_0.id, USER_1.id),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_1.ownerId, apps._translatePortBindings(APP_1.portBindings, APP_1.manifest), APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, APP_2.ownerId, apps._translatePortBindings(APP_2.portBindings, APP_2.manifest), APP_2),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
appdb.add.bind(null, APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, apps._translatePortBindings(APP_1.portBindings, APP_1.manifest), APP_1),
appdb.add.bind(null, APP_2.id, APP_2.appStoreId, APP_2.manifest, APP_2.location, APP_2.domain, apps._translatePortBindings(APP_2.portBindings, APP_2.manifest), APP_2),
], done);
});
+2 -3
View File
@@ -91,8 +91,7 @@ var APP = {
httpPort: 4567,
portBindings: null,
accessRestriction: null,
memoryLimit: 0,
ownerId: ADMIN.id
memoryLimit: 0
};
var awsHostedZones;
@@ -123,7 +122,7 @@ describe('apptask', function () {
database._clear,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
userdb.add.bind(null, ADMIN.id, ADMIN),
appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.domain, APP.ownerId, APP.portBindings, APP)
appdb.add.bind(null, APP.id, APP.appStoreId, APP.manifest, APP.location, APP.domain, APP.portBindings, APP)
], done);
});
+4 -7
View File
@@ -416,7 +416,6 @@ describe('database', function () {
debugMode: null,
robotsTxt: null,
enableBackup: true,
ownerId: USER_0.id,
env: {},
mailboxName: 'talktome',
enableAutomaticUpdate: true,
@@ -426,7 +425,7 @@ describe('database', function () {
};
it('cannot delete referenced domain', function (done) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, function (error) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0, function (error) {
expect(error).to.be(null);
domaindb.del(DOMAIN_0.domain, function (error) {
@@ -996,7 +995,6 @@ describe('database', function () {
debugMode: null,
robotsTxt: null,
enableBackup: true,
ownerId: USER_0.id,
alternateDomains: [],
env: {
'CUSTOM_KEY': 'CUSTOM_VALUE'
@@ -1030,7 +1028,6 @@ describe('database', function () {
debugMode: null,
robotsTxt: null,
enableBackup: true,
ownerId: USER_0.id,
alternateDomains: [],
env: {},
mailboxName: 'callme',
@@ -1065,7 +1062,7 @@ describe('database', function () {
});
it('add succeeds', function (done) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, APP_0.portBindings, APP_0, function (error) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.portBindings, APP_0, function (error) {
expect(error).to.be(null);
done();
});
@@ -1089,7 +1086,7 @@ describe('database', function () {
});
it('add of same app fails', function (done) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, [], APP_0, function (error) {
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, [], APP_0, function (error) {
expect(error).to.be.a(DatabaseError);
expect(error.reason).to.be(DatabaseError.ALREADY_EXISTS);
done();
@@ -1162,7 +1159,7 @@ describe('database', function () {
});
it('add second app succeeds', function (done) {
appdb.add(APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, APP_0.ownerId, [], APP_1, function (error) {
appdb.add(APP_1.id, APP_1.appStoreId, APP_1.manifest, APP_1.location, APP_1.domain, [], APP_1, function (error) {
expect(error).to.be(null);
done();
});
+12 -78
View File
@@ -79,7 +79,6 @@ var APP_0 = {
restoreConfig: null,
oldConfig: null,
memoryLimit: 4294967296,
ownerId: null,
mailboxName: 'some-location-0.app'
};
@@ -105,11 +104,12 @@ function setup(done) {
users.createOwner(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE, function (error, result) {
if (error) return callback(error);
USER_0.id = APP_0.ownerId = result.id;
USER_0.id = result.id;
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, callback);
callback();
});
},
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
(done) => mailboxdb.addMailbox(USER_0.username.toLowerCase(), DOMAIN_0.domain, USER_0.id, done),
(done) => mailboxdb.setAliasesForName(USER_0.username.toLowerCase(), DOMAIN_0.domain, [ USER_0_ALIAS.toLocaleLowerCase() ], done),
appdb.update.bind(null, APP_0.id, { containerId: APP_0.containerId }),
@@ -185,6 +185,15 @@ function setup(done) {
}
};
status = 200;
} else if (req.method === 'GET' && req.url === '/containers/someContainerId/json') {
answer = {
Config: {
Labels: {
appId: APP_0.id
}
}
};
status = 200;
}
res.writeHead(status);
@@ -554,42 +563,6 @@ describe('Ldap', function () {
});
});
});
it ('lists the owner as admin', function (done) {
// make a normal user the owner
appdb.update(APP_0.id, { ownerId: USER_1.id, accessRestriction: { users: [], groups: [ GROUP_ID ] } }, function (error) {
expect(error).to.be(null);
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: 'objectcategory=person'
};
client.search('ou=users,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
entries.sort(function (a, b) { return a.username > b.username; });
expect(entries[0].username).to.equal(USER_0.username.toLowerCase());
expect(entries[1].username).to.equal(USER_1.username.toLowerCase());
expect(entries[1].isadmin).to.equal('1');
client.unbind();
appdb.update(APP_0.id, { ownerId: USER_0.id, accessRestriction: null }, done);
});
});
});
});
});
describe('search groups', function () {
@@ -760,45 +733,6 @@ describe('Ldap', function () {
});
});
});
it ('shows owner as admin', function (done) {
appdb.update(APP_0.id, { ownerId: USER_1.id, accessRestriction: { users: [], groups: [ GROUP_ID ] } }, function (error) {
expect(error).to.be(null);
var client = ldap.createClient({ url: 'ldap://127.0.0.1:' + config.get('ldapPort') });
var opts = {
filter: '&(objectclass=group)(cn=*)'
};
client.search('ou=groups,dc=cloudron', opts, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an(EventEmitter);
var entries = [];
result.on('searchEntry', function (entry) { entries.push(entry.object); });
result.on('error', done);
result.on('end', function (result) {
expect(result.status).to.equal(0);
expect(entries.length).to.equal(2);
expect(entries[0].cn).to.equal('users');
expect(entries[0].memberuid.length).to.equal(2);
expect(entries[0].memberuid[0]).to.equal(USER_0.id);
expect(entries[0].memberuid[1]).to.equal(USER_1.id);
expect(entries[1].cn).to.equal('admins');
expect(entries[1].memberuid.length).to.equal(2);
expect(entries[1].memberuid[0]).to.equal(USER_0.id);
expect(entries[1].memberuid[1]).to.equal(USER_1.id);
client.unbind();
appdb.update(APP_0.id, { ownerId: USER_0.id, accessRestriction: null }, done);
});
});
});
});
});
// ldapsearch -LLL -E pr=10/noprompt -x -h localhost -p 3002 -b cn=userName0@example.com,ou=mailboxes,dc=cloudron objectclass=mailbox
+9 -30
View File
@@ -245,8 +245,7 @@ describe('updatechecker - app - manual (email)', function () {
portBindings: { PORT: 5678 },
healthy: null,
accessRestriction: null,
memoryLimit: 0,
ownerId: null
memoryLimit: 0
};
before(function (done) {
@@ -263,14 +262,8 @@ describe('updatechecker - app - manual (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
mail.addDomain.bind(null, DOMAIN_0.domain),
function (next) {
users.createOwner(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE, function (error, userObject) {
if (error) return next(error);
APP_0.ownerId = userObject.id;
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, next);
});
},
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
settings.setAppAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
], done);
@@ -363,8 +356,7 @@ describe('updatechecker - app - automatic (no email)', function () {
portBindings: { PORT: 5678 },
healthy: null,
accessRestriction: null,
memoryLimit: 0,
ownerId: null
memoryLimit: 0
};
before(function (done) {
@@ -381,14 +373,8 @@ describe('updatechecker - app - automatic (no email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
mail.addDomain.bind(null, DOMAIN_0.domain),
function (next) {
users.createOwner(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE, function (error, userObject) {
if (error) return next(error);
APP_0.ownerId = userObject.id;
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, next);
});
},
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
], done);
@@ -437,8 +423,7 @@ describe('updatechecker - app - automatic free (email)', function () {
portBindings: { PORT: 5678 },
healthy: null,
accessRestriction: null,
memoryLimit: 0,
ownerId: null
memoryLimit: 0
};
before(function (done) {
@@ -455,14 +440,8 @@ describe('updatechecker - app - automatic free (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
mail.addDomain.bind(null, DOMAIN_0.domain),
function (next) {
users.createOwner(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE, function (error, userObject) {
if (error) return next(error);
APP_0.ownerId = userObject.id;
appdb.add(APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, APP_0.ownerId, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0, next);
});
},
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
appdb.add.bind(null, APP_0.id, APP_0.appStoreId, APP_0.manifest, APP_0.location, APP_0.domain, apps._translatePortBindings(APP_0.portBindings, APP_0.manifest), APP_0),
settings.setAppAutoupdatePattern.bind(null, '00 00 1,3,5,23 * * *'),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
], done);
+4 -21
View File
@@ -29,12 +29,11 @@ exports = module.exports = {
setTwoFactorAuthenticationSecret: setTwoFactorAuthenticationSecret,
enableTwoFactorAuthentication: enableTwoFactorAuthentication,
disableTwoFactorAuthentication: disableTwoFactorAuthentication,
transferOwnership: transferOwnership
count: count
};
var apps = require('./apps.js'),
AppsError = apps.AppsError,
assert = require('assert'),
let assert = require('assert'),
crypto = require('crypto'),
config = require('./config.js'),
constants = require('./constants.js'),
@@ -542,7 +541,7 @@ function createOwner(username, password, email, displayName, auditSource, callba
// This is only not allowed for the owner
if (username === '') return callback(new UsersError(UsersError.BAD_FIELD, 'Username cannot be empty'));
userdb.count(function (error, count) {
count(function (error, count) {
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
if (count !== 0) return callback(new UsersError(UsersError.ALREADY_EXISTS, 'Owner already exists'));
@@ -655,19 +654,3 @@ function disableTwoFactorAuthentication(userId, callback) {
callback(null);
});
}
function transferOwnership(oldOwnerId, newOwnerId, auditSource, callback) {
assert.strictEqual(typeof oldOwnerId, 'string');
assert.strictEqual(typeof newOwnerId, 'string');
assert(auditSource && typeof auditSource === 'object');
assert.strictEqual(typeof callback, 'function');
apps.transferOwnership(oldOwnerId, newOwnerId, function (error) {
if (error && error.reason === AppsError.NOT_FOUND) return callback(new UsersError(UsersError.NOT_FOUND, error.message));
if (error) return callback(new UsersError(UsersError.INTERNAL_ERROR, error));
eventlog.add(eventlog.ACTION_USER_TRANSFER, auditSource, { oldOwnerId: oldOwnerId, newOwnerId: newOwnerId });
callback(null);
});
}