diff --git a/src/apps.js b/src/apps.js index eab53272c..a7fd45f39 100644 --- a/src/apps.js +++ b/src/apps.js @@ -118,6 +118,7 @@ AppsError.PORT_RESERVED = 'Port Reserved'; AppsError.PORT_CONFLICT = 'Port Conflict'; AppsError.BILLING_REQUIRED = 'Billing Required'; AppsError.ACCESS_DENIED = 'Access denied'; +AppsError.USER_REQUIRED = 'User required'; // Hostname validation comes from RFC 1123 (section 2.1) // Domain name validation comes from RFC 2181 (Name syntax) @@ -319,6 +320,10 @@ function install(appId, appStoreId, manifest, location, portBindings, accessRest error = validateAccessRestriction(accessRestriction); if (error) return callback(new AppsError(AppsError.BAD_FIELD, error.message)); + // singleUser mode requires accessRestriction to contain exactly one user + if (manifest.singleUser && accessRestriction === null) return callback(new AppsError(AppsError.USER_REQUIRED)); + if (manifest.singleUser && accessRestriction.users.length !== 1) return callback(new AppsError(AppsError.USER_REQUIRED)); + if (icon) { if (!validator.isBase64(icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64')); diff --git a/src/routes/apps.js b/src/routes/apps.js index 3a28123b2..937f4d076 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -129,6 +129,7 @@ function installApp(req, res, next) { if (error && error.reason === AppsError.PORT_CONFLICT) return next(new HttpError(409, 'Port ' + error.message + ' is already in use.')); if (error && error.reason === AppsError.BAD_FIELD) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.BILLING_REQUIRED) return next(new HttpError(402, 'Billing required')); + if (error && error.reason === AppsError.USER_REQUIRED) return next(new HttpError(400, 'accessRestriction must specify one user')); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(202, { id: appId } )); diff --git a/src/routes/test/apps-test.js b/src/routes/test/apps-test.js index e934d2641..2f1751b4a 100644 --- a/src/routes/test/apps-test.js +++ b/src/routes/test/apps-test.js @@ -49,8 +49,14 @@ var APP_STORE_ID = 'test', APP_ID; var APP_LOCATION = 'appslocation'; var APP_LOCATION_2 = 'appslocationtwo'; var APP_LOCATION_NEW = 'appslocationnew'; + var APP_MANIFEST = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8')); APP_MANIFEST.dockerImage = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG; + +var APP_MANIFEST_1 = JSON.parse(fs.readFileSync(__dirname + '/../../../../test-app/CloudronManifest.json', 'utf8')); +APP_MANIFEST_1.dockerImage = TEST_IMAGE_REPO + ':' + TEST_IMAGE_TAG; +APP_MANIFEST_1.singleUser = true; + var USERNAME = 'admin', PASSWORD = 'password', EMAIL ='admin@me.com'; var USERNAME_1 = 'user', PASSWORD_1 = 'password', EMAIL_1 ='user@me.com'; var token = null; // authentication token @@ -295,6 +301,28 @@ describe('App API', function () { }); }); + it('app install fails - accessRestriction no users not allowed', function (done) { + request.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null, oauthProxy: false }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + expect(res.body.message).to.eql('accessRestriction must specify one user'); + done(err); + }); + }); + + it('app install fails - accessRestriction too many users not allowed', function (done) { + request.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: { users: [ 'one', 'two' ] }, oauthProxy: false }) + .end(function (err, res) { + expect(res.statusCode).to.equal(400); + expect(res.body.message).to.eql('accessRestriction must specify one user'); + done(err); + }); + }); + it('app install fails - oauthProxy is required', function (done) { request.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token })