diff --git a/CHANGES b/CHANGES index 8ec31d535..b764a4f2f 100644 --- a/CHANGES +++ b/CHANGES @@ -2346,4 +2346,5 @@ * notifications: better oom message for redis * Add way to impersonate users for presetup * mail: open up port 465 for mail submission (TLS) +* Implement operator role for apps diff --git a/src/apps.js b/src/apps.js index 2578f4a95..ebc9261ab 100644 --- a/src/apps.js +++ b/src/apps.js @@ -3,6 +3,7 @@ exports = module.exports = { canAccess, isOperator, + accessLevel, removeInternalFields, removeRestrictedFields, @@ -126,7 +127,6 @@ exports = module.exports = { _validatePortBindings: validatePortBindings, _validateAccessRestriction: validateAccessRestriction, _translatePortBindings: translatePortBindings, - _accessLevel: accessLevel, _clear: clear }; @@ -439,7 +439,7 @@ function getDataDir(app, dataDir) { function removeInternalFields(app) { return _.pick(app, 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', - 'location', 'domain', 'fqdn', 'mailboxName', 'mailboxDomain', 'accessLevel', + 'location', 'domain', 'fqdn', 'mailboxName', 'mailboxDomain', 'accessRestriction', 'manifest', 'portBindings', 'iconUrl', 'memoryLimit', 'cpuShares', 'operators', 'sso', 'debugMode', 'reverseProxyConfig', 'enableBackup', 'creationTime', 'updateTime', 'ts', 'tags', 'label', 'alternateDomains', 'aliasDomains', 'env', 'enableAutomaticUpdate', 'dataDir', 'mounts', 'enableMailbox'); @@ -449,7 +449,7 @@ function removeInternalFields(app) { function removeRestrictedFields(app) { return _.pick(app, 'id', 'appStoreId', 'installationState', 'error', 'runState', 'health', 'taskId', 'accessRestriction', 'alternateDomains', 'aliasDomains', 'sso', - 'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup', 'accessLevel'); + 'location', 'domain', 'fqdn', 'manifest', 'portBindings', 'iconUrl', 'creationTime', 'ts', 'tags', 'label', 'enableBackup'); } async function getIcon(app, options) { @@ -778,8 +778,8 @@ async function updateWithConstraints(id, app, constraints) { } const fields = [ ], values = [ ]; - for (let p in app) { - if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig') { + for (const p in app) { + if (p === 'manifest' || p === 'tags' || p === 'accessRestriction' || p === 'debugMode' || p === 'error' || p === 'reverseProxyConfig' || p === 'servicesConfig' || p === 'operators') { fields.push(`${p}Json = ?`); values.push(JSON.stringify(app[p])); } else if (p !== 'portBindings' && p !== 'location' && p !== 'domain' && p !== 'alternateDomains' && p !== 'aliasDomains' && p !== 'env' && p !== 'mounts') { @@ -928,16 +928,8 @@ async function getByFqdn(fqdn) { async function listByUser(user) { assert.strictEqual(typeof user, 'object'); - const allApps = await list(); - const result = []; - for (const app of allApps) { - const al = accessLevel(app, user); - if (!al) continue; - app.accessLevel = al; - result.push(app); - } - - return result; + const result = await list(); + return result.filter((app) => canAccess(app, user)); } async function downloadManifest(appStoreId, manifest) { diff --git a/src/routes/apps.js b/src/routes/apps.js index dcab0954e..13e53f580 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -74,7 +74,10 @@ async function load(req, res, next) { function getApp(req, res, next) { assert.strictEqual(typeof req.app, 'object'); - next(new HttpSuccess(200, apps.removeInternalFields(req.app))); + const result = apps.removeInternalFields(req.app); + result.accessLevel = apps.accessLevel(req.app, req.user); + + next(new HttpSuccess(200, result)); } async function listByUser(req, res, next) { @@ -83,7 +86,11 @@ async function listByUser(req, res, next) { let [error, result] = await safe(apps.listByUser(req.user)); if (error) return next(BoxError.toHttpError(error)); - result = result.map(apps.removeRestrictedFields); + result = result.map(r => { + const app = apps.removeRestrictedFields(r); + app.accessLevel = apps.accessLevel(r, req.user); + return app; + }); next(new HttpSuccess(200, { apps: result })); } diff --git a/src/server.js b/src/server.js index 729ec0ffe..18c1df117 100644 --- a/src/server.js +++ b/src/server.js @@ -200,7 +200,7 @@ function initializeExpressSync() { // app routes router.post('/api/v1/apps/install', json, token, authorizeAdmin, routes.apps.install); router.get ('/api/v1/apps', token, routes.apps.listByUser); - router.get ('/api/v1/apps/:id', token, authorizeAdmin, routes.apps.load, routes.apps.getApp); + router.get ('/api/v1/apps/:id', token, routes.apps.load, authorizeOperator, routes.apps.getApp); router.get ('/api/v1/apps/:id/icon', token, routes.apps.load, routes.apps.getAppIcon); router.post('/api/v1/apps/:id/uninstall', json, token, authorizeAdmin, routes.apps.load, routes.apps.uninstall); router.post('/api/v1/apps/:id/configure/access_restriction', json, token, authorizeAdmin, routes.apps.load, routes.apps.setAccessRestriction); diff --git a/src/test/apps-test.js b/src/test/apps-test.js index 3ecfa4d37..f542366d9 100644 --- a/src/test/apps-test.js +++ b/src/test/apps-test.js @@ -217,6 +217,7 @@ describe('Apps', function () { accessRestriction: '', memoryLimit: 1337, cpuShares: 102, + operators: { users: [ 'someid' ] } }; await apps.update(app.id, data);