Compare commits

..

4 Commits

Author SHA1 Message Date
Girish Ramakrishnan 92f143163b make app password work with sftp
(cherry picked from commit 9faae96d61)
(cherry picked from commit f3b979f112)
2020-03-26 21:56:45 -07:00
Girish Ramakrishnan 3c1a1f1b81 5.0.6 changelog 2020-03-26 19:30:52 -07:00
Girish Ramakrishnan 4bc7c70e2e make eventlog routes owner only
(cherry picked from commit 007a8d248d)
2020-03-26 19:08:43 -07:00
Girish Ramakrishnan cc6ddf50b1 5.0.5 changes 2020-03-25 07:57:39 -07:00
43 changed files with 1465 additions and 1333 deletions
-13
View File
@@ -1854,16 +1854,3 @@
* Make mail eventlog only visible to owners
* Make app password work with sftp
[5.1.0]
* Add turn addon
* Fix disk usage display
* Drop support for TLSv1 and TLSv1.1
* Make cert validation work for ECC certs
* Add type filter to mail eventlog
* mail: Fix listing of mailboxes and aliases in the UI
* branding: fix login page title
* Only a Cloudron owner can install/update/exec apps with the docker addon
* security: reset tokens are only valid for a day
* mail: fix eventlog db perms
* Fix various bugs in the disk graphs
+2 -12
View File
@@ -44,6 +44,7 @@ apt-get -y install \
linux-generic \
logrotate \
mysql-server-5.7 \
nginx-full \
openssh-server \
pwgen \
resolvconf \
@@ -53,17 +54,6 @@ apt-get -y install \
unbound \
xfsprogs
if [[ "${ubuntu_version}" == "16.04" ]]; then
echo "==> installing nginx for xenial for TLSv3 support"
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.14.0-1~xenial_amd64.deb -o /tmp/nginx.deb
# apt install with install deps (as opposed to dpkg -i)
apt install -y /tmp/nginx.deb
rm /tmp/nginx.deb
else
apt install -y nginx-full
fi
# on some providers like scaleway the sudo file is changed and we want to keep the old one
apt-get -o Dpkg::Options::="--force-confold" install -y sudo
@@ -121,7 +111,7 @@ for image in ${images}; do
done
echo "==> Install collectd"
if ! apt-get install -y libcurl3-gnutls collectd collectd-utils; then
if ! apt-get install -y collectd collectd-utils; then
# FQDNLookup is true in default debian config. The box code has a custom collectd.conf that fixes this
echo "Failed to install collectd. Presumably because of http://mailman.verplant.org/pipermail/collectd/2015-March/006491.html"
sed -e 's/^FQDNLookup true/FQDNLookup false/' -i /etc/collectd/collectd.conf
@@ -1,15 +0,0 @@
'use strict';
exports.up = function(db, callback) {
db.runSql('ALTER TABLE users ADD COLUMN resetTokenCreationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP', function (error) {
if (error) console.error(error);
callback(error);
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE users DROP COLUMN resetTokenCreationTime', function (error) {
if (error) console.error(error);
callback(error);
});
};
@@ -1,28 +0,0 @@
'use strict';
let async = require('async');
exports.up = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY mailboxDomain VARCHAR(128)', [], function (error) { // make it nullable
if (error) console.error(error);
// clear mailboxName/Domain for apps that do not use mail addons
db.all('SELECT * FROM apps', function (error, apps) {
if (error) return callback(error);
async.eachSeries(apps, function (app, iteratorDone) {
var manifest = JSON.parse(app.manifestJson);
if (manifest.addons['sendmail'] || manifest.addons['recvmail']) return iteratorDone();
db.runSql('UPDATE apps SET mailboxName=?, mailboxDomain=? WHERE id=?', [ null, null, app.id ], iteratorDone);
}, callback);
});
});
};
exports.down = function(db, callback) {
db.runSql('ALTER TABLE apps MODIFY manifestJson VARCHAR(128) NOT NULL', [], function (error) {
if (error) console.error(error);
callback(error);
});
};
+2 -5
View File
@@ -28,9 +28,6 @@ CREATE TABLE IF NOT EXISTS users(
twoFactorAuthenticationEnabled BOOLEAN DEFAULT false,
source VARCHAR(128) DEFAULT "",
role VARCHAR(32),
resetToken VARCHAR(128) DEFAULT "",
resetTokenCreationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
active BOOLEAN DEFAULT 1,
PRIMARY KEY(id));
@@ -79,8 +76,8 @@ CREATE TABLE IF NOT EXISTS apps(
reverseProxyConfigJson TEXT, // { robotsTxt, csp }
enableBackup BOOLEAN DEFAULT 1, // misnomer: controls automatic daily backups
enableAutomaticUpdate BOOLEAN DEFAULT 1,
mailboxName VARCHAR(128), // mailbox of this app
mailboxDomain VARCHAR(128), // mailbox domain of this apps
mailboxName VARCHAR(128), // mailbox of this app. default allocated as '.app'
mailboxDomain VARCHAR(128) NOT NULL, // mailbox domain of this apps
label VARCHAR(128), // display name
tagsJson VARCHAR(2048), // array of tags
dataDir VARCHAR(256) UNIQUE,
+11 -11
View File
@@ -736,22 +736,22 @@
}
},
"cloudron-manifestformat": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-5.1.1.tgz",
"integrity": "sha512-1mArahTp9qkYRQsUJfpT/x6est1qW+gKPF+HoFU0hPuOVuBdMkfu6UUmwZDYmQF4FrbQkir46GyQAJADaXBg6g==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/cloudron-manifestformat/-/cloudron-manifestformat-4.0.0.tgz",
"integrity": "sha512-St/Quu8ofQOf0rUAMaIsOL0u0dZ46irweU8rYVMvAXU0CGwSD9KDaeLW5NjGRg3FVjNzladUDVUE/BGD4rwEvA==",
"requires": {
"cron": "^1.8.2",
"cron": "^1.7.2",
"java-packagename-regex": "^1.0.0",
"safetydance": "1.0.0",
"semver": "^7.1.3",
"safetydance": "0.7.1",
"semver": "^6.3.0",
"tv4": "^1.3.0",
"validator": "^12.2.0"
"validator": "^12.0.0"
},
"dependencies": {
"semver": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz",
"integrity": "sha512-ekM0zfiA9SCBlsKa2X1hxyxiI4L3B6EbVJkkdgQXnSEEaHlGdvyodMruTiulSRWMMB4NeIuYNMC9rTKTz97GxA=="
"safetydance": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/safetydance/-/safetydance-0.7.1.tgz",
"integrity": "sha1-FOtNQqHKr8UUVVK2zmnJuIJN0qo="
},
"validator": {
"version": "12.2.0",
+1 -1
View File
@@ -20,7 +20,7 @@
"async": "^2.6.3",
"aws-sdk": "^2.610.0",
"body-parser": "^1.19.0",
"cloudron-manifestformat": "^5.1.1",
"cloudron-manifestformat": "^4.0.0",
"connect": "^3.7.0",
"connect-lastmile": "^1.2.2",
"connect-timeout": "^1.9.0",
+2 -5
View File
@@ -13,7 +13,7 @@ HELP_MESSAGE="
This script collects diagnostic information to help debug server related issues
Options:
--owner-login Login as owner
--admin-login Login as administrator
--enable-ssh Enable SSH access for the Cloudron support team
--help Show this message
"
@@ -26,7 +26,7 @@ fi
enableSSH="false"
args=$(getopt -o "" -l "help,enable-ssh,admin-login,owner-login" -n "$0" -- "$@")
args=$(getopt -o "" -l "help,enable-ssh,admin-login" -n "$0" -- "$@")
eval set -- "${args}"
while true; do
@@ -34,9 +34,6 @@ while true; do
--help) echo -e "${HELP_MESSAGE}"; exit 0;;
--enable-ssh) enableSSH="true"; shift;;
--admin-login)
# fall through
;&
--owner-login)
admin_username=$(mysql -NB -uroot -ppassword -e "SELECT username FROM box.users WHERE role='owner' LIMIT 1" 2>/dev/null)
admin_password=$(pwgen -1s 12)
ghost_file=/home/yellowtent/platformdata/cloudron_ghost.json
-9
View File
@@ -56,15 +56,6 @@ if [[ $(docker version --format {{.Client.Version}}) != "18.09.2" ]]; then
rm /tmp/containerd.deb /tmp/docker-ce-cli.deb /tmp/docker.deb
fi
readonly nginx_version=$(nginx -v)
if [[ "${nginx_version}" != *"1.14."* && "${ubuntu_version}" == "16.04" ]]; then
echo "==> installer: installing nginx for xenial for TLSv3 support"
curl -sL http://nginx.org/packages/ubuntu/pool/nginx/n/nginx/nginx_1.14.0-1~xenial_amd64.deb -o /tmp/nginx.deb
# apt install with install deps (as opposed to dpkg -i)
apt install -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" --force-yes /tmp/nginx.deb
rm /tmp/nginx.deb
fi
echo "==> installer: updating node"
if [[ "$(node --version)" != "v10.18.1" ]]; then
mkdir -p /usr/local/node-10.18.1
-5
View File
@@ -12,11 +12,6 @@ iptables -t filter -I CLOUDRON -m state --state RELATED,ESTABLISHED -j ACCEPT
# ssh is allowed alternately on port 202
iptables -A CLOUDRON -p tcp -m tcp -m multiport --dports 22,25,80,202,443,587,993,4190 -j ACCEPT
# turn and stun service
iptables -t filter -A CLOUDRON -p tcp -m multiport --dports 3478,5349 -j ACCEPT
iptables -t filter -A CLOUDRON -p udp -m multiport --dports 3478,5349 -j ACCEPT
iptables -t filter -A CLOUDRON -p udp -m multiport --dports 50000:51000 -j ACCEPT
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-request -j ACCEPT
iptables -t filter -A CLOUDRON -p icmp --icmp-type echo-reply -j ACCEPT
iptables -t filter -A CLOUDRON -p udp --sport 53 -j ACCEPT
+1 -3
View File
@@ -6,8 +6,7 @@ PATHS = [] # { name, dir, exclude }
INTERVAL = 60 * 60 * 12 # twice a day. change values in docker-graphite if you change this
def du(pathinfo):
# -B1 makes du print block sizes and not apparent sizes (to match df which also uses block sizes)
cmd = 'timeout 1800 du -DsB1 "{}"'.format(pathinfo['dir'])
cmd = 'timeout 1800 du -Dsb "{}"'.format(pathinfo['dir'])
if pathinfo['exclude'] != '':
cmd += ' --exclude "{}"'.format(pathinfo['exclude'])
@@ -27,7 +26,6 @@ def parseSize(size):
def dockerSize():
# use --format '{{json .}}' to dump the string. '{{if eq .Type "Images"}}{{.Size}}{{end}}' still creates newlines
# https://godoc.org/github.com/docker/go-units#HumanSize is used. so it's 1000 (KB) and not 1024 (KiB)
cmd = 'timeout 1800 docker system df --format "{{.Size}}" | head -n1'
try:
size = subprocess.check_output(cmd, shell=True).strip().decode('utf-8')
+22 -91
View File
@@ -22,6 +22,10 @@ exports = module.exports = {
getServiceDetails: getServiceDetails,
// exported for testing
_setupOauth: setupOauth,
_teardownOauth: teardownOauth,
SERVICE_STATUS_STARTING: 'starting', // container up, waiting for healthcheck
SERVICE_STATUS_ACTIVE: 'active',
SERVICE_STATUS_STOPPED: 'stopped'
@@ -63,13 +67,6 @@ const RMADDONDIR_CMD = path.join(__dirname, 'scripts/rmaddondir.sh');
// setup can be called multiple times for the same app (configure crash restart) and existing data must not be lost
// teardown is destructive. app data stored with the addon is lost
var KNOWN_ADDONS = {
turn: {
setup: setupTurn,
teardown: teardownTurn,
backup: NOOP,
restore: NOOP,
clear: NOOP
},
email: {
setup: setupEmail,
teardown: teardownEmail,
@@ -105,6 +102,13 @@ var KNOWN_ADDONS = {
restore: restoreMySql,
clear: clearMySql,
},
oauth: {
setup: setupOauth,
teardown: teardownOauth,
backup: NOOP,
restore: setupOauth,
clear: NOOP,
},
postgresql: {
setup: setupPostgreSql,
teardown: teardownPostgreSql,
@@ -150,11 +154,6 @@ var KNOWN_ADDONS = {
};
const KNOWN_SERVICES = {
turn: {
status: statusTurn,
restart: restartContainer.bind(null, 'turn'),
defaultMemoryLimit: 256 * 1024 * 1024
},
mail: {
status: containerStatus.bind(null, 'mail', 'CLOUDRON_MAIL_TOKEN'),
restart: mail.restartMail,
@@ -238,7 +237,6 @@ function rebuildService(serviceName, callback) {
// this attempts to recreate the service docker container if they don't exist but platform infra version is unchanged
// passing an infra version of 'none' will not attempt to purge existing data, not sure if this is good or bad
if (serviceName === 'turn') return startTurn({ version: 'none' }, callback);
if (serviceName === 'mongodb') return startMongodb({ version: 'none' }, callback);
if (serviceName === 'postgresql') return startPostgresql({ version: 'none' }, callback);
if (serviceName === 'mysql') return startMysql({ version: 'none' }, callback);
@@ -652,7 +650,6 @@ function startServices(existingInfra, callback) {
if (existingInfra.version !== infra.version) {
debug(`startServices: ${existingInfra.version} -> ${infra.version}. starting all services`);
startFuncs.push(
startTurn.bind(null, existingInfra),
startMysql.bind(null, existingInfra),
startPostgresql.bind(null, existingInfra),
startMongodb.bind(null, existingInfra),
@@ -661,7 +658,6 @@ function startServices(existingInfra, callback) {
} else {
assert.strictEqual(typeof existingInfra.images, 'object');
if (!existingInfra.images.turn || infra.images.turn.tag !== existingInfra.images.turn.tag) startFuncs.push(startTurn.bind(null, existingInfra));
if (infra.images.mysql.tag !== existingInfra.images.mysql.tag) startFuncs.push(startMysql.bind(null, existingInfra));
if (infra.images.postgresql.tag !== existingInfra.images.postgresql.tag) startFuncs.push(startPostgresql.bind(null, existingInfra));
if (infra.images.mongodb.tag !== existingInfra.images.mongodb.tag) startFuncs.push(startMongodb.bind(null, existingInfra));
@@ -681,7 +677,7 @@ function getEnvironment(app, callback) {
appdb.getAddonConfigByAppId(app.id, function (error, result) {
if (error) return callback(error);
if (app.manifest.addons['docker']) result.push({ name: 'CLOUDRON_DOCKER_HOST', value: `tcp://172.18.0.1:${constants.DOCKER_PROXY_PORT}` });
if (app.manifest.addons['docker']) result.push({ name: 'DOCKER_HOST', value: `tcp://172.18.0.1:${constants.DOCKER_PROXY_PORT}` });
return callback(null, result.map(function (e) { return e.name + '=' + e.value; }));
});
@@ -772,37 +768,30 @@ function teardownLocalStorage(app, options, callback) {
], callback);
}
function setupTurn(app, options, callback) {
function setupOauth(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
var turnSecret = safe.fs.readFileSync(paths.ADDON_TURN_SECRET_FILE, 'utf8');
if (!turnSecret) console.error('No turn secret set. Will leave emtpy, but this is a problem!');
debugApp(app, 'setupOauth');
const env = [
{ name: 'CLOUDRON_STUN_SERVER', value: settings.adminFqdn() },
{ name: 'CLOUDRON_STUN_PORT', value: '3478' },
{ name: 'CLOUDRON_STUN_TLS_PORT', value: '5349' },
{ name: 'CLOUDRON_TURN_SERVER', value: settings.adminFqdn() },
{ name: 'CLOUDRON_TURN_PORT', value: '3478' },
{ name: 'CLOUDRON_TURN_TLS_PORT', value: '5349' },
{ name: 'CLOUDRON_TURN_SECRET', value: turnSecret }
];
if (!app.sso) return callback(null);
debugApp(app, 'Setting up TURN');
const env = [];
appdb.setAddonConfig(app.id, 'turn', env, callback);
debugApp(app, 'Setting oauth addon config to %j', env);
appdb.setAddonConfig(app.id, 'oauth', env, callback);
}
function teardownTurn(app, options, callback) {
function teardownOauth(app, options, callback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof callback, 'function');
debugApp(app, 'Tearing down TURN');
debugApp(app, 'teardownOauth');
appdb.unsetAddonConfig(app.id, 'turn', callback);
appdb.unsetAddonConfig(app.id, 'oauth', callback);
}
function setupEmail(app, options, callback) {
@@ -1358,43 +1347,6 @@ function restorePostgreSql(app, options, callback) {
});
}
function startTurn(existingInfra, callback) {
assert.strictEqual(typeof existingInfra, 'object');
assert.strictEqual(typeof callback, 'function');
// get and ensure we have a turn secret
var turnSecret = safe.fs.readFileSync(paths.ADDON_TURN_SECRET_FILE, 'utf8');
if (!turnSecret) {
turnSecret = 'a' + crypto.randomBytes(15).toString('hex'); // prefix with a to ensure string starts with a letter
safe.fs.writeFileSync(paths.ADDON_TURN_SECRET_FILE, turnSecret, 'utf8');
}
const tag = infra.images.turn.tag;
const memoryLimit = 256;
const realm = settings.adminFqdn();
if (existingInfra.version === infra.version && existingInfra.images.turn && infra.images.turn.tag === existingInfra.images.turn.tag) return callback();
// this exports 3478/tcp, 5349/tls and 50000-51000/udp
const cmd = `docker run --restart=always -d --name="turn" \
--hostname turn \
--net host \
--log-driver syslog \
--log-opt syslog-address=udp://127.0.0.1:2514 \
--log-opt syslog-format=rfc5424 \
--log-opt tag=turn \
-m ${memoryLimit}m \
--memory-swap ${memoryLimit * 2}m \
--dns 172.18.0.1 \
--dns-search=. \
-e CLOUDRON_TURN_SECRET="${turnSecret}" \
-e CLOUDRON_REALM="${realm}" \
--label isCloudronManaged=true \
--read-only -v /tmp -v /run "${tag}"`;
shell.exec('startTurn', cmd, callback);
}
function startMongodb(existingInfra, callback) {
assert.strictEqual(typeof existingInfra, 'object');
assert.strictEqual(typeof callback, 'function');
@@ -1754,27 +1706,6 @@ function restoreRedis(app, options, callback) {
});
}
function statusTurn(callback) {
assert.strictEqual(typeof callback, 'function');
docker.inspect('turn', function (error, container) {
if (error && error.reason === BoxError.NOT_FOUND) return callback(null, { status: exports.SERVICE_STATUS_STOPPED });
if (error) return callback(error);
docker.memoryUsage(container.Id, function (error, result) {
if (error) return callback(error);
var tmp = {
status: container.State.Running ? exports.SERVICE_STATUS_ACTIVE : exports.SERVICE_STATUS_STOPPED,
memoryUsed: result.memory_stats.usage,
memoryPercent: parseInt(100 * result.memory_stats.usage / result.memory_stats.limit)
};
callback(null, tmp);
});
});
}
function statusDocker(callback) {
assert.strictEqual(typeof callback, 'function');
+897 -834
View File
File diff suppressed because it is too large Load Diff
+1 -3
View File
@@ -21,8 +21,7 @@ exports = module.exports = {
runSystemChecks: runSystemChecks,
};
var addons = require('./addons.js'),
apps = require('./apps.js'),
var apps = require('./apps.js'),
appstore = require('./appstore.js'),
assert = require('assert'),
async = require('async'),
@@ -327,7 +326,6 @@ function setDashboardAndMailDomain(domain, auditSource, callback) {
if (error) return callback(error);
mail.onMailFqdnChanged(NOOP_CALLBACK); // this will update dns and re-configure mail server
addons.restartService('turn', NOOP_CALLBACK); // to update the realm variable
callback(null);
});
+16 -15
View File
@@ -23,6 +23,11 @@ var apps = require('./apps.js'),
var gHttpServer = null;
function authorizeApp(req, res, next) {
// TODO add here some authorization
// - block apps not using the docker addon
// - block calls regarding platform containers
// - only allow managing and inspection of containers belonging to the app
// make the tests pass for now
if (constants.TEST) {
req.app = { id: 'testappid' };
@@ -59,8 +64,6 @@ function attachDockerRequest(req, res, next) {
dockerResponse.pipe(res, { end: true });
});
req.dockerRequest.on('error', () => {}); // abort() throws
next();
}
@@ -71,21 +74,22 @@ function containersCreate(req, res, next) {
safe.set(req.body, 'Labels', _.extend({ }, safe.query(req.body, 'Labels'), { appId: req.app.id, isCloudronManaged: String(false) })); // overwrite the app id to track containers of an app
safe.set(req.body, 'HostConfig.LogConfig', { Type: 'syslog', Config: { 'tag': req.app.id, 'syslog-address': 'udp://127.0.0.1:2514', 'syslog-format': 'rfc5424' }});
const appDataDir = path.join(paths.APPS_DATA_DIR, req.app.id, 'data');
const appDataDir = path.join(paths.APPS_DATA_DIR, req.app.id, 'data'),
dockerDataDir = path.join(paths.APPS_DATA_DIR, req.app.id, 'docker');
debug('Original bind mounts:', req.body.HostConfig.Binds);
debug('Original volume binds:', req.body.HostConfig.Binds);
let binds = [];
for (let bind of (req.body.HostConfig.Binds || [])) {
if (!bind.startsWith('/app/data/')) {
req.dockerRequest.abort();
return next(new HttpError(400, 'Binds must be under /app/data/'));
}
binds.push(bind.replace(new RegExp('^/app/data/'), appDataDir + '/'));
if (bind.startsWith(appDataDir)) binds.push(bind); // eclipse will inspect docker to find out the host folders and pass that to child containers
else if (bind.startsWith('/app/data')) binds.push(bind.replace(new RegExp('^/app/data'), appDataDir));
else binds.push(`${dockerDataDir}/${bind}`);
}
debug('Rewritten bind mounts:', binds);
// cleanup the paths from potential double slashes
binds = binds.map(function (bind) { return bind.replace(/\/+/g, '/'); });
debug('Rewritten volume binds:', binds);
safe.set(req.body, 'HostConfig.Binds', binds);
let plainBody = JSON.stringify(req.body);
@@ -113,9 +117,6 @@ function start(callback) {
assert(gHttpServer === null, 'Already started');
let json = middleware.json({ strict: true });
// we protect container create as the app/admin can otherwise mount random paths (like the ghost file)
// protected other paths is done by preventing install/exec access of apps using docker addon
let router = new express.Router();
router.post('/:version/containers/create', containersCreate);
@@ -136,7 +137,7 @@ function start(callback) {
.use(middleware.lastMile());
gHttpServer = http.createServer(proxyServer);
gHttpServer.listen(constants.DOCKER_PROXY_PORT, '172.18.0.1', callback);
gHttpServer.listen(constants.DOCKER_PROXY_PORT, '0.0.0.0', callback);
// Overwrite the default 2min request timeout. This is required for large builds for example
gHttpServer.setTimeout(60 * 60 * 1000);
+9 -20
View File
@@ -51,21 +51,16 @@ function getAll(callback) {
});
}
function add(name, data, callback) {
function add(name, domain, callback) {
assert.strictEqual(typeof name, 'string');
assert.strictEqual(typeof data, 'object');
assert.strictEqual(typeof data.zoneName, 'string');
assert.strictEqual(typeof data.provider, 'string');
assert.strictEqual(typeof data.config, 'object');
assert.strictEqual(typeof data.tlsConfig, 'object');
assert.strictEqual(typeof domain, 'object');
assert.strictEqual(typeof domain.zoneName, 'string');
assert.strictEqual(typeof domain.provider, 'string');
assert.strictEqual(typeof domain.config, 'object');
assert.strictEqual(typeof domain.tlsConfig, 'object');
assert.strictEqual(typeof callback, 'function');
let queries = [
{ query: 'INSERT INTO domains (domain, zoneName, provider, configJson, tlsConfigJson) VALUES (?, ?, ?, ?, ?)', args: [ name, data.zoneName, data.provider, JSON.stringify(data.config), JSON.stringify(data.tlsConfig) ] },
{ query: 'INSERT INTO mail (domain, dkimSelector) VALUES (?, ?)', args: [ name, data.dkimSelector || 'cloudron' ] },
];
database.transaction(queries, function (error) {
database.query('INSERT INTO domains (domain, zoneName, provider, configJson, tlsConfigJson) VALUES (?, ?, ?, ?, ?)', [ name, domain.zoneName, domain.provider, JSON.stringify(domain.config), JSON.stringify(domain.tlsConfig) ], function (error) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, error));
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
@@ -105,12 +100,7 @@ function del(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
let queries = [
{ query: 'DELETE FROM mail WHERE domain = ?', args: [ domain ] },
{ query: 'DELETE FROM domains WHERE domain = ?', args: [ domain ] },
];
database.transaction(queries, function (error, results) {
database.query('DELETE FROM domains WHERE domain=?', [ domain ], function (error, result) {
if (error && error.code === 'ER_ROW_IS_REFERENCED_2') {
if (error.message.indexOf('apps_mailDomain_constraint') !== -1) return callback(new BoxError(BoxError.CONFLICT, 'Domain is in use by an app or the mailbox of an app. Check the domains of apps and the Email section of each app.'));
if (error.message.indexOf('subdomains') !== -1) return callback(new BoxError(BoxError.CONFLICT, 'Domain is in use by one or more app(s).'));
@@ -118,9 +108,8 @@ function del(domain, callback) {
return callback(new BoxError(BoxError.CONFLICT, error.message));
}
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (results[1].affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Domain not found'));
if (result.affectedRows === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Domain not found'));
callback(null);
});
+2 -11
View File
@@ -40,7 +40,6 @@ var assert = require('assert'),
debug = require('debug')('box:domains'),
domaindb = require('./domaindb.js'),
eventlog = require('./eventlog.js'),
mail = require('./mail.js'),
reverseProxy = require('./reverseproxy.js'),
safe = require('safetydance'),
settings = require('./settings.js'),
@@ -49,8 +48,6 @@ var assert = require('assert'),
util = require('util'),
_ = require('underscore');
const NOOP_CALLBACK = function (error) { if (error) debug(error); };
// choose which subdomain backend we use for test purpose we use route53
function api(provider) {
assert.strictEqual(typeof provider, 'string');
@@ -174,7 +171,7 @@ function add(domain, data, auditSource, callback) {
assert.strictEqual(typeof data.tlsConfig, 'object');
assert.strictEqual(typeof callback, 'function');
let { zoneName, provider, config, fallbackCertificate, tlsConfig, dkimSelector } = data;
let { zoneName, provider, config, fallbackCertificate, tlsConfig } = data;
if (!tld.isValid(domain)) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' }));
if (domain.endsWith('.')) return callback(new BoxError(BoxError.BAD_FIELD, 'Invalid domain', { field: 'domain' }));
@@ -197,12 +194,10 @@ function add(domain, data, auditSource, callback) {
let error = validateTlsConfig(tlsConfig, provider);
if (error) return callback(error);
if (!dkimSelector) dkimSelector = 'cloudron-' + settings.adminDomain().replace(/\./g, '');
verifyDnsConfig(config, domain, zoneName, provider, function (error, sanitizedConfig) {
if (error) return callback(error);
domaindb.add(domain, { zoneName, provider, config: sanitizedConfig, tlsConfig, dkimSelector }, function (error) {
domaindb.add(domain, { zoneName: zoneName, provider: provider, config: sanitizedConfig, tlsConfig: tlsConfig }, function (error) {
if (error) return callback(error);
reverseProxy.setFallbackCertificate(domain, fallbackCertificate, function (error) {
@@ -210,8 +205,6 @@ function add(domain, data, auditSource, callback) {
eventlog.add(eventlog.ACTION_DOMAIN_ADD, auditSource, { domain, zoneName, provider });
mail.onDomainAdded(domain, NOOP_CALLBACK);
callback();
});
});
@@ -321,8 +314,6 @@ function del(domain, auditSource, callback) {
eventlog.add(eventlog.ACTION_DOMAIN_REMOVE, auditSource, { domain });
mail.onDomainRemoved(domain, NOOP_CALLBACK);
return callback(null);
});
}
+3 -4
View File
@@ -15,13 +15,12 @@ exports = module.exports = {
// a major version bump in the db containers will trigger the restore logic that uses the db dumps
// docker inspect --format='{{index .RepoDigests 0}}' $IMAGE to get the sha256
'images': {
'turn': { repo: 'cloudron/turn', tag: 'cloudron/turn:1.0.1@sha256:d7e8b3a88e30986a459a4da9742fbd50a1d150f07408942b91e279c41e6af059' },
'mysql': { repo: 'cloudron/mysql', tag: 'cloudron/mysql:2.2.0@sha256:440c8a9ca4d2958d51a375359f8158ef702b83395aa9ac4f450c51825ec09239' },
'mysql': { repo: 'cloudron/mysql', tag: 'cloudron/mysql:2.1.0@sha256:eee0dfd3829d563f2063084bc0d7c8802c4bdd6e233159c6226a17ff7a9a3503' },
'postgresql': { repo: 'cloudron/postgresql', tag: 'cloudron/postgresql:2.0.2@sha256:6dcee0731dfb9b013ed94d56205eee219040ee806c7e251db3b3886eaa4947ff' },
'mongodb': { repo: 'cloudron/mongodb', tag: 'cloudron/mongodb:2.1.0@sha256:6d1bf221cfe6124957e2c58b57c0a47214353496009296acb16adf56df1da9d5' },
'redis': { repo: 'cloudron/redis', tag: 'cloudron/redis:2.1.0@sha256:f2cda21bd15c21bbf44432df412525369ef831a2d53860b5c5b1675e6f384de2' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.7.2@sha256:f20d112ff9a97e052a9187063eabbd8d484ce369114d44186e344169a1b3ef6b' },
'mail': { repo: 'cloudron/mail', tag: 'cloudron/mail:2.6.5@sha256:d17cc0a3d2b6431cb683109abf40fffb91199e2af1d6d99f81d8ec3a1e1bb442' },
'graphite': { repo: 'cloudron/graphite', tag: 'cloudron/graphite:2.2.0@sha256:fc9ca69d16e6ebdbd98ed53143d4a0d2212eef60cb638dc71219234e6f427a2c' },
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:1.0.0@sha256:3b70aac36700225945a4a39b5a400c28e010e980879d0dcca76e4a37b04a16ed' }
'sftp': { repo: 'cloudron/sftp', tag: 'cloudron/sftp:0.1.0@sha256:e177c5bf5f38c84ce1dea35649c22a1b05f96eec67a54a812c5a35e585670f0f' }
}
};
+28 -12
View File
@@ -7,11 +7,10 @@ exports = module.exports = {
getDomains: getDomains,
getDomain: getDomain,
addDomain: addDomain,
removeDomain: removeDomain,
clearDomains: clearDomains,
onDomainAdded: onDomainAdded,
onDomainRemoved: onDomainRemoved,
removePrivateFields: removePrivateFields,
setDnsRecords: setDnsRecords,
@@ -313,8 +312,9 @@ function checkDmarc(domain, callback) {
if (txtRecords.length !== 0) {
dmarc.value = txtRecords[0].join('');
const actual = txtToDict(dmarc.value);
dmarc.status = actual.v === 'DMARC1'; // see box#666
// allow extra fields in dmarc like rua
const actual = txtToDict(dmarc.value), expected = txtToDict(dmarc.expected);
dmarc.status = Object.keys(expected).every(k => expected[k] === actual[k]);
}
callback(null, dmarc);
@@ -905,21 +905,37 @@ function onMailFqdnChanged(callback) {
});
}
function onDomainAdded(domain, callback) {
function addDomain(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
async.series([
upsertDnsRecords.bind(null, domain, settings.mailFqdn()), // do this first to ensure DKIM keys
restartMailIfActivated
], callback);
const dkimSelector = domain === settings.adminDomain() ? 'cloudron' : ('cloudron-' + settings.adminDomain().replace(/\./g, ''));
maildb.add(domain, { dkimSelector }, function (error) {
if (error) return callback(error);
async.series([
upsertDnsRecords.bind(null, domain, settings.mailFqdn()), // do this first to ensure DKIM keys
restartMailIfActivated
], NOOP_CALLBACK); // do these asynchronously
callback();
});
}
function onDomainRemoved(domain, callback) {
function removeDomain(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
restartMail(callback);
if (domain === settings.adminDomain()) return callback(new BoxError(BoxError.CONFLICT));
maildb.del(domain, function (error) {
if (error) return callback(error);
restartMail(NOOP_CALLBACK);
callback();
});
}
function clearDomains(callback) {
+30
View File
@@ -1,6 +1,8 @@
'use strict';
exports = module.exports = {
add: add,
del: del,
get: get,
list: list,
update: update,
@@ -32,6 +34,20 @@ function postProcess(data) {
return data;
}
function add(domain, data, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof data, 'object');
assert.strictEqual(typeof callback, 'function');
database.query('INSERT INTO mail (domain, dkimSelector) VALUES (?, ?)', [ domain, data.dkimSelector || 'cloudron' ], function (error) {
if (error && error.code === 'ER_DUP_ENTRY') return callback(new BoxError(BoxError.ALREADY_EXISTS, 'mail domain already exists'));
if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND), 'no such domain');
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
callback(null);
});
}
function clear(callback) {
assert.strictEqual(typeof callback, 'function');
@@ -42,6 +58,20 @@ function clear(callback) {
});
}
function del(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
// deletes aliases as well
database.query('DELETE FROM mail WHERE domain=?', [ domain ], function (error, result) {
if (error && error.code === 'ER_ROW_IS_REFERENCED_2') return callback(new BoxError(BoxError.CONFLICT));
if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
if (result.affectedRows === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Mail domain not found'));
callback(null);
});
}
function get(domain, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof callback, 'function');
+8 -7
View File
@@ -58,16 +58,17 @@ server {
ssl_certificate <%= certFilePath %>;
ssl_certificate_key <%= keyFilePath %>;
ssl_session_timeout 5m;
ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
ssl_session_tickets off;
ssl_session_cache shared:SSL:50m;
# https://bettercrypto.org/static/applied-crypto-hardening.pdf
# https://mozilla.github.io/server-side-tls/ssl-config-generator/
# https://cipherli.st/
# https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
# https://github.com/ssllabs/research/wiki/SSL-and-TLS-Deployment-Best-Practices#25-use-forward-secrecy
# ciphers according to https://ssl-config.mozilla.org/#server=nginx&version=1.14.0&config=intermediate&openssl=1.1.1&guideline=5.4
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # don't use SSLv3 ref: POODLE
# ciphers according to https://mozilla.github.io/server-side-tls/ssl-config-generator/?server=nginx-1.10.3&openssl=1.0.2g&hsts=yes&profile=modern
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_dhparam /home/yellowtent/boxdata/dhparams.pem;
add_header Strict-Transport-Security "max-age=15768000";
+1 -2
View File
@@ -22,7 +22,7 @@ exports = module.exports = {
PLATFORM_DATA_DIR: path.join(baseDir(), 'platformdata'),
APPS_DATA_DIR: path.join(baseDir(), 'appsdata'),
BOX_DATA_DIR: path.join(baseDir(), 'boxdata'), // box data dir is part of box backup
BOX_DATA_DIR: path.join(baseDir(), 'boxdata'),
ACME_CHALLENGES_DIR: path.join(baseDir(), 'platformdata/acme'),
ADDON_CONFIG_DIR: path.join(baseDir(), 'platformdata/addons'),
@@ -46,7 +46,6 @@ exports = module.exports = {
APP_CERTS_DIR: path.join(baseDir(), 'boxdata/certs'),
CLOUDRON_AVATAR_FILE: path.join(baseDir(), 'boxdata/avatar.png'),
UPDATE_CHECKER_FILE: path.join(baseDir(), 'boxdata/updatechecker.json'),
ADDON_TURN_SECRET_FILE: path.join(baseDir(), 'boxdata/addon-turn-secret'),
LOG_DIR: path.join(baseDir(), 'platformdata/logs'),
TASKS_LOG_DIR: path.join(baseDir(), 'platformdata/logs/tasks'),
+5 -4
View File
@@ -133,10 +133,11 @@ function pruneInfraImages(callback) {
function stopContainers(existingInfra, callback) {
// always stop addons to restart them on any infra change, regardless of minor or major update
if (existingInfra.version !== infra.version) {
// TODO: only nuke containers with isCloudronManaged=true
debug('stopping all containers for infra upgrade');
async.series([
shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker stop'),
shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker rm -f')
shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'network=cloudron\' | xargs --no-run-if-empty docker stop'),
shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'network=cloudron\' | xargs --no-run-if-empty docker rm -f')
], callback);
} else {
assert(typeof infra.images, 'object');
@@ -149,8 +150,8 @@ function stopContainers(existingInfra, callback) {
let filterArg = changedAddons.map(function (c) { return `--filter 'name=${c}'`; }).join(' '); // name=c matches *c*. required for redis-{appid}
// ignore error if container not found (and fail later) so that this code works across restarts
async.series([
shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'label=isCloudronManaged' | xargs --no-run-if-empty docker stop || true`),
shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'label=isCloudronManaged' | xargs --no-run-if-empty docker rm -f || true`)
shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'network=cloudron' | xargs --no-run-if-empty docker stop || true`),
shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'network=cloudron' | xargs --no-run-if-empty docker rm -f || true`)
], callback);
}
}
+2 -2
View File
@@ -121,8 +121,7 @@ function setup(dnsConfig, sysinfoConfig, auditSource, callback) {
provider: dnsConfig.provider,
config: dnsConfig.config,
fallbackCertificate: dnsConfig.fallbackCertificate || null,
tlsConfig: dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' },
dkimSelector: 'cloudron'
tlsConfig: dnsConfig.tlsConfig || { provider: 'letsencrypt-prod' }
};
domains.add(domain, data, auditSource, function (error) {
@@ -138,6 +137,7 @@ function setup(dnsConfig, sysinfoConfig, auditSource, callback) {
settings.setSysinfoConfig.bind(null, sysinfoConfig),
domains.prepareDashboardDomain.bind(null, domain, auditSource, (progress) => setProgress('setup', progress.message, NOOP_CALLBACK)),
cloudron.setDashboardDomain.bind(null, domain, auditSource),
mail.addDomain.bind(null, domain), // this relies on settings.mailFqdn() and settings.adminDomain()
setProgress.bind(null, 'setup', 'Done'),
eventlog.add.bind(null, eventlog.ACTION_PROVISION, auditSource, { })
], function (error) {
+7 -7
View File
@@ -146,19 +146,19 @@ function validateCertificate(location, domainObject, certificate) {
// -checkhost checks for SAN or CN exclusively. SAN takes precedence and if present, ignores the CN.
const fqdn = domains.fqdn(location, domainObject);
let result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert });
var result = safe.child_process.execSync(`openssl x509 -noout -checkhost "${fqdn}"`, { encoding: 'utf8', input: cert });
if (result === null) return new BoxError(BoxError.BAD_FIELD, 'Unable to get certificate subject:' + safe.error.message, { field: 'cert' });
if (result.indexOf('does match certificate') === -1) return new BoxError(BoxError.BAD_FIELD, `Certificate is not valid for this domain. Expecting ${fqdn}`, { field: 'cert' });
// check if public key in the cert and private key matches. pkey below works for RSA and ECDSA keys
const pubKeyFromCert = safe.child_process.execSync('openssl x509 -noout -pubkey', { encoding: 'utf8', input: cert });
if (pubKeyFromCert === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get public key from cert: ${safe.error.message}`, { field: 'cert' });
// http://httpd.apache.org/docs/2.0/ssl/ssl_faq.html#verify
var certModulus = safe.child_process.execSync('openssl x509 -noout -modulus', { encoding: 'utf8', input: cert });
if (certModulus === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get cert modulus: ${safe.error.message}`, { field: 'cert' });
const pubKeyFromKey = safe.child_process.execSync('openssl pkey -pubout', { encoding: 'utf8', input: key });
if (pubKeyFromKey === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get public key from private key: ${safe.error.message}`, { field: 'cert' });
var keyModulus = safe.child_process.execSync('openssl rsa -noout -modulus', { encoding: 'utf8', input: key });
if (keyModulus === null) return new BoxError(BoxError.BAD_FIELD, `Unable to get key modulus: ${safe.error.message}`, { field: 'cert' });
if (pubKeyFromCert !== pubKeyFromKey) return new BoxError(BoxError.BAD_FIELD, 'Public key does not match the certificate.', { field: 'cert' });
if (certModulus !== keyModulus) return new BoxError(BoxError.BAD_FIELD, 'Key does not match the certificate.', { field: 'cert' });
// check expiration
result = safe.child_process.execSync('openssl x509 -checkend 0', { encoding: 'utf8', input: cert });
+95 -126
View File
@@ -4,16 +4,16 @@ exports = module.exports = {
getApp: getApp,
getApps: getApps,
getAppIcon: getAppIcon,
install: install,
uninstall: uninstall,
restore: restore,
installApp: installApp,
uninstallApp: uninstallApp,
restoreApp: restoreApp,
importApp: importApp,
backup: backup,
update: update,
backupApp: backupApp,
updateApp: updateApp,
getLogs: getLogs,
getLogStream: getLogStream,
listBackups: listBackups,
repair: repair,
repairApp: repairApp,
setAccessRestriction: setAccessRestriction,
setLabel: setLabel,
@@ -31,18 +31,16 @@ exports = module.exports = {
setLocation: setLocation,
setDataDir: setDataDir,
stop: stop,
start: start,
restart: restart,
stopApp: stopApp,
startApp: startApp,
restartApp: restartApp,
exec: exec,
execWebSocket: execWebSocket,
clone: clone,
cloneApp: cloneApp,
uploadFile: uploadFile,
downloadFile: downloadFile,
load: load
downloadFile: downloadFile
};
var apps = require('../apps.js'),
@@ -53,28 +51,19 @@ var apps = require('../apps.js'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
safe = require('safetydance'),
users = require('../users.js'),
util = require('util'),
WebSocket = require('ws');
function load(req, res, next) {
function getApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.get(req.params.id, function (error, result) {
apps.get(req.params.id, function (error, app) {
if (error) return next(BoxError.toHttpError(error));
req.resource = result;
next();
next(new HttpSuccess(200, apps.removeInternalFields(app)));
});
}
function getApp(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
next(new HttpSuccess(200, apps.removeInternalFields(req.resource)));
}
function getApps(req, res, next) {
assert.strictEqual(typeof req.user, 'object');
@@ -88,19 +77,19 @@ function getApps(req, res, next) {
}
function getAppIcon(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
apps.getIconPath(req.resource, { original: req.query.original }, function (error, iconPath) {
apps.getIconPath(req.params.id, { original: req.query.original }, function (error, iconPath) {
if (error) return next(BoxError.toHttpError(error));
res.sendFile(iconPath);
});
}
function install(req, res, next) {
function installApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
const data = req.body;
var data = req.body;
// atleast one
if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
@@ -144,28 +133,20 @@ function install(req, res, next) {
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
apps.downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
apps.install(data, req.user, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
if (safe.query(manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to install app with docker addon'));
data.appStoreId = appStoreId;
data.manifest = manifest;
apps.install(data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { id: result.id, taskId: result.taskId }));
});
next(new HttpSuccess(202, { id: result.id, taskId: result.taskId }));
});
}
function setAccessRestriction(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
apps.setAccessRestriction(req.resource, req.body.accessRestriction, auditSource.fromRequest(req), function (error) {
apps.setAccessRestriction(req.params.id, req.body.accessRestriction, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -174,11 +155,11 @@ function setAccessRestriction(req, res, next) {
function setLabel(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
apps.setLabel(req.resource, req.body.label, auditSource.fromRequest(req), function (error) {
apps.setLabel(req.params.id, req.body.label, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -187,12 +168,12 @@ function setLabel(req, res, next) {
function setTags(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (!Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array'));
if (req.body.tags.some((t) => typeof t !== 'string')) return next(new HttpError(400, 'tags array must contain strings'));
apps.setTags(req.resource, req.body.tags, auditSource.fromRequest(req), function (error) {
apps.setTags(req.params.id, req.body.tags, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -201,11 +182,11 @@ function setTags(req, res, next) {
function setIcon(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.icon !== null && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon is null or a base-64 image string'));
apps.setIcon(req.resource, req.body.icon, auditSource.fromRequest(req), function (error) {
apps.setIcon(req.params.id, req.body.icon, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -214,11 +195,11 @@ function setIcon(req, res, next) {
function setMemoryLimit(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.memoryLimit !== 'number') return next(new HttpError(400, 'memoryLimit is not a number'));
apps.setMemoryLimit(req.resource, req.body.memoryLimit, auditSource.fromRequest(req), function (error, result) {
apps.setMemoryLimit(req.params.id, req.body.memoryLimit, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -227,11 +208,11 @@ function setMemoryLimit(req, res, next) {
function setCpuShares(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.cpuShares !== 'number') return next(new HttpError(400, 'cpuShares is not a number'));
apps.setCpuShares(req.resource, req.body.cpuShares, auditSource.fromRequest(req), function (error, result) {
apps.setCpuShares(req.params.id, req.body.cpuShares, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -240,11 +221,11 @@ function setCpuShares(req, res, next) {
function setAutomaticBackup(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
apps.setAutomaticBackup(req.resource, req.body.enable, auditSource.fromRequest(req), function (error) {
apps.setAutomaticBackup(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -253,11 +234,11 @@ function setAutomaticBackup(req, res, next) {
function setAutomaticUpdate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.enable !== 'boolean') return next(new HttpError(400, 'enable must be a boolean'));
apps.setAutomaticUpdate(req.resource, req.body.enable, auditSource.fromRequest(req), function (error) {
apps.setAutomaticUpdate(req.params.id, req.body.enable, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -266,13 +247,13 @@ function setAutomaticUpdate(req, res, next) {
function setReverseProxyConfig(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.robotsTxt !== null && typeof req.body.robotsTxt !== 'string') return next(new HttpError(400, 'robotsTxt is not a string'));
if (req.body.csp !== null && typeof req.body.csp !== 'string') return next(new HttpError(400, 'csp is not a string'));
apps.setReverseProxyConfig(req.resource, req.body, auditSource.fromRequest(req), function (error) {
apps.setReverseProxyConfig(req.params.id, req.body, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -281,14 +262,14 @@ function setReverseProxyConfig(req, res, next) {
function setCertificate(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.key !== null && typeof req.body.cert !== 'string') return next(new HttpError(400, 'cert must be a string'));
if (req.body.cert !== null && typeof req.body.key !== 'string') return next(new HttpError(400, 'key must be a string'));
if (req.body.cert && !req.body.key) return next(new HttpError(400, 'key must be provided'));
if (!req.body.cert && req.body.key) return next(new HttpError(400, 'cert must be provided'));
apps.setCertificate(req.resource, req.body, auditSource.fromRequest(req), function (error) {
apps.setCertificate(req.params.id, req.body, auditSource.fromRequest(req), function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, {}));
@@ -297,12 +278,12 @@ function setCertificate(req, res, next) {
function setEnvironment(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (!req.body.env || typeof req.body.env !== 'object') return next(new HttpError(400, 'env must be an object'));
if (Object.keys(req.body.env).some((key) => typeof req.body.env[key] !== 'string')) return next(new HttpError(400, 'env must contain values as strings'));
apps.setEnvironment(req.resource, req.body.env, auditSource.fromRequest(req), function (error, result) {
apps.setEnvironment(req.params.id, req.body.env, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -311,11 +292,11 @@ function setEnvironment(req, res, next) {
function setDebugMode(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.debugMode !== null && typeof req.body.debugMode !== 'object') return next(new HttpError(400, 'debugMode must be an object'));
apps.setDebugMode(req.resource, req.body.debugMode, auditSource.fromRequest(req), function (error, result) {
apps.setDebugMode(req.params.id, req.body.debugMode, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -324,12 +305,12 @@ function setDebugMode(req, res, next) {
function setMailbox(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.mailboxName !== null && typeof req.body.mailboxName !== 'string') return next(new HttpError(400, 'mailboxName must be a string'));
if (typeof req.body.mailboxDomain !== 'string') return next(new HttpError(400, 'mailboxDomain must be a string'));
apps.setMailbox(req.resource, req.body.mailboxName, req.body.mailboxDomain, auditSource.fromRequest(req), function (error, result) {
apps.setMailbox(req.params.id, req.body.mailboxName, req.body.mailboxDomain, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -338,7 +319,7 @@ function setMailbox(req, res, next) {
function setLocation(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.body.location !== 'string') return next(new HttpError(400, 'location must be string')); // location may be an empty string
if (!req.body.domain) return next(new HttpError(400, 'domain is required'));
@@ -353,7 +334,7 @@ function setLocation(req, res, next) {
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
apps.setLocation(req.resource, req.body, auditSource.fromRequest(req), function (error, result) {
apps.setLocation(req.params.id, req.body, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -362,49 +343,47 @@ function setLocation(req, res, next) {
function setDataDir(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (req.body.dataDir !== null && typeof req.body.dataDir !== 'string') return next(new HttpError(400, 'dataDir must be a string'));
apps.setDataDir(req.resource, req.body.dataDir, auditSource.fromRequest(req), function (error, result) {
apps.setDataDir(req.params.id, req.body.dataDir, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function repair(req, res, next) {
function repairApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
const data = req.body;
if ('manifest' in data) {
if (!data.manifest || typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest must be an object'));
if (safe.query(data.manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to repair app with docker addon'));
}
if ('dockerImage' in data) {
if (!data.dockerImage || typeof data.dockerImage !== 'string') return next(new HttpError(400, 'dockerImage must be a string'));
}
apps.repair(req.resource, data, auditSource.fromRequest(req), function (error, result) {
apps.repair(req.params.id, data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function restore(req, res, next) {
function restoreApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var data = req.body;
if (!data.backupId || typeof data.backupId !== 'string') return next(new HttpError(400, 'backupId must be non-empty string'));
apps.restore(req.resource, data.backupId, auditSource.fromRequest(req), function (error, result) {
apps.restore(req.params.id, data.backupId, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
@@ -413,7 +392,7 @@ function restore(req, res, next) {
function importApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var data = req.body;
@@ -435,16 +414,16 @@ function importApp(req, res, next) {
}
}
apps.importApp(req.resource, data, auditSource.fromRequest(req), function (error, result) {
apps.importApp(req.params.id, data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function clone(req, res, next) {
function cloneApp(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var data = req.body;
@@ -455,66 +434,66 @@ function clone(req, res, next) {
if ('overwriteDns' in req.body && typeof req.body.overwriteDns !== 'boolean') return next(new HttpError(400, 'overwriteDns must be boolean'));
apps.clone(req.resource, data, req.user, auditSource.fromRequest(req), function (error, result) {
apps.clone(req.params.id, data, req.user, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, { id: result.id, taskId: result.taskId }));
});
}
function backup(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
function backupApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.backup(req.resource, function (error, result) {
apps.backup(req.params.id, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function uninstall(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
function uninstallApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.uninstall(req.resource, auditSource.fromRequest(req), function (error, result) {
apps.uninstall(req.params.id, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function start(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
function startApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.start(req.resource, auditSource.fromRequest(req), function (error, result) {
apps.start(req.params.id, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function stop(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
function stopApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.stop(req.resource, auditSource.fromRequest(req), function (error, result) {
apps.stop(req.params.id, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function restart(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
function restartApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
apps.restart(req.resource, auditSource.fromRequest(req), function (error, result) {
apps.restart(req.params.id, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
function update(req, res, next) {
function updateApp(req, res, next) {
assert.strictEqual(typeof req.params.id, 'string');
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.resource, 'object');
var data = req.body;
@@ -526,24 +505,16 @@ function update(req, res, next) {
if ('skipBackup' in data && typeof data.skipBackup !== 'boolean') return next(new HttpError(400, 'skipBackup must be a boolean'));
if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean'));
apps.downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
apps.update(req.params.id, req.body, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
if (safe.query(manifest, 'addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is required to update app with docker addon'));
data.appStoreId = appStoreId;
data.manifest = manifest;
apps.update(req.resource, data, auditSource.fromRequest(req), function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, { taskId: result.taskId }));
});
next(new HttpSuccess(202, { taskId: result.taskId }));
});
}
// this route is for streaming logs
function getLogStream(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10; // we ignore last-event-id
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a valid number'));
@@ -558,7 +529,7 @@ function getLogStream(req, res, next) {
format: 'json'
};
apps.getLogs(req.resource, options, function (error, logStream) {
apps.getLogs(req.params.id, options, function (error, logStream) {
if (error) return next(BoxError.toHttpError(error));
res.writeHead(200, {
@@ -580,7 +551,7 @@ function getLogStream(req, res, next) {
}
function getLogs(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var lines = 'lines' in req.query ? parseInt(req.query.lines, 10) : 10;
if (isNaN(lines)) return next(new HttpError(400, 'lines must be a number'));
@@ -591,7 +562,7 @@ function getLogs(req, res, next) {
format: req.query.format || 'json'
};
apps.getLogs(req.resource, options, function (error, logStream) {
apps.getLogs(req.params.id, options, function (error, logStream) {
if (error) return next(BoxError.toHttpError(error));
res.writeHead(200, {
@@ -627,7 +598,7 @@ function demuxStream(stream, stdin) {
}
function exec(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var cmd = null;
if (req.query.cmd) {
@@ -641,11 +612,9 @@ function exec(req, res, next) {
var rows = req.query.rows ? parseInt(req.query.rows, 10) : null;
if (isNaN(rows)) return next(new HttpError(400, 'rows must be a number'));
var tty = req.query.tty === 'true';
var tty = req.query.tty === 'true' ? true : false;
if (safe.query(req.resource, 'manifest.addons.docker') && req.user.role !== users.ROLE_OWNER) return next(new HttpError(403, '"owner" role is requied to exec app with docker addon'));
apps.exec(req.resource, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
if (error) return next(BoxError.toHttpError(error));
if (req.headers['upgrade'] !== 'tcp') return next(new HttpError(404, 'exec requires TCP upgrade'));
@@ -667,7 +636,7 @@ function exec(req, res, next) {
}
function execWebSocket(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var cmd = null;
if (req.query.cmd) {
@@ -683,7 +652,7 @@ function execWebSocket(req, res, next) {
var tty = req.query.tty === 'true' ? true : false;
apps.exec(req.resource, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
apps.exec(req.params.id, { cmd: cmd, rows: rows, columns: columns, tty: tty }, function (error, duplexStream) {
if (error) return next(BoxError.toHttpError(error));
req.clearTimeout();
@@ -713,7 +682,7 @@ function execWebSocket(req, res, next) {
}
function listBackups(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
var page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
@@ -721,7 +690,7 @@ function listBackups(req, res, next) {
var perPage = typeof req.query.per_page !== 'undefined'? parseInt(req.query.per_page) : 25;
if (!perPage || perPage < 0) return next(new HttpError(400, 'per_page query param has to be a postive number'));
apps.listBackups(req.resource, page, perPage, function (error, result) {
apps.listBackups(page, perPage, req.params.id, function (error, result) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(200, { backups: result }));
@@ -729,12 +698,12 @@ function listBackups(req, res, next) {
}
function uploadFile(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
if (!req.files.file) return next(new HttpError(400, 'file must be provided as multipart'));
apps.uploadFile(req.resource, req.files.file.path, req.query.file, function (error) {
apps.uploadFile(req.params.id, req.files.file.path, req.query.file, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(202, {}));
@@ -742,11 +711,11 @@ function uploadFile(req, res, next) {
}
function downloadFile(req, res, next) {
assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.params.id, 'string');
if (typeof req.query.file !== 'string' || !req.query.file) return next(new HttpError(400, 'file query argument must be provided'));
apps.downloadFile(req.resource, req.query.file, function (error, stream, info) {
apps.downloadFile(req.params.id, req.query.file, function (error, stream, info) {
if (error) return next(BoxError.toHttpError(error));
var headers = {
-3
View File
@@ -102,7 +102,6 @@ function passwordReset(req, res, next) {
users.getByResetToken(req.body.resetToken, function (error, userObject) {
if (error) return next(new HttpError(401, 'Invalid resetToken'));
if (Date.now() - userObject.resetTokenCreationTime > 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
if (!userObject.username) return next(new HttpError(409, 'No username set'));
// setPassword clears the resetToken
@@ -131,8 +130,6 @@ function setupAccount(req, res, next) {
users.getByResetToken(req.body.resetToken, function (error, userObject) {
if (error) return next(new HttpError(401, 'Invalid Reset Token'));
if (Date.now() - userObject.resetTokenCreationTime > 24 * 60 * 60 * 1000) return next(new HttpError(401, 'Token expired'));
users.update(userObject, { username: req.body.username, displayName: req.body.displayName }, auditSource.fromRequest(req), function (error) {
if (error && error.reason === BoxError.ALREADY_EXISTS) return next(new HttpError(409, 'Username already used'));
if (error && error.reason === BoxError.BAD_FIELD) return next(new HttpError(400, error.message));
+24
View File
@@ -2,6 +2,8 @@
exports = module.exports = {
getDomain: getDomain,
addDomain: addDomain,
removeDomain: removeDomain,
setDnsRecords: setDnsRecords,
@@ -48,6 +50,18 @@ function getDomain(req, res, next) {
});
}
function addDomain(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string'));
mail.addDomain(req.body.domain, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(201, { domain: req.body.domain }));
});
}
function setDnsRecords(req, res, next) {
assert.strictEqual(typeof req.body, 'object');
assert.strictEqual(typeof req.params.domain, 'string');
@@ -63,6 +77,16 @@ function setDnsRecords(req, res, next) {
});
}
function removeDomain(req, res, next) {
assert.strictEqual(typeof req.params.domain, 'string');
mail.removeDomain(req.params.domain, function (error) {
if (error) return next(BoxError.toHttpError(error));
next(new HttpSuccess(204));
});
}
function getStatus(req, res, next) {
assert.strictEqual(typeof req.params.domain, 'string');
+197 -7
View File
@@ -124,6 +124,46 @@ describe('Mail API', function () {
after(cleanup);
describe('crud', function () {
it('cannot add non-existing domain', function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: 'doesnotexist.com' })
.end(function (err, res) {
expect(res.statusCode).to.equal(404);
done();
});
});
it('domain must be a string', function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: ['doesnotexist.com'] })
.end(function (err, res) {
expect(res.statusCode).to.equal(400);
done();
});
});
it('can add domain', function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
it('cannot add domain twice', function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(409);
done();
});
});
it('cannot get non-existing domain', function (done) {
superagent.get(SERVER_URL + '/api/v1/mail/doesnotexist.com')
.query({ access_token: token })
@@ -148,6 +188,33 @@ describe('Mail API', function () {
done();
});
});
it('cannot delete non-existing domain', function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/doesnotexist.com')
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(404);
done();
});
});
it('cannot delete admin mail domain', function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + ADMIN_DOMAIN.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(409);
done();
});
});
it('can delete admin mail domain', function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
});
describe('status', function () {
@@ -176,13 +243,20 @@ describe('Mail API', function () {
mxDomain = DOMAIN_0.domain;
dmarcDomain = '_dmarc.' + DOMAIN_0.domain;
superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/enable')
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ enabled: true })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(202);
expect(res.statusCode).to.equal(201);
done();
superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/enable')
.query({ access_token: token })
.send({ enabled: true })
.end(function (err, res) {
expect(res.statusCode).to.equal(202);
done();
});
});
});
@@ -191,7 +265,12 @@ describe('Mail API', function () {
dns.resolve = resolve;
done();
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
it('does not fail when dns errors', function (done) {
@@ -424,6 +503,25 @@ describe('Mail API', function () {
});
describe('mail from validation', function () {
before(function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
after(function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
it('get mail from validation succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
@@ -456,6 +554,25 @@ describe('Mail API', function () {
});
describe('catch_all', function () {
before(function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
after(function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
it('get catch_all succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
@@ -507,6 +624,25 @@ describe('Mail API', function () {
});
describe('mail relay', function () {
before(function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
after(function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
it('get mail relay succeeds', function (done) {
superagent.get(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
@@ -565,6 +701,25 @@ describe('Mail API', function () {
});
describe('mailboxes', function () {
before(function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
after(function (done) {
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
it('add succeeds', function (done) {
superagent.post(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain + '/mailboxes')
.send({ name: MAILBOX_NAME, userId: userId })
@@ -648,10 +803,26 @@ describe('Mail API', function () {
});
describe('aliases', function () {
before(function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
});
after(function (done) {
mail.removeMailboxes(DOMAIN_0.domain, function (error) {
if (error) return done(error);
done();
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
});
@@ -753,11 +924,30 @@ describe('Mail API', function () {
});
describe('mailinglists', function () {
before(function (done) {
async.series([
function (done) {
superagent.post(SERVER_URL + '/api/v1/mail')
.query({ access_token: token })
.send({ domain: DOMAIN_0.domain })
.end(function (err, res) {
expect(res.statusCode).to.equal(201);
done();
});
}
], done);
});
after(function (done) {
mail.removeMailboxes(DOMAIN_0.domain, function (error) {
if (error) return done(error);
done();
superagent.del(SERVER_URL + '/api/v1/mail/' + DOMAIN_0.domain)
.query({ access_token: token })
.end(function (err, res) {
expect(res.statusCode).to.equal(204);
done();
});
});
});
+1
View File
@@ -47,6 +47,7 @@ function setup(done) {
server.start,
database._clear,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
mail.addDomain.bind(null, DOMAIN_0.domain)
], function (error) {
expect(error).to.not.be.ok();
+37 -35
View File
@@ -191,45 +191,45 @@ function initializeExpressSync() {
// app routes
router.get ('/api/v1/apps', token, routes.apps.getApps);
router.get ('/api/v1/apps/:id', token, authorizeAdmin, routes.apps.load, routes.apps.getApp);
router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, routes.apps.getAppIcon);
router.get ('/api/v1/apps/:id', token, authorizeAdmin, routes.apps.getApp);
router.get ('/api/v1/apps/:id/icon', token, routes.apps.getAppIcon);
router.post('/api/v1/apps/install', token, authorizeAdmin, routes.apps.install);
router.post('/api/v1/apps/:id/uninstall', token, authorizeAdmin, routes.apps.load, routes.apps.uninstall);
router.post('/api/v1/apps/install', token, authorizeAdmin, routes.apps.installApp);
router.post('/api/v1/apps/:id/uninstall', token, authorizeAdmin, routes.apps.uninstallApp);
router.post('/api/v1/apps/:id/configure/access_restriction', token, authorizeAdmin, routes.apps.load, routes.apps.setAccessRestriction);
router.post('/api/v1/apps/:id/configure/label', token, authorizeAdmin, routes.apps.load, routes.apps.setLabel);
router.post('/api/v1/apps/:id/configure/tags', token, authorizeAdmin, routes.apps.load, routes.apps.setTags);
router.post('/api/v1/apps/:id/configure/icon', token, authorizeAdmin, routes.apps.load, routes.apps.setIcon);
router.post('/api/v1/apps/:id/configure/memory_limit', token, authorizeAdmin, routes.apps.load, routes.apps.setMemoryLimit);
router.post('/api/v1/apps/:id/configure/cpu_shares', token, authorizeAdmin, routes.apps.load, routes.apps.setCpuShares);
router.post('/api/v1/apps/:id/configure/automatic_backup', token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticBackup);
router.post('/api/v1/apps/:id/configure/automatic_update', token, authorizeAdmin, routes.apps.load, routes.apps.setAutomaticUpdate);
router.post('/api/v1/apps/:id/configure/reverse_proxy', token, authorizeAdmin, routes.apps.load, routes.apps.setReverseProxyConfig);
router.post('/api/v1/apps/:id/configure/cert', token, authorizeAdmin, routes.apps.load, routes.apps.setCertificate);
router.post('/api/v1/apps/:id/configure/debug_mode', token, authorizeAdmin, routes.apps.load, routes.apps.setDebugMode);
router.post('/api/v1/apps/:id/configure/mailbox', token, authorizeAdmin, routes.apps.load, routes.apps.setMailbox);
router.post('/api/v1/apps/:id/configure/env', token, authorizeAdmin, routes.apps.load, routes.apps.setEnvironment);
router.post('/api/v1/apps/:id/configure/data_dir', token, authorizeAdmin, routes.apps.load, routes.apps.setDataDir);
router.post('/api/v1/apps/:id/configure/location', token, authorizeAdmin, routes.apps.load, routes.apps.setLocation);
router.post('/api/v1/apps/:id/configure/access_restriction', token, authorizeAdmin, routes.apps.setAccessRestriction);
router.post('/api/v1/apps/:id/configure/label', token, authorizeAdmin, routes.apps.setLabel);
router.post('/api/v1/apps/:id/configure/tags', token, authorizeAdmin, routes.apps.setTags);
router.post('/api/v1/apps/:id/configure/icon', token, authorizeAdmin, routes.apps.setIcon);
router.post('/api/v1/apps/:id/configure/memory_limit', token, authorizeAdmin, routes.apps.setMemoryLimit);
router.post('/api/v1/apps/:id/configure/cpu_shares', token, authorizeAdmin, routes.apps.setCpuShares);
router.post('/api/v1/apps/:id/configure/automatic_backup', token, authorizeAdmin, routes.apps.setAutomaticBackup);
router.post('/api/v1/apps/:id/configure/automatic_update', token, authorizeAdmin, routes.apps.setAutomaticUpdate);
router.post('/api/v1/apps/:id/configure/reverse_proxy', token, authorizeAdmin, routes.apps.setReverseProxyConfig);
router.post('/api/v1/apps/:id/configure/cert', token, authorizeAdmin, routes.apps.setCertificate);
router.post('/api/v1/apps/:id/configure/debug_mode', token, authorizeAdmin, routes.apps.setDebugMode);
router.post('/api/v1/apps/:id/configure/mailbox', token, authorizeAdmin, routes.apps.setMailbox);
router.post('/api/v1/apps/:id/configure/env', token, authorizeAdmin, routes.apps.setEnvironment);
router.post('/api/v1/apps/:id/configure/data_dir', token, authorizeAdmin, routes.apps.setDataDir);
router.post('/api/v1/apps/:id/configure/location', token, authorizeAdmin, routes.apps.setLocation);
router.post('/api/v1/apps/:id/repair', token, authorizeAdmin, routes.apps.load, routes.apps.repair);
router.post('/api/v1/apps/:id/update', token, authorizeAdmin, routes.apps.load, routes.apps.update);
router.post('/api/v1/apps/:id/restore', token, authorizeAdmin, routes.apps.load, routes.apps.restore);
router.post('/api/v1/apps/:id/import', token, authorizeAdmin, routes.apps.load, routes.apps.importApp);
router.post('/api/v1/apps/:id/backup', token, authorizeAdmin, routes.apps.load, routes.apps.backup);
router.get ('/api/v1/apps/:id/backups', token, authorizeAdmin, routes.apps.load, routes.apps.listBackups);
router.post('/api/v1/apps/:id/start', token, authorizeAdmin, routes.apps.load, routes.apps.start);
router.post('/api/v1/apps/:id/stop', token, authorizeAdmin, routes.apps.load, routes.apps.stop);
router.post('/api/v1/apps/:id/restart', token, authorizeAdmin, routes.apps.load, routes.apps.restart);
router.get ('/api/v1/apps/:id/logstream', token, authorizeAdmin, routes.apps.load, routes.apps.getLogStream);
router.get ('/api/v1/apps/:id/logs', token, authorizeAdmin, routes.apps.load, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', token, authorizeAdmin, routes.apps.load, routes.apps.exec);
router.post('/api/v1/apps/:id/repair', token, authorizeAdmin, routes.apps.repairApp);
router.post('/api/v1/apps/:id/update', token, authorizeAdmin, routes.apps.updateApp);
router.post('/api/v1/apps/:id/restore', token, authorizeAdmin, routes.apps.restoreApp);
router.post('/api/v1/apps/:id/import', token, authorizeAdmin, routes.apps.importApp);
router.post('/api/v1/apps/:id/backup', token, authorizeAdmin, routes.apps.backupApp);
router.get ('/api/v1/apps/:id/backups', token, authorizeAdmin, routes.apps.listBackups);
router.post('/api/v1/apps/:id/stop', token, authorizeAdmin, routes.apps.stopApp);
router.post('/api/v1/apps/:id/start', token, authorizeAdmin, routes.apps.startApp);
router.post('/api/v1/apps/:id/restart', token, authorizeAdmin, routes.apps.restartApp);
router.get ('/api/v1/apps/:id/logstream', token, authorizeAdmin, routes.apps.getLogStream);
router.get ('/api/v1/apps/:id/logs', token, authorizeAdmin, routes.apps.getLogs);
router.get ('/api/v1/apps/:id/exec', token, authorizeAdmin, routes.apps.exec);
// websocket cannot do bearer authentication
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, users.ROLE_ADMIN), routes.apps.load, routes.apps.execWebSocket);
router.post('/api/v1/apps/:id/clone', token, authorizeAdmin, routes.apps.load, routes.apps.clone);
router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.load, routes.apps.downloadFile);
router.post('/api/v1/apps/:id/upload', token, authorizeAdmin, multipart, routes.apps.load, routes.apps.uploadFile);
router.get ('/api/v1/apps/:id/execws', routes.accesscontrol.websocketAuth.bind(null, users.ROLE_ADMIN), routes.apps.execWebSocket);
router.post('/api/v1/apps/:id/clone', token, authorizeAdmin, routes.apps.cloneApp);
router.get ('/api/v1/apps/:id/download', token, authorizeAdmin, routes.apps.downloadFile);
router.post('/api/v1/apps/:id/upload', token, authorizeAdmin, multipart, routes.apps.uploadFile);
router.get ('/api/v1/branding/:setting', token, authorizeOwner, routes.branding.get);
router.post('/api/v1/branding/:setting', token, authorizeOwner, (req, res, next) => {
@@ -253,6 +253,8 @@ function initializeExpressSync() {
}, routes.mailserver.proxy);
router.get ('/api/v1/mail/:domain', token, authorizeAdmin, routes.mail.getDomain);
router.post('/api/v1/mail', token, authorizeAdmin, routes.mail.addDomain);
router.del ('/api/v1/mail/:domain', token, authorizeAdmin, routes.mail.removeDomain);
router.get ('/api/v1/mail/:domain/status', token, authorizeAdmin, routes.mail.getStatus);
router.post('/api/v1/mail/:domain/mail_from_validation', token, authorizeAdmin, routes.mail.setMailFromValidation);
router.post('/api/v1/mail/:domain/catch_all', token, authorizeAdmin, routes.mail.setCatchAllAddress);
+1 -1
View File
@@ -40,7 +40,7 @@ function getDisks(callback) {
const ext4Disks = values[0].filter((r) => r.type === 'ext4').sort((a, b) => a.mountpoint.localeCompare(b.mountpoint));
const disks = {
disks: ext4Disks, // root disk is first. { filesystem, type, size, used, avialable, capacity, mountpoint }
disks: ext4Disks, // root disk is first
boxDataDisk: values[1].filesystem,
mailDataDisk: values[1].filesystem,
platformDataDisk: values[2].filesystem,
-9
View File
@@ -15,7 +15,6 @@ var appdb = require('../appdb.js'),
groupdb = require('../groupdb.js'),
groups = require('../groups.js'),
hat = require('../hat.js'),
settings = require('../settings.js'),
userdb = require('../userdb.js');
let AUDIT_SOURCE = { ip: '1.2.3.4' };
@@ -174,7 +173,6 @@ describe('Apps', function () {
async.series([
database.initialize,
database._clear,
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
domains.add.bind(null, DOMAIN_1.domain, DOMAIN_1, AUDIT_SOURCE),
userdb.add.bind(null, ADMIN_0.id, ADMIN_0),
@@ -210,13 +208,6 @@ describe('Apps', function () {
expect(apps._validatePortBindings({ port: 1567 }, { tcpPorts: { port3: null } })).to.be.an(Error);
});
it('does not allow reserved ports', function () {
expect(apps._validatePortBindings({ port: 443 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 50000 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 51000 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
expect(apps._validatePortBindings({ port: 50100 }, { tcpPorts: { port: 5000 } })).to.be.an(Error);
});
it('allows valid bindings', function () {
expect(apps._validatePortBindings({ port: 1024 }, { tcpPorts: { port: 5000 } })).to.be(null);
+21 -2
View File
@@ -18,7 +18,6 @@ var addons = require('../addons.js'),
net = require('net'),
nock = require('nock'),
paths = require('../paths.js'),
settings = require('../settings.js'),
userdb = require('../userdb.js'),
_ = require('underscore');
@@ -120,7 +119,6 @@ describe('apptask', function () {
async.series([
database.initialize,
database._clear,
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
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.portBindings, APP)
@@ -186,6 +184,27 @@ describe('apptask', function () {
});
});
it('allocate OAuth credentials', function (done) {
addons._setupOauth(APP, {}, function (error) {
expect(error).to.be(null);
done();
});
});
it('remove OAuth credentials', function (done) {
addons._teardownOauth(APP, {}, function (error) {
expect(error).to.be(null);
done();
});
});
it('remove OAuth credentials twice succeeds', function (done) {
addons._teardownOauth(APP, {}, function (error) {
expect(!error).to.be.ok();
done();
});
});
it('barfs on empty manifest', function (done) {
var badApp = _.extend({ }, APP);
badApp.manifest = { };
+26 -11
View File
@@ -40,8 +40,7 @@ var USER_0 = {
twoFactorAuthenticationSecret: '',
role: 'user',
active: true,
source: '',
resetTokenCreationTime: Date.now()
source: ''
};
var USER_1 = {
@@ -59,8 +58,7 @@ var USER_1 = {
twoFactorAuthenticationSecret: '',
role: 'user',
active: true,
source: '',
resetTokenCreationTime: Date.now()
source: ''
};
var USER_2 = {
@@ -78,8 +76,7 @@ var USER_2 = {
twoFactorAuthenticationSecret: '',
role: 'user',
active: true,
source: '',
resetTokenCreationTime: Date.now()
source: ''
};
const DOMAIN_0 = {
@@ -983,7 +980,7 @@ describe('database', function () {
appdb.get(APP_0.id, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an('object');
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_0);
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime'])).to.be.eql(APP_0);
done();
});
});
@@ -1023,7 +1020,7 @@ describe('database', function () {
appdb.get(APP_0.id, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an('object');
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime','resetTokenCreationTime'])).to.be.eql(APP_0);
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime'])).to.be.eql(APP_0);
done();
});
});
@@ -1033,7 +1030,7 @@ describe('database', function () {
appdb.getByHttpPort(APP_0.httpPort, function (error, result) {
expect(error).to.be(null);
expect(result).to.be.an('object');
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime','resetTokenCreationTime'])).to.be.eql(APP_0);
expect(_.omit(result, ['creationTime', 'updateTime', 'ts', 'healthTime'])).to.be.eql(APP_0);
done();
});
});
@@ -1058,8 +1055,8 @@ describe('database', function () {
expect(error).to.be(null);
expect(result).to.be.an(Array);
expect(result.length).to.be(2);
expect(_.omit(result[0], ['creationTime', 'updateTime','ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_0);
expect(_.omit(result[1], ['creationTime', 'updateTime','ts', 'healthTime', 'resetTokenCreationTime'])).to.be.eql(APP_1);
expect(_.omit(result[0], ['creationTime', 'updateTime','ts', 'healthTime'])).to.be.eql(APP_0);
expect(_.omit(result[1], ['creationTime', 'updateTime','ts', 'healthTime'])).to.be.eql(APP_1);
done();
});
});
@@ -1777,6 +1774,7 @@ describe('database', function () {
before(function (done) {
async.series([
domaindb.add.bind(null, DOMAIN_0.domain, { zoneName: DOMAIN_0.zoneName, provider: DOMAIN_0.provider, config: DOMAIN_0.config, tlsConfig: DOMAIN_0.tlsConfig }),
maildb.add.bind(null, DOMAIN_0.domain, {})
], done);
});
@@ -1950,6 +1948,23 @@ describe('database', function () {
database._clear(done);
});
it('cannot add non-existing domain', function (done) {
maildb.add(MAIL_DOMAIN_0.domain + 'nope', {}, function (error) {
expect(error).to.be.ok();
expect(error.reason).to.be(BoxError.NOT_FOUND);
done();
});
});
it('can add domain', function (done) {
maildb.add(MAIL_DOMAIN_0.domain, {}, function (error) {
expect(error).to.equal(null);
done();
});
});
it('can get all domains', function (done) {
maildb.list(function (error, result) {
expect(error).to.equal(null);
+1 -1
View File
@@ -11,7 +11,7 @@ var constants = require('../constants.js'),
exec = require('child_process').exec,
expect = require('expect.js');
const DOCKER = `docker -H tcp://172.18.0.1:${constants.DOCKER_PROXY_PORT} `;
const DOCKER = `docker -H tcp://localhost:${constants.DOCKER_PROXY_PORT} `;
describe('Dockerproxy', function () {
var containerId;
+1
View File
@@ -88,6 +88,7 @@ function setup(done) {
database._clear.bind(null),
ldapServer.start.bind(null),
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
maildb.add.bind(null, DOMAIN_0.domain, {}),
function (callback) {
users.createOwner(USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE, function (error, result) {
if (error) return callback(error);
+1
View File
@@ -34,6 +34,7 @@ function setup(done) {
database._clear,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain)
], done);
}
-15
View File
@@ -67,17 +67,6 @@ describe('Certificates', function () {
var validCert2 = '-----BEGIN CERTIFICATE-----\nMIIBwjCCAWwCCQCZjm6jL50XfTANBgkqhkiG9w0BAQsFADBoMQswCQYDVQQGEwJE\nRTEPMA0GA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05l\nYnVsb24xDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wHhcN\nMTYxMTA4MDgyMDE1WhcNMjAxMTA3MDgyMDE1WjBoMQswCQYDVQQGEwJERTEPMA0G\nA1UECAwGQmVybGluMQ8wDQYDVQQHDAZCZXJsaW4xEDAOBgNVBAoMB05lYnVsb24x\nDDAKBgNVBAsMA0NUTzEXMBUGA1UEAwwOYmF6LmZvb2Jhci5jb20wXDANBgkqhkiG\n9w0BAQEFAANLADBIAkEAtKoyTPrf2DjKbnW7Xr1HbRvV+quHTcGmUq5anDI7G4w/\nabqDXGYyakHHlPyZxYp7FWQxCm83rHUuDT1LiLIBZQIDAQABMA0GCSqGSIb3DQEB\nCwUAA0EAVaD2Q6bF9hcUUBev5NyjaMdDYURuWfjuwWUkb8W50O2ed3O+MATKrDdS\nyVaBy8W02KJ4Y1ym4je/MF8nilPurA==\n-----END CERTIFICATE-----';
var validKey2 = '-----BEGIN RSA PRIVATE KEY-----\nMIIBPQIBAAJBALSqMkz639g4ym51u169R20b1fqrh03BplKuWpwyOxuMP2m6g1xm\nMmpBx5T8mcWKexVkMQpvN6x1Lg09S4iyAWUCAwEAAQJBAJXu7YHPbjfuoalcUZzF\nbuKRCFtZQRf5z0Os6QvZ8A3iR0SzYJzx+c2ibp7WdifMXp3XaKm4tHSOfumrjUIq\nt10CIQDrs9Xo7bq0zuNjUV5IshNfaiYKZRfQciRVW2O8xBP9VwIhAMQ5CCEDZy+u\nsaF9RtmB0bjbe6XonBlAzoflfH/MAwWjAiEA50hL+ohr0MfCMM7DKaozgEj0kvan\n645VQLywnaX5x3kCIQDCwjinS9FnKmV0e/uOd6PJb0/S5IXLKt/TUpu33K5DMQIh\nAM9peu3B5t9pO59MmeUGZwI+bEJfEb+h03WTptBxS3pO\n-----END RSA PRIVATE KEY-----';
/*
Generate these with:
openssl ecparam -genkey -name prime256v1 -out server.key
openssl req -new -sha256 -key server.key -out server.csr
openssl req -x509 -sha256 -days 1460 -key server.key -in server.csr -out server.crt
*/
// *.foobar.com
var validCert4 = '-----BEGIN CERTIFICATE-----\nMIICDDCCAbOgAwIBAgIUduLaSQC6kh9LxVdua1EUBCgQOHYwCgYIKoZIzj0EAwIw\nXDELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGElu\ndGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEVMBMGA1UEAwwMKi5mb29iYXIuY29tMB4X\nDTIwMDMyNTA0MTYxMloXDTI0MDMyNDA0MTYxMlowXDELMAkGA1UEBhMCQVUxEzAR\nBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5\nIEx0ZDEVMBMGA1UEAwwMKi5mb29iYXIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0D\nAQcDQgAEmBum8MbyGXKuLP+NEOmR15XlemPEHR4b68A+B0Zjh/cuLQncAIwfmLT7\nutUOh3CivEKvZYkQIdd71xhCbVtbkqNTMFEwHQYDVR0OBBYEFCxEvAFsSFyAITNw\niBttbdsyEwO4MB8GA1UdIwQYMBaAFCxEvAFsSFyAITNwiBttbdsyEwO4MA8GA1Ud\nEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDRwAwRAIgd+rxp8xTXy7wsV45hiu1HQ2p\nwrEEPFmfPinVHwhDCiECIAEnIr5bEYUzSjujiHg7C2q3zh41XJhZWQie3VHLY/Kt\n-----END CERTIFICATE-----\n';
var validKey4 = '-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIAXuQG4YDaQuwOCvWOZjkOvw/Y5V8Oum+rWnliMTsA5woAoGCCqGSM49\nAwEHoUQDQgAEmBum8MbyGXKuLP+NEOmR15XlemPEHR4b68A+B0Zjh/cuLQncAIwf\nmLT7utUOh3CivEKvZYkQIdd71xhCbVtbkg==\n-----END EC PRIVATE KEY-----\n';
// cp /etc/ssl/openssl.cnf /tmp/openssl.cnf
// echo -e "[SAN]\nsubjectAltName=DNS:amazing.com,DNS:*.amazing.com\n" >> /tmp/openssl.cnf
// openssl req -x509 -newkey rsa:2048 -keyout amazing.key -out amazing.crt -days 3650 -subj /CN=*.amazing.com -nodes -extensions SAN -config /tmp/openssl.cnf
@@ -134,10 +123,6 @@ describe('Certificates', function () {
expect(reverseProxy.validateCertificate('', amazingDomain, { cert: validCert3, key: validKey3 })).to.be(null);
expect(reverseProxy.validateCertificate('subdomain', amazingDomain, { cert: validCert3, key: validKey3 })).to.be(null);
});
it('allows valid cert with matching domain (subdomain) - ecdsa', function () {
expect(reverseProxy.validateCertificate('baz', foobarDomain, { cert: validCert4, key: validKey4 })).to.be(null);
});
});
describe('generateFallbackCertificiate - non-hyphenated', function () {
+6
View File
@@ -82,6 +82,7 @@ describe('updatechecker - box - manual (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
settings.setBoxAutoupdatePattern.bind(null, constants.AUTOUPDATE_PATTERN_NEVER),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
@@ -153,6 +154,7 @@ describe('updatechecker - box - automatic (no email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
], done);
@@ -188,6 +190,7 @@ describe('updatechecker - box - automatic free (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
settingsdb.set.bind(null, settings.CLOUDRON_TOKEN_KEY, 'atoken'),
], done);
@@ -251,6 +254,7 @@ describe('updatechecker - app - manual (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
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),
@@ -360,6 +364,7 @@ describe('updatechecker - app - automatic (no email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
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 * * *'),
@@ -425,6 +430,7 @@ describe('updatechecker - app - automatic free (email)', function () {
cron.startJobs,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
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 * * *'),
+1
View File
@@ -74,6 +74,7 @@ function setup(done) {
database._clear,
domains.add.bind(null, DOMAIN_0.domain, DOMAIN_0, AUDIT_SOURCE),
settings.setAdmin.bind(null, DOMAIN_0.domain, 'my.' + DOMAIN_0.domain),
mail.addDomain.bind(null, DOMAIN_0.domain),
], done);
}
+1 -1
View File
@@ -29,7 +29,7 @@ var assert = require('assert'),
mysql = require('mysql');
var USERS_FIELDS = [ 'id', 'username', 'email', 'fallbackEmail', 'password', 'salt', 'createdAt', 'modifiedAt', 'resetToken', 'displayName',
'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role', 'resetTokenCreationTime' ].join(',');
'twoFactorAuthenticationEnabled', 'twoFactorAuthenticationSecret', 'active', 'source', 'role' ].join(',');
var APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime' ].join(',');
+2 -3
View File
@@ -495,11 +495,10 @@ function resetPasswordByIdentifier(identifier, callback) {
getter(identifier.toLowerCase(), function (error, result) {
if (error) return callback(error);
let resetToken = hat(256), resetTokenCreationTime = new Date();
let resetToken = hat(256);
result.resetToken = resetToken;
result.resetTokenCreationTime = resetTokenCreationTime;
userdb.update(result.id, { resetToken, resetTokenCreationTime }, function (error) {
userdb.update(result.id, { resetToken }, function (error) {
if (error) return callback(error);
mailer.passwordReset(result);