only owner can install/repair/update/exec docker addon apps
This commit is contained in:
+138
-141
@@ -49,6 +49,7 @@ exports = module.exports = {
|
||||
exec: exec,
|
||||
|
||||
checkManifestConstraints: checkManifestConstraints,
|
||||
downloadManifest: downloadManifest,
|
||||
|
||||
canAutoupdateApp: canAutoupdateApp,
|
||||
autoupdateApps: autoupdateApps,
|
||||
@@ -694,6 +695,8 @@ function install(data, auditSource, callback) {
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
assert(data.appStoreId && data.manifest); // manifest is already downloaded
|
||||
|
||||
var location = data.location.toLowerCase(),
|
||||
domain = data.domain.toLowerCase(),
|
||||
portBindings = data.portBindings || null,
|
||||
@@ -710,113 +713,109 @@ function install(data, auditSource, callback) {
|
||||
env = data.env || {},
|
||||
label = data.label || null,
|
||||
tags = data.tags || [],
|
||||
overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false;
|
||||
overwriteDns = 'overwriteDns' in data ? data.overwriteDns : false,
|
||||
appStoreId = data.appStoreId,
|
||||
manifest = data.manifest;
|
||||
|
||||
assert(data.appStoreId || data.manifest); // atleast one of them is required
|
||||
let error = manifestFormat.parse(manifest);
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, 'Manifest error: ' + error.message));
|
||||
|
||||
downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
|
||||
error = checkManifestConstraints(manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validatePortBindings(portBindings, manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateAccessRestriction(accessRestriction);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateMemoryLimit(manifest, memoryLimit);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDebugMode(debugMode);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateLabel(label);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateTags(tags);
|
||||
if (error) return callback(error);
|
||||
|
||||
if ('sso' in data && !('optionalSso' in manifest)) return callback(new BoxError(BoxError.BAD_FIELD, 'sso can only be specified for apps with optionalSso'));
|
||||
// if sso was unspecified, enable it by default if possible
|
||||
if (sso === null) sso = !!manifest.addons['ldap'] || !!manifest.addons['oauth'];
|
||||
|
||||
error = validateEnv(env);
|
||||
if (error) return callback(error);
|
||||
|
||||
const mailboxName = mailboxNameForLocation(location, manifest);
|
||||
const appId = uuid.v4();
|
||||
|
||||
if (icon) {
|
||||
if (!validator.isBase64(icon)) return callback(new BoxError(BoxError.BAD_FIELD, 'icon is not base64', { field: 'icon' }));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.png'), Buffer.from(icon, 'base64'))) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
}
|
||||
|
||||
const locations = [{subdomain: location, domain}].concat(alternateDomains);
|
||||
validateLocations(locations, function (error, domainObjectMap) {
|
||||
if (error) return callback(error);
|
||||
|
||||
error = manifestFormat.parse(manifest);
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, 'Manifest error: ' + error.message));
|
||||
|
||||
error = checkManifestConstraints(manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validatePortBindings(portBindings, manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateAccessRestriction(accessRestriction);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateMemoryLimit(manifest, memoryLimit);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateDebugMode(debugMode);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateLabel(label);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = validateTags(tags);
|
||||
if (error) return callback(error);
|
||||
|
||||
if ('sso' in data && !('optionalSso' in manifest)) return callback(new BoxError(BoxError.BAD_FIELD, 'sso can only be specified for apps with optionalSso'));
|
||||
// if sso was unspecified, enable it by default if possible
|
||||
if (sso === null) sso = !!manifest.addons['ldap'] || !!manifest.addons['oauth'];
|
||||
|
||||
error = validateEnv(env);
|
||||
if (error) return callback(error);
|
||||
|
||||
const mailboxName = mailboxNameForLocation(location, manifest);
|
||||
const appId = uuid.v4();
|
||||
|
||||
if (icon) {
|
||||
if (!validator.isBase64(icon)) return callback(new BoxError(BoxError.BAD_FIELD, 'icon is not base64', { field: 'icon' }));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.png'), Buffer.from(icon, 'base64'))) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
if (cert && key) {
|
||||
error = reverseProxy.validateCertificate(location, domainObjectMap[domain], { cert, key });
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, error.message, { field: 'cert' }));
|
||||
}
|
||||
|
||||
const locations = [{subdomain: location, domain}].concat(alternateDomains);
|
||||
validateLocations(locations, function (error, domainObjectMap) {
|
||||
debug('Will install app with id : ' + appId);
|
||||
|
||||
var data = {
|
||||
accessRestriction: accessRestriction,
|
||||
memoryLimit: memoryLimit,
|
||||
sso: sso,
|
||||
debugMode: debugMode,
|
||||
mailboxName: mailboxName,
|
||||
mailboxDomain: domain,
|
||||
enableBackup: enableBackup,
|
||||
enableAutomaticUpdate: enableAutomaticUpdate,
|
||||
alternateDomains: alternateDomains,
|
||||
env: env,
|
||||
label: label,
|
||||
tags: tags,
|
||||
runState: exports.RSTATE_RUNNING,
|
||||
installationState: exports.ISTATE_PENDING_INSTALL
|
||||
};
|
||||
|
||||
appdb.add(appId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
|
||||
if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, locations, domainObjectMap, portBindings));
|
||||
if (error) return callback(error);
|
||||
|
||||
if (cert && key) {
|
||||
error = reverseProxy.validateCertificate(location, domainObjectMap[domain], { cert, key });
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, error.message, { field: 'cert' }));
|
||||
}
|
||||
|
||||
debug('Will install app with id : ' + appId);
|
||||
|
||||
var data = {
|
||||
accessRestriction: accessRestriction,
|
||||
memoryLimit: memoryLimit,
|
||||
sso: sso,
|
||||
debugMode: debugMode,
|
||||
mailboxName: mailboxName,
|
||||
mailboxDomain: domain,
|
||||
enableBackup: enableBackup,
|
||||
enableAutomaticUpdate: enableAutomaticUpdate,
|
||||
alternateDomains: alternateDomains,
|
||||
env: env,
|
||||
label: label,
|
||||
tags: tags,
|
||||
runState: exports.RSTATE_RUNNING,
|
||||
installationState: exports.ISTATE_PENDING_INSTALL
|
||||
};
|
||||
|
||||
appdb.add(appId, appStoreId, manifest, location, domain, translatePortBindings(portBindings, manifest), data, function (error) {
|
||||
if (error && error.reason === BoxError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(error.message, locations, domainObjectMap, portBindings));
|
||||
purchaseApp({ appId: appId, appstoreId: appStoreId, manifestId: manifest.id || 'customapp' }, function (error) {
|
||||
if (error) return callback(error);
|
||||
|
||||
purchaseApp({ appId: appId, appstoreId: appStoreId, manifestId: manifest.id || 'customapp' }, function (error) {
|
||||
// save cert to boxdata/certs
|
||||
if (cert && key) {
|
||||
let error = reverseProxy.setAppCertificateSync(location, domainObjectMap[domain], { cert, key });
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
const task = {
|
||||
args: { restoreConfig: null, overwriteDns },
|
||||
values: { },
|
||||
requiredState: data.installationState
|
||||
};
|
||||
|
||||
addTask(appId, data.installationState, task, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
// save cert to boxdata/certs
|
||||
if (cert && key) {
|
||||
let error = reverseProxy.setAppCertificateSync(location, domainObjectMap[domain], { cert, key });
|
||||
if (error) return callback(error);
|
||||
}
|
||||
const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings });
|
||||
newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]);
|
||||
newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
|
||||
const task = {
|
||||
args: { restoreConfig: null, overwriteDns },
|
||||
values: { },
|
||||
requiredState: data.installationState
|
||||
};
|
||||
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId, app: newApp, taskId: result.taskId });
|
||||
|
||||
addTask(appId, data.installationState, task, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
const newApp = _.extend({}, data, { appStoreId, manifest, location, domain, portBindings });
|
||||
newApp.fqdn = domains.fqdn(newApp.location, domainObjectMap[newApp.domain]);
|
||||
newApp.alternateDomains.forEach(function (ad) { ad.fqdn = domains.fqdn(ad.subdomain, domainObjectMap[ad.domain]); });
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId, app: newApp, taskId: result.taskId });
|
||||
|
||||
callback(null, { id : appId, taskId: result.taskId });
|
||||
});
|
||||
callback(null, { id : appId, taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1257,12 +1256,14 @@ function setDataDir(appId, dataDir, auditSource, callback) {
|
||||
function update(appId, data, auditSource, callback) {
|
||||
assert.strictEqual(typeof appId, 'string');
|
||||
assert(data && typeof data === 'object');
|
||||
assert.strictEqual(data.manifest && typeof data.manifest === 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
debug(`update: id:${appId}`);
|
||||
|
||||
const skipBackup = !!data.skipBackup;
|
||||
const skipBackup = !!data.skipBackup,
|
||||
manifest = data.manifest;
|
||||
|
||||
get(appId, function (error, app) {
|
||||
if (error) return callback(error);
|
||||
@@ -1272,67 +1273,63 @@ function update(appId, data, auditSource, callback) {
|
||||
|
||||
if (app.runState === exports.RSTATE_STOPPED) return callback(new BoxError(BoxError.BAD_STATE, 'Stopped apps cannot be updated'));
|
||||
|
||||
downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) {
|
||||
if (error) return callback(error);
|
||||
error = manifestFormat.parse(manifest);
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, 'Manifest error:' + error.message));
|
||||
|
||||
error = manifestFormat.parse(manifest);
|
||||
if (error) return callback(new BoxError(BoxError.BAD_FIELD, 'Manifest error:' + error.message));
|
||||
error = checkManifestConstraints(manifest);
|
||||
if (error) return callback(error);
|
||||
|
||||
error = checkManifestConstraints(manifest);
|
||||
if (error) return callback(error);
|
||||
var updateConfig = { skipBackup, manifest };
|
||||
|
||||
var updateConfig = { skipBackup, manifest };
|
||||
// prevent user from installing a app with different manifest id over an existing app
|
||||
// this allows cloudron install -f --app <appid> for an app installed from the appStore
|
||||
if (app.manifest.id !== updateConfig.manifest.id) {
|
||||
if (!data.force) return callback(new BoxError(BoxError.BAD_FIELD, 'manifest id does not match. force to override'));
|
||||
// clear appStoreId so that this app does not get updates anymore
|
||||
updateConfig.appStoreId = '';
|
||||
}
|
||||
|
||||
// prevent user from installing a app with different manifest id over an existing app
|
||||
// this allows cloudron install -f --app <appid> for an app installed from the appStore
|
||||
if (app.manifest.id !== updateConfig.manifest.id) {
|
||||
if (!data.force) return callback(new BoxError(BoxError.BAD_FIELD, 'manifest id does not match. force to override'));
|
||||
// clear appStoreId so that this app does not get updates anymore
|
||||
updateConfig.appStoreId = '';
|
||||
}
|
||||
// suffix '0' if prerelease is missing for semver.lte to work as expected
|
||||
const currentVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
|
||||
const updateVersion = semver.prerelease(updateConfig.manifest.version) ? updateConfig.manifest.version : `${updateConfig.manifest.version}-0`;
|
||||
if (app.appStoreId !== '' && semver.lte(updateVersion, currentVersion)) {
|
||||
if (!data.force) return callback(new BoxError(BoxError.BAD_FIELD, 'Downgrades are not permitted for apps installed from AppStore. force to override'));
|
||||
}
|
||||
|
||||
// suffix '0' if prerelease is missing for semver.lte to work as expected
|
||||
const currentVersion = semver.prerelease(app.manifest.version) ? app.manifest.version : `${app.manifest.version}-0`;
|
||||
const updateVersion = semver.prerelease(updateConfig.manifest.version) ? updateConfig.manifest.version : `${updateConfig.manifest.version}-0`;
|
||||
if (app.appStoreId !== '' && semver.lte(updateVersion, currentVersion)) {
|
||||
if (!data.force) return callback(new BoxError(BoxError.BAD_FIELD, 'Downgrades are not permitted for apps installed from AppStore. force to override'));
|
||||
}
|
||||
if ('icon' in data) {
|
||||
if (data.icon) {
|
||||
if (!validator.isBase64(data.icon)) return callback(new BoxError(BoxError.BAD_FIELD, 'icon is not base64', { field: 'icon' }));
|
||||
|
||||
if ('icon' in data) {
|
||||
if (data.icon) {
|
||||
if (!validator.isBase64(data.icon)) return callback(new BoxError(BoxError.BAD_FIELD, 'icon is not base64', { field: 'icon' }));
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'), Buffer.from(data.icon, 'base64'))) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
} else {
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'));
|
||||
if (!safe.fs.writeFileSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'), Buffer.from(data.icon, 'base64'))) {
|
||||
return callback(new BoxError(BoxError.FS_ERROR, 'Error saving icon:' + safe.error.message));
|
||||
}
|
||||
} else {
|
||||
safe.fs.unlinkSync(path.join(paths.APP_ICONS_DIR, appId + '.user.png'));
|
||||
}
|
||||
}
|
||||
|
||||
// do not update apps in debug mode
|
||||
if (app.debugMode && !data.force) return callback(new BoxError(BoxError.BAD_STATE, 'debug mode enabled. force to override'));
|
||||
// do not update apps in debug mode
|
||||
if (app.debugMode && !data.force) return callback(new BoxError(BoxError.BAD_STATE, 'debug mode enabled. force to override'));
|
||||
|
||||
// Ensure we update the memory limit in case the new app requires more memory as a minimum
|
||||
// 0 and -1 are special updateConfig for memory limit indicating unset and unlimited
|
||||
if (app.memoryLimit > 0 && updateConfig.manifest.memoryLimit && app.memoryLimit < updateConfig.manifest.memoryLimit) {
|
||||
updateConfig.memoryLimit = updateConfig.manifest.memoryLimit;
|
||||
}
|
||||
// Ensure we update the memory limit in case the new app requires more memory as a minimum
|
||||
// 0 and -1 are special updateConfig for memory limit indicating unset and unlimited
|
||||
if (app.memoryLimit > 0 && updateConfig.manifest.memoryLimit && app.memoryLimit < updateConfig.manifest.memoryLimit) {
|
||||
updateConfig.memoryLimit = updateConfig.manifest.memoryLimit;
|
||||
}
|
||||
|
||||
const task = {
|
||||
args: { updateConfig },
|
||||
values: {}
|
||||
};
|
||||
addTask(appId, exports.ISTATE_PENDING_UPDATE, task, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
const task = {
|
||||
args: { updateConfig },
|
||||
values: {}
|
||||
};
|
||||
addTask(appId, exports.ISTATE_PENDING_UPDATE, task, function (error, result) {
|
||||
if (error) return callback(error);
|
||||
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId, app, skipBackup, toManifest: manifest, fromManifest: app.manifest, force: data.force, taskId: result.taskId });
|
||||
eventlog.add(eventlog.ACTION_APP_UPDATE, auditSource, { appId, app, skipBackup, toManifest: manifest, fromManifest: app.manifest, force: data.force, taskId: result.taskId });
|
||||
|
||||
// clear update indicator, if update fails, it will come back through the update checker
|
||||
updateChecker.resetAppUpdateInfo(appId);
|
||||
// clear update indicator, if update fails, it will come back through the update checker
|
||||
updateChecker.resetAppUpdateInfo(appId);
|
||||
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
callback(null, { taskId: result.taskId });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user