diff --git a/src/appstore.js b/src/appstore.js index 55851158e..fea0b28e7 100644 --- a/src/appstore.js +++ b/src/appstore.js @@ -310,7 +310,7 @@ async function registerCloudron(data) { // app could already have been installed if we deleted the cloudron.io record and user re-registers for (const app of await apps.list()) { - await purchaseApp({ appId: app.id, appstoreId: app.appStoreId, manifestId: app.manifest.id || 'customapp' }); + await safe(purchaseApp({ appId: app.id, appstoreId: app.appStoreId, manifestId: app.manifest.id || 'customapp' }), { debug }); } } diff --git a/src/groups.js b/src/groups.js index 770617155..e5dc5428d 100644 --- a/src/groups.js +++ b/src/groups.js @@ -83,7 +83,7 @@ async function add(group, auditSource) { await eventlog.add(eventlog.ACTION_GROUP_ADD, auditSource, { id, name, source }); - return { id, name, source }; + return { id, name, source, appIds: [] }; } async function del(group, auditSource) { @@ -101,12 +101,21 @@ async function del(group, auditSource) { await eventlog.add(eventlog.ACTION_GROUP_REMOVE, auditSource, { group }); } +async function postProcess(group) { + assert.strictEqual(typeof group, 'object'); + + const results = await database.query('SELECT id FROM apps WHERE JSON_CONTAINS(accessRestrictionJson, ?, "$.groups")', [ `"${group.id}"` ]); + group.appIds = results.map(r => r.id); +} + async function get(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query(`SELECT ${GROUPS_FIELDS} FROM userGroups WHERE id = ? ORDER BY name`, [ id ]); if (result.length === 0) return null; + await postProcess(result[0]); + return result[0]; } @@ -116,16 +125,11 @@ async function getByName(name) { const result = await database.query(`SELECT ${GROUPS_FIELDS} FROM userGroups WHERE name = ?`, [ name ]); if (result.length === 0) return null; + await postProcess(result[0]); + return result[0]; } -async function getAppIds(groupId) { - assert.strictEqual(typeof groupId, 'string'); - - const results = await database.query('SELECT id FROM apps WHERE JSON_CONTAINS(accessRestrictionJson, ?, "$.groups")', [ `"${groupId}"` ]); - return results.map(r => r.id); -} - async function getWithMembers(id) { assert.strictEqual(typeof id, 'string'); @@ -138,7 +142,8 @@ async function getWithMembers(id) { const result = results[0]; result.userIds = result.userIds ? result.userIds.split(',') : [ ]; - result.appIds = await getAppIds(result.id); + + await postProcess(result); return result; } @@ -147,7 +152,7 @@ async function list() { const results = await database.query('SELECT ' + GROUPS_FIELDS + ' FROM userGroups ORDER BY name'); for (const r of results) { - r.appIds = await getAppIds(r.id); + await postProcess(r); } return results; @@ -161,7 +166,7 @@ async function listWithMembers() { results.forEach(function (result) { result.userIds = result.userIds ? result.userIds.split(',') : [ ]; }); for (const r of results) { - r.appIds = await getAppIds(r.id); + await postProcess(r); } return results; diff --git a/src/routes/test/common.js b/src/routes/test/common.js index e349f8bf3..8b8c741e3 100644 --- a/src/routes/test/common.js +++ b/src/routes/test/common.js @@ -1,6 +1,7 @@ 'use strict'; -const appstore = require('../../appstore.js'), +const apps = require('../../apps.js'), + appstore = require('../../appstore.js'), debug = require('debug')('box:test/common'), constants = require('../../constants.js'), database = require('../../database.js'), @@ -17,6 +18,34 @@ const appstore = require('../../appstore.js'), timers = require('timers/promises'), tokens = require('../../tokens.js'); +const manifest = { + 'id': 'io.cloudron.test', + 'author': 'The Presidents Of the United States Of America', + 'title': 'test title', + 'description': 'test description', + 'tagline': 'test rocks', + 'website': 'http://test.cloudron.io', + 'contactEmail': 'test@cloudron.io', + 'version': '0.1.0', + 'manifestVersion': 2, + 'dockerImage': 'cloudron/test:25.2.0', + 'healthCheckPath': '/', + 'httpPort': 7777, + 'tcpPorts': { + 'ECHO_SERVER_PORT': { + 'title': 'Echo Server Port', + 'description': 'Echo server', + 'containerPort': 7778 + } + }, + 'addons': { + 'oauth': { }, + 'redis': { }, + 'mysql': { }, + 'postgresql': { } + } +}; + exports = module.exports = { setup, setupServer, @@ -50,6 +79,25 @@ exports = module.exports = { token: null }, + app: { + id: 'appid', + appStoreId: 'appStoreId', + installationState: apps.ISTATE_PENDING_INSTALL, + runState: 'running', + subdomain: 'app', + domain: 'test.example.com', + fqdn: 'app.test.example.com', + manifest, + containerId: 'someid', + portBindings: {}, + accessRestriction: null, + memoryLimit: 0, + mailboxDomain: 'test.example.com', + secondaryDomains: [], + redirectDomains: [], + aliasDomains: [] + }, + mockApiServerOrigin: 'http://localhost:6060', dashboardDomain: 'test.example.com', dashboardFqdn: 'my.test.example.com', @@ -114,6 +162,9 @@ async function setup() { const token2 = await tokens.add({ identifier: user.id, clientId: tokens.ID_WEBADMIN, expires: Date.now() + (60 * 60 * 1000), name: 'fromtest' }); user.token = token2.accessToken; + // create app object + await apps.add(exports.app.id, exports.app.appStoreId, exports.app.manifest, exports.app.subdomain, exports.app.domain, exports.app.portBindings, exports.app); + await settings._set(settings.APPSTORE_API_TOKEN_KEY, exports.appstoreToken); // appstore token debug('Setup complete'); diff --git a/src/routes/test/groups-test.js b/src/routes/test/groups-test.js index ab3553335..e3e72e485 100644 --- a/src/routes/test/groups-test.js +++ b/src/routes/test/groups-test.js @@ -14,7 +14,7 @@ const GROUP_NAME = 'externals'; let group0Object, group1Object; describe('Groups API', function () { - const { setup, cleanup, serverUrl, owner, user } = common; + const { setup, cleanup, serverUrl, owner, user, app } = common; before(setup); after(cleanup); @@ -176,6 +176,31 @@ describe('Groups API', function () { expect(response.statusCode).to.equal(200); }); + it('cannot set unknown app access', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/groups/${group1Object.id}/apps`) + .query({ access_token: owner.token }) + .send({ appIds: ['app-id' ] }) + .ok(() => true); + + expect(response.statusCode).to.equal(200); // bad appId is just ignored + }); + + it('can set app access', async function () { + const response = await superagent.put(`${serverUrl}/api/v1/groups/${group1Object.id}/apps`) + .query({ access_token: owner.token }) + .send({ appIds: [app.id] }); + + expect(response.statusCode).to.equal(200); + }); + + it('did set app access', async function () { + const response = await superagent.get(`${serverUrl}/api/v1/groups`) + .query({ access_token: owner.token }); + + const group1 = response.body.groups.filter(g => g.id === group1Object.id)[0]; + expect(group1.appIds).to.eql([app.id]); + }); + it('remove user from group', async function () { const response = await superagent.put(`${serverUrl}/api/v1/users/${user.id}/groups`) .query({ access_token: owner.token }) diff --git a/src/test/groups-test.js b/src/test/groups-test.js index 6ce3ed52f..bdff692f4 100644 --- a/src/test/groups-test.js +++ b/src/test/groups-test.js @@ -13,7 +13,7 @@ const BoxError = require('../boxerror.js'), safe = require('safetydance'); describe('Groups', function () { - const { setup, cleanup, admin, user, auditSource } = common; + const { setup, cleanup, admin, user, auditSource, app } = common; before(setup); after(cleanup); @@ -196,6 +196,63 @@ describe('Groups', function () { }); }); + describe('app access', function () { + let groupObject; + + before(async function () { + const [error, result] = await safe(groups.add({ name: 'kootam' }, auditSource)); + expect(error).to.be(null); + groupObject = result; + }); + + it('has no app access', async function () { + expect(groupObject.appIds).to.eql([]); + + const g1 = await groups.get(groupObject.id); + expect(g1.appIds).to.eql([]); + + const g2 = await groups.getByName(groupObject.name); + expect(g2.appIds).to.eql([]); + + const g3 = await groups.getWithMembers(groupObject.id); + expect(g3.appIds).to.eql([]); + }); + + it('set app access', async function () { + await groups.setAllowedApps(groupObject, [ app.id ], auditSource); + + const g1 = await groups.get(groupObject.id); + expect(g1.appIds).to.eql([ app.id ]); + + const g2 = await groups.getByName(groupObject.name); + expect(g2.appIds).to.eql([ app.id ]); + + const g3 = await groups.getWithMembers(groupObject.id); + expect(g3.appIds).to.eql([ app.id ]); + + const allGroups = await groups.listWithMembers(); + const g4 = allGroups.filter(g => g.id === groupObject.id)[0]; + expect(g4.appIds).to.eql([ app.id ]); + }); + + it('cleared app access', async function () { + await groups.setAllowedApps(groupObject, [ ], auditSource); + + const g1 = await groups.get(groupObject.id); + expect(g1.appIds).to.eql([ ]); + + const g2 = await groups.getByName(groupObject.name); + expect(g2.appIds).to.eql([ ]); + + const g3 = await groups.getWithMembers(groupObject.id); + expect(g3.appIds).to.eql([ ]); + + const allGroups = await groups.listWithMembers(); + const g4 = allGroups.filter(g => g.id === groupObject.id)[0]; + expect(g4.appIds).to.eql([ ]); + }); + }); + describe('ldap group', function () { let ldapGroup;