diff --git a/migrations/20220607204734-apps-add-upstreamUri.js b/migrations/20220607204734-apps-add-upstreamUri.js new file mode 100644 index 000000000..1c65d786e --- /dev/null +++ b/migrations/20220607204734-apps-add-upstreamUri.js @@ -0,0 +1,9 @@ +'use strict'; + +exports.up = async function (db) { + await db.runSql('ALTER TABLE apps ADD COLUMN upstreamUri VARCHAR(256) DEFAULT ""'); +}; + +exports.down = async function (db) { + await db.runSql('ALTER TABLE apps DROP COLUMN upstreamUri'); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index f418ce488..e092fec25 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -102,6 +102,7 @@ CREATE TABLE IF NOT EXISTS apps( appStoreIcon MEDIUMBLOB, icon MEDIUMBLOB, crontab TEXT, + upstreamUri VARCHAR(256) DEFAULT "", FOREIGN KEY(mailboxDomain) REFERENCES domains(domain), FOREIGN KEY(taskId) REFERENCES tasks(id), diff --git a/src/apps.js b/src/apps.js index 2df184803..9e241b7c6 100644 --- a/src/apps.js +++ b/src/apps.js @@ -27,6 +27,7 @@ exports = module.exports = { setAccessRestriction, setOperators, setCrontab, + setUpstreamUri, setLabel, setIcon, setTags, @@ -186,7 +187,7 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS 'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.accessRestrictionJson', 'apps.memoryLimit', 'apps.cpuShares', 'apps.label', 'apps.tagsJson', 'apps.taskId', 'apps.reverseProxyConfigJson', 'apps.servicesConfigJson', 'apps.operatorsJson', 'apps.sso', 'apps.debugModeJson', 'apps.enableBackup', 'apps.proxyAuth', 'apps.containerIp', 'apps.crontab', - 'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', + 'apps.creationTime', 'apps.updateTime', 'apps.enableAutomaticUpdate', 'apps.upstreamUri', 'apps.enableMailbox', 'apps.mailboxDisplayName', 'apps.mailboxName', 'apps.mailboxDomain', 'apps.enableInbox', 'apps.inboxName', 'apps.inboxDomain', 'apps.storageVolumeId', 'apps.storageVolumePrefix', 'apps.ts', 'apps.healthTime', '(apps.icon IS NOT NULL) AS hasIcon', '(apps.appStoreIcon IS NOT NULL) AS hasAppStoreIcon' ].join(','); @@ -454,6 +455,14 @@ function validateBackupFormat(format) { return new BoxError(BoxError.BAD_FIELD, 'Invalid backup format'); } +function validateUpstreamUri(upstreamUri) { + if (upstreamUri === null) return null; + + if (upstreamUri.length > 256) return new BoxError(BoxError.BAD_FIELD, 'upstreamUri must be less than 256'); + + return null; +} + function validateLabel(label) { if (label === null) return null; @@ -554,7 +563,7 @@ async function getStorageDir(app) { function removeInternalFields(app) { return _.pick(app, 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', - 'subdomain', 'domain', 'fqdn', 'crontab', + 'subdomain', 'domain', 'fqdn', 'crontab', 'upstreamUri', 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', 'label', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'storageVolumeId', 'storageVolumePrefix', 'mounts', @@ -565,7 +574,7 @@ function removeInternalFields(app) { function removeRestrictedFields(app) { return _.pick(app, 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'accessRestriction', 'secondaryDomains', 'redirectDomains', 'aliasDomains', 'sso', - 'subdomain', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup'); + 'subdomain', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup', 'upstreamUri'); } async function getIcon(app, options) { @@ -1430,6 +1439,19 @@ async function setCrontab(app, crontab, auditSource) { await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, crontab }); } +async function setUpstreamUri(app, upstreamUri, auditSource) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof upstreamUri, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + + const appId = app.id; + let error = validateUpstreamUri(upstreamUri); + if (error) throw error; + + await update(appId, { upstreamUri }); + await eventlog.add(eventlog.ACTION_APP_CONFIGURE, auditSource, { appId, app, upstreamUri }); +} + async function setLabel(app, label, auditSource) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof label, 'string'); diff --git a/src/routes/apps.js b/src/routes/apps.js index 8161658a1..7887b486c 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -37,6 +37,7 @@ exports = module.exports = { setLocation, setStorage, setMounts, + setUpstreamUri, stop, start, @@ -898,6 +899,18 @@ async function setMounts(req, res, next) { next(new HttpSuccess(202, { taskId: result.taskId })); } +async function setUpstreamUri(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + assert.strictEqual(typeof req.app, 'object'); + + if (typeof req.body.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a string')); + + const [error] = await safe(apps.setUpstreamUri(req.app, req.body.upstreamUri, AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(200, {})); +} + async function listEventlog(req, res, next) { assert.strictEqual(typeof req.app, 'object'); diff --git a/src/server.js b/src/server.js index dd6106294..5ad21f1d8 100644 --- a/src/server.js +++ b/src/server.js @@ -227,6 +227,7 @@ function initializeExpressSync() { router.post('/api/v1/apps/:id/configure/location', json, token, routes.apps.load, authorizeAdmin, routes.apps.setLocation); router.post('/api/v1/apps/:id/configure/mounts', json, token, routes.apps.load, authorizeAdmin, routes.apps.setMounts); router.post('/api/v1/apps/:id/configure/crontab', json, token, routes.apps.load, authorizeOperator, routes.apps.setCrontab); + router.post('/api/v1/apps/:id/configure/upstream_uri', json, token, routes.apps.load, authorizeOperator, routes.apps.setUpstreamUri); router.post('/api/v1/apps/:id/repair', json, token, routes.apps.load, authorizeOperator, routes.apps.repair); router.post('/api/v1/apps/:id/check_for_updates', json, token, routes.apps.load, authorizeOperator, routes.apps.checkForUpdates); router.post('/api/v1/apps/:id/update', json, token, routes.apps.load, authorizeOperator, routes.apps.update);