diff --git a/migrations/20151016131005-apps-rename-accessRestriction.js b/migrations/20151016131005-apps-rename-accessRestriction.js new file mode 100644 index 000000000..9f5303e87 --- /dev/null +++ b/migrations/20151016131005-apps-rename-accessRestriction.js @@ -0,0 +1,17 @@ +var dbm = global.dbm || require('db-migrate'); +var type = dbm.dataType; + +exports.up = function(db, callback) { + db.runSql('ALTER TABLE apps CHANGE accessRestriction accessRestrictionJson VARCHAR(2048)', [], function (error) { + if (error) console.error(error); + callback(error); + }); +}; + +exports.down = function(db, callback) { + db.runSql('ALTER TABLE apps CHANGE accessRestrictionJson accessRestriction VARCHAR(2048)', [], function (error) { + if (error) console.error(error); + callback(error); + }); +}; + diff --git a/migrations/schema.sql b/migrations/schema.sql index a3885c623..c3b8aea4c 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS apps( httpPort INTEGER, // this is the nginx proxy port and not manifest.httpPort location VARCHAR(128) NOT NULL UNIQUE, dnsRecordId VARCHAR(512), - accessRestriction VARCHAR(512), + accessRestrictionJson VARCHAR(2048), oauthProxy BOOLEAN DEFAULT 0, createdAt TIMESTAMP(2) NOT NULL DEFAULT CURRENT_TIMESTAMP, diff --git a/src/appdb.js b/src/appdb.js index 82c524090..47bad521c 100644 --- a/src/appdb.js +++ b/src/appdb.js @@ -59,7 +59,7 @@ var assert = require('assert'), var APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationState', 'apps.installationProgress', 'apps.runState', 'apps.health', 'apps.containerId', 'apps.manifestJson', 'apps.httpPort', 'apps.location', 'apps.dnsRecordId', - 'apps.accessRestriction', 'apps.lastBackupId', 'apps.lastBackupConfigJson', 'apps.oldConfigJson', 'apps.oauthProxy' ].join(','); + 'apps.accessRestrictionJson', 'apps.lastBackupId', 'apps.lastBackupConfigJson', 'apps.oldConfigJson', 'apps.oauthProxy' ].join(','); var PORT_BINDINGS_FIELDS = [ 'hostPort', 'environmentVariable', 'appId' ].join(','); @@ -93,6 +93,11 @@ function postProcess(result) { } result.oauthProxy = !!result.oauthProxy; + + assert(result.accessRestrictionJson === null || typeof result.accessRestrictionJson === 'string'); + result.accessRestriction = safe.JSON.parse(result.accessRestrictionJson); + if (result.accessRestriction && !result.accessRestriction.users) result.accessRestriction.users = []; + delete result.accessRestrictionJson; } function get(id, callback) { @@ -181,18 +186,19 @@ function add(id, appStoreId, manifest, location, portBindings, accessRestriction assert.strictEqual(typeof manifest.version, 'string'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof portBindings, 'object'); - assert.strictEqual(typeof accessRestriction, 'string'); + assert.strictEqual(typeof accessRestriction, 'object'); assert.strictEqual(typeof oauthProxy, 'boolean'); assert.strictEqual(typeof callback, 'function'); portBindings = portBindings || { }; var manifestJson = JSON.stringify(manifest); + var accessRestrictionJson = JSON.stringify(accessRestriction); var queries = [ ]; queries.push({ - query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, accessRestriction, oauthProxy) VALUES (?, ?, ?, ?, ?, ?, ?)', - args: [ id, appStoreId, manifestJson, exports.ISTATE_PENDING_INSTALL, location, accessRestriction, oauthProxy ] + query: 'INSERT INTO apps (id, appStoreId, manifestJson, installationState, location, accessRestrictionJson, oauthProxy) VALUES (?, ?, ?, ?, ?, ?, ?)', + args: [ id, appStoreId, manifestJson, exports.ISTATE_PENDING_INSTALL, location, accessRestrictionJson, oauthProxy ] }); Object.keys(portBindings).forEach(function (env) { @@ -301,6 +307,9 @@ function updateWithConstraints(id, app, constraints, callback) { } else if (p === 'oldConfig') { fields.push('oldConfigJson = ?'); values.push(JSON.stringify(app[p])); + } else if (p === 'accessRestriction') { + fields.push('accessRestrictionJson = ?'); + values.push(JSON.stringify(app[p])); } else if (p !== 'portBindings') { fields.push(p + ' = ?'); values.push(app[p]); diff --git a/src/apps.js b/src/apps.js index 60b7671cb..f3a5cd2f2 100644 --- a/src/apps.js +++ b/src/apps.js @@ -184,16 +184,12 @@ function validatePortBindings(portBindings, tcpPorts) { } function validateAccessRestriction(accessRestriction) { - assert.strictEqual(typeof accessRestriction, 'string'); + assert.strictEqual(typeof accessRestriction, 'object'); - function validator(entry) { - if (entry === '') return true; - if (entry.indexOf('user-') === 0 && entry.length > 'user-'.length) return true; - return false; - } + if (accessRestriction === null) return null; - var entries = accessRestriction.split(',').map(function (e) { return e.trim(); }); - if (!entries.every(validator)) return new Error('Invalid accessRestriction'); + if (!accessRestriction.users) return new Error('Users property required'); + if (!accessRestriction.users.every(function (e) { return typeof e === 'string'; })) return new Error('All users have to be strings'); return null; } @@ -229,14 +225,8 @@ function hasAccessTo(app, user) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof user, 'object'); - function validator(entry) { - if (entry.indexOf('user-') === 0 && entry.slice('user-'.length) === user.id) return true; - return false; - } - - if (app.accessRestriction === '') return true; - - return app.accessRestriction.split(',').some(validator); + if (app.accessRestriction === null) return true; + return app.accessRestriction.users.some(function (e) { return e === user.id; }); } function get(appId, callback) { @@ -308,7 +298,7 @@ function install(appId, appStoreId, manifest, location, portBindings, accessRest assert(manifest && typeof manifest === 'object'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof portBindings, 'object'); - assert.strictEqual(typeof accessRestriction, 'string'); + assert.strictEqual(typeof accessRestriction, 'object'); assert.strictEqual(typeof oauthProxy, 'boolean'); assert(!icon || typeof icon === 'string'); assert.strictEqual(typeof callback, 'function'); @@ -356,7 +346,7 @@ function configure(appId, location, portBindings, accessRestriction, oauthProxy, assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof location, 'string'); assert.strictEqual(typeof portBindings, 'object'); - assert.strictEqual(typeof accessRestriction, 'string'); + assert.strictEqual(typeof accessRestriction, 'object'); assert.strictEqual(typeof oauthProxy, 'boolean'); assert.strictEqual(typeof callback, 'function'); @@ -401,12 +391,11 @@ function configure(appId, location, portBindings, accessRestriction, oauthProxy, }); } -function update(appId, force, manifest, portBindings, accessRestriction, icon, callback) { +function update(appId, force, manifest, portBindings, icon, callback) { assert.strictEqual(typeof appId, 'string'); assert.strictEqual(typeof force, 'boolean'); assert(manifest && typeof manifest === 'object'); assert(!portBindings || typeof portBindings === 'object'); - assert(accessRestriction === null || typeof accessRestriction === 'string'); assert(!icon || typeof icon === 'string'); assert.strictEqual(typeof callback, 'function'); @@ -421,11 +410,6 @@ function update(appId, force, manifest, portBindings, accessRestriction, icon, c error = validatePortBindings(portBindings, manifest.tcpPorts); if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message)); - if (accessRestriction !== null) { - error = validateAccessRestriction(accessRestriction); - if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message)); - } - if (icon) { if (!validator.isBase64(icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64')); @@ -441,7 +425,6 @@ function update(appId, force, manifest, portBindings, accessRestriction, icon, c var values = { manifest: manifest, portBindings: portBindings, - accessRestriction: accessRestriction === null ? app.accessRestriction : accessRestriction, oldConfig: { manifest: app.manifest, portBindings: app.portBindings, diff --git a/src/routes/apps.js b/src/routes/apps.js index 41ab76e69..3a28123b2 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -114,14 +114,14 @@ function installApp(req, res, next) { if (typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId is required')); if (typeof data.location !== 'string') return next(new HttpError(400, 'location is required')); if (('portBindings' in data) && typeof data.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object')); - if (typeof data.accessRestriction !== 'string') return next(new HttpError(400, 'accessRestriction is required')); + if (typeof data.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction is required')); if (typeof data.oauthProxy !== 'boolean') return next(new HttpError(400, 'oauthProxy must be a boolean')); if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string')); // allow tests to provide an appId for testing var appId = (process.env.BOX_ENV === 'test' && typeof data.appId === 'string') ? data.appId : uuid.v4(); - debug('Installing app id:%s storeid:%s loc:%s port:%j accessRestriction:%s oauthproxy:%s manifest:%j', appId, data.appStoreId, data.location, data.portBindings, data.accessRestriction, data.oauthProxy, data.manifest); + debug('Installing app id:%s storeid:%s loc:%s port:%j accessRestriction:%j oauthproxy:%s manifest:%j', appId, data.appStoreId, data.location, data.portBindings, data.accessRestriction, data.oauthProxy, data.manifest); apps.install(appId, data.appStoreId, data.manifest, data.location, data.portBindings || null, data.accessRestriction, data.oauthProxy, data.icon || null, function (error) { if (error && error.reason === AppsError.ALREADY_EXISTS) return next(new HttpError(409, error.message)); @@ -151,10 +151,10 @@ function configureApp(req, res, next) { if (!data) return next(new HttpError(400, 'Cannot parse data field')); if (typeof data.location !== 'string') return next(new HttpError(400, 'location is required')); if (('portBindings' in data) && typeof data.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object')); - if (typeof data.accessRestriction !== 'string') return next(new HttpError(400, 'accessRestriction is required')); + if (typeof data.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction is required')); if (typeof data.oauthProxy !== 'boolean') return next(new HttpError(400, 'oauthProxy must be a boolean')); - debug('Configuring app id:%s location:%s bindings:%j accessRestriction:%s oauthProxy:%s', req.params.id, data.location, data.portBindings, data.accessRestriction, data.oauthProxy); + debug('Configuring app id:%s location:%s bindings:%j accessRestriction:%j oauthProxy:%s', req.params.id, data.location, data.portBindings, data.accessRestriction, data.oauthProxy); apps.configure(req.params.id, data.location, data.portBindings || null, data.accessRestriction, data.oauthProxy, function (error) { if (error && error.reason === AppsError.ALREADY_EXISTS) return next(new HttpError(409, error.message)); @@ -253,13 +253,12 @@ function updateApp(req, res, next) { if (!data) return next(new HttpError(400, 'Cannot parse data field')); if (!data.manifest || typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest is required')); if (('portBindings' in data) && typeof data.portBindings !== 'object') return next(new HttpError(400, 'portBindings must be an object')); - if (data.accessRestriction !== null && typeof data.accessRestriction !== 'string') return next(new HttpError(400, 'accessRestriction is required')); if ('icon' in data && typeof data.icon !== 'string') return next(new HttpError(400, 'icon is not a string')); if ('force' in data && typeof data.force !== 'boolean') return next(new HttpError(400, 'force must be a boolean')); - debug('Update app id:%s to manifest:%j with portBindings:%j accessRestriction:%s', req.params.id, data.manifest, data.portBindings, data.accessRestriction); + debug('Update app id:%s to manifest:%j with portBindings:%j', req.params.id, data.manifest, data.portBindings); - apps.update(req.params.id, data.force || false, data.manifest, data.portBindings, data.accessRestriction, data.icon, function (error) { + apps.update(req.params.id, data.force || false, data.manifest, data.portBindings, data.icon, function (error) { if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app')); if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.BAD_STATE) return next(new HttpError(409, error.message));