From 4852ecf96a065288132d44c61f9c523ab0ac89c4 Mon Sep 17 00:00:00 2001 From: Michael Drury Date: Wed, 11 Nov 2020 17:34:15 +0000 Subject: [PATCH] WIP - this is working towards the permissions system but stopping here for the night, this is currently not functional. --- .../server/src/api/controllers/accesslevel.js | 11 +- packages/server/src/api/controllers/user.js | 10 +- packages/server/src/api/routes/accesslevel.js | 18 ++-- packages/server/src/api/routes/analytics.js | 2 +- packages/server/src/api/routes/apikeys.js | 2 +- packages/server/src/api/routes/application.js | 2 +- packages/server/src/api/routes/automation.js | 8 +- packages/server/src/api/routes/component.js | 2 +- packages/server/src/api/routes/deploy.js | 2 +- packages/server/src/api/routes/pages.js | 2 +- packages/server/src/api/routes/row.js | 19 ++-- packages/server/src/api/routes/screen.js | 2 +- packages/server/src/api/routes/static.js | 2 +- packages/server/src/api/routes/table.js | 8 +- packages/server/src/api/routes/templates.js | 2 +- .../src/api/routes/tests/accesslevel.spec.js | 16 +-- .../src/api/routes/tests/couchTestUtils.js | 2 +- .../server/src/api/routes/tests/user.spec.js | 2 +- packages/server/src/api/routes/user.js | 32 ++++-- packages/server/src/api/routes/view.js | 8 +- packages/server/src/api/routes/webhook.js | 8 +- .../src/automations/steps/createUser.js | 4 +- .../server/src/middleware/authenticated.js | 14 +-- packages/server/src/middleware/authorized.js | 50 +++------ packages/server/src/utilities/accessLevels.js | 36 ------- .../src/utilities/builder/setBuilderToken.js | 2 +- packages/server/src/utilities/permissions.js | 2 +- .../src/utilities/security/accessLevels.js | 44 ++++++++ .../src/utilities/security/permissions.js | 101 ++++++++++++++++++ 29 files changed, 263 insertions(+), 150 deletions(-) delete mode 100644 packages/server/src/utilities/accessLevels.js create mode 100644 packages/server/src/utilities/security/accessLevels.js create mode 100644 packages/server/src/utilities/security/permissions.js diff --git a/packages/server/src/api/controllers/accesslevel.js b/packages/server/src/api/controllers/accesslevel.js index 143c633e42..8c525ba52a 100644 --- a/packages/server/src/api/controllers/accesslevel.js +++ b/packages/server/src/api/controllers/accesslevel.js @@ -2,9 +2,8 @@ const CouchDB = require("../../db") const { generateAdminPermissions, generatePowerUserPermissions, - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, -} = require("../../utilities/accessLevels") + BUILTIN_LEVELS, +} = require("../../utilities/security/accessLevels") const { generateAccessLevelID, getAccessLevelParams, @@ -21,13 +20,11 @@ exports.fetch = async function(ctx) { const staticAccessLevels = [ { - _id: ADMIN_LEVEL_ID, - name: "Admin", + ...BUILTIN_LEVELS.admin, permissions: await generateAdminPermissions(ctx.user.appId), }, { - _id: POWERUSER_LEVEL_ID, - name: "Power User", + ...BUILTIN_LEVELS.power, permissions: await generatePowerUserPermissions(ctx.user.appId), }, ] diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index 5e4f963f5b..7d460c1db6 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -2,9 +2,8 @@ const CouchDB = require("../../db") const bcrypt = require("../../utilities/bcrypt") const { generateUserID, getUserParams } = require("../../db/utils") const { - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, -} = require("../../utilities/accessLevels") + BUILTIN_LEVELS_IDS, +} = require("../../utilities/security/accessLevels") exports.fetch = async function(ctx) { const database = new CouchDB(ctx.user.appId) @@ -89,10 +88,7 @@ exports.find = async function(ctx) { const checkAccessLevel = async (db, accessLevelId) => { if (!accessLevelId) return - if ( - accessLevelId === POWERUSER_LEVEL_ID || - accessLevelId === ADMIN_LEVEL_ID - ) { + if (BUILTIN_LEVELS_IDS.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/api/routes/accesslevel.js b/packages/server/src/api/routes/accesslevel.js index af1ff80ec6..f8bb70da63 100644 --- a/packages/server/src/api/routes/accesslevel.js +++ b/packages/server/src/api/routes/accesslevel.js @@ -1,14 +1,20 @@ const Router = require("@koa/router") const controller = require("../controllers/accesslevel") +const authorized = require("../../middleware/authorized") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() router - .post("/api/accesslevels", controller.create) - .put("/api/accesslevels", controller.update) - .get("/api/accesslevels", controller.fetch) - .get("/api/accesslevels/:levelId", controller.find) - .delete("/api/accesslevels/:levelId/:rev", controller.destroy) - .patch("/api/accesslevels/:levelId", controller.patch) + .post("/api/accesslevels", authorized(BUILDER), controller.create) + .put("/api/accesslevels", authorized(BUILDER), controller.update) + .get("/api/accesslevels", authorized(BUILDER), controller.fetch) + .get("/api/accesslevels/:levelId", authorized(BUILDER), controller.find) + .delete( + "/api/accesslevels/:levelId/:rev", + authorized(BUILDER), + controller.destroy + ) + .patch("/api/accesslevels/:levelId", authorized(BUILDER), controller.patch) module.exports = router diff --git a/packages/server/src/api/routes/analytics.js b/packages/server/src/api/routes/analytics.js index 626e3c2994..0d5e38c34d 100644 --- a/packages/server/src/api/routes/analytics.js +++ b/packages/server/src/api/routes/analytics.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") const controller = require("../controllers/analytics") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/apikeys.js b/packages/server/src/api/routes/apikeys.js index bec9ab677c..d6d0edeac0 100644 --- a/packages/server/src/api/routes/apikeys.js +++ b/packages/server/src/api/routes/apikeys.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/apikeys") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/application.js b/packages/server/src/api/routes/application.js index aeb815d38c..e3b4ddf6cf 100644 --- a/packages/server/src/api/routes/application.js +++ b/packages/server/src/api/routes/application.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/application") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/automation.js b/packages/server/src/api/routes/automation.js index 3ac7937da2..8644c75787 100644 --- a/packages/server/src/api/routes/automation.js +++ b/packages/server/src/api/routes/automation.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const controller = require("../controllers/automation") const authorized = require("../../middleware/authorized") const joiValidator = require("../../middleware/joi-validator") -const { BUILDER, EXECUTE_AUTOMATION } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const Joi = require("joi") const router = Router() @@ -75,7 +79,7 @@ router ) .post( "/api/automations/:id/trigger", - authorized(EXECUTE_AUTOMATION), + authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE), controller.trigger ) .delete("/api/automations/:id/:rev", authorized(BUILDER), controller.destroy) diff --git a/packages/server/src/api/routes/component.js b/packages/server/src/api/routes/component.js index 8fbe7ac41a..e9db3bee76 100644 --- a/packages/server/src/api/routes/component.js +++ b/packages/server/src/api/routes/component.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/component") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/deploy.js b/packages/server/src/api/routes/deploy.js index 4f7aa9b33b..d8667c6fc1 100644 --- a/packages/server/src/api/routes/deploy.js +++ b/packages/server/src/api/routes/deploy.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/deploy") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/pages.js b/packages/server/src/api/routes/pages.js index 1ec01dc780..43fb0e764c 100644 --- a/packages/server/src/api/routes/pages.js +++ b/packages/server/src/api/routes/pages.js @@ -1,6 +1,6 @@ const Router = require("@koa/router") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const controller = require("../controllers/page") const router = Router() diff --git a/packages/server/src/api/routes/row.js b/packages/server/src/api/routes/row.js index ae5ae772a6..4409f7f27b 100644 --- a/packages/server/src/api/routes/row.js +++ b/packages/server/src/api/routes/row.js @@ -2,46 +2,49 @@ const Router = require("@koa/router") const rowController = require("../controllers/row") const authorized = require("../../middleware/authorized") const usage = require("../../middleware/usageQuota") -const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels") +const { + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const router = Router() router .get( "/api/:tableId/:rowId/enrich", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.fetchEnrichedRow ) .get( "/api/:tableId/rows", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.fetchTableRows ) .get( "/api/:tableId/rows/:rowId", - authorized(READ_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), rowController.find ) .post("/api/rows/search", rowController.search) .post( "/api/:tableId/rows", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), usage, rowController.save ) .patch( "/api/:tableId/rows/:id", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), rowController.patch ) .post( "/api/:tableId/rows/validate", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), rowController.validate ) .delete( "/api/:tableId/rows/:rowId/:revId", - authorized(WRITE_TABLE, ctx => ctx.params.tableId), + authorized(PermissionTypes.TABLE, PermissionLevels.WRITE), usage, rowController.destroy ) diff --git a/packages/server/src/api/routes/screen.js b/packages/server/src/api/routes/screen.js index 407bbd1a94..1804bec27e 100644 --- a/packages/server/src/api/routes/screen.js +++ b/packages/server/src/api/routes/screen.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/screen") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const joiValidator = require("../../middleware/joi-validator") const Joi = require("joi") diff --git a/packages/server/src/api/routes/static.js b/packages/server/src/api/routes/static.js index 5c33900eca..a519a63781 100644 --- a/packages/server/src/api/routes/static.js +++ b/packages/server/src/api/routes/static.js @@ -3,7 +3,7 @@ const controller = require("../controllers/static") const { budibaseTempDir } = require("../../utilities/budibaseDir") const env = require("../../environment") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() diff --git a/packages/server/src/api/routes/table.js b/packages/server/src/api/routes/table.js index 40bfa9326f..ef0eb7caec 100644 --- a/packages/server/src/api/routes/table.js +++ b/packages/server/src/api/routes/table.js @@ -1,7 +1,11 @@ const Router = require("@koa/router") const tableController = require("../controllers/table") const authorized = require("../../middleware/authorized") -const { BUILDER, READ_TABLE } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const router = Router() @@ -9,7 +13,7 @@ router .get("/api/tables", authorized(BUILDER), tableController.fetch) .get( "/api/tables/:id", - authorized(READ_TABLE, ctx => ctx.params.id), + authorized(PermissionTypes.TABLE, PermissionLevels.READ), tableController.find ) .post("/api/tables", authorized(BUILDER), tableController.save) diff --git a/packages/server/src/api/routes/templates.js b/packages/server/src/api/routes/templates.js index 3e481610ce..05882a22ea 100644 --- a/packages/server/src/api/routes/templates.js +++ b/packages/server/src/api/routes/templates.js @@ -1,7 +1,7 @@ const Router = require("@koa/router") const controller = require("../controllers/templates") const authorized = require("../../middleware/authorized") -const { BUILDER } = require("../../utilities/accessLevels") +const { BUILDER } = require("../../utilities/security/permissions") const router = Router() diff --git a/packages/server/src/api/routes/tests/accesslevel.spec.js b/packages/server/src/api/routes/tests/accesslevel.spec.js index 3362cbd713..2fc4b54e20 100644 --- a/packages/server/src/api/routes/tests/accesslevel.spec.js +++ b/packages/server/src/api/routes/tests/accesslevel.spec.js @@ -8,11 +8,11 @@ const { const { generateAdminPermissions, generatePowerUserPermissions, - POWERUSER_LEVEL_ID, - ADMIN_LEVEL_ID, + BUILTIN_LEVELS, READ_TABLE, WRITE_TABLE, -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") +const { BUILTIN_PERMISSION_NAMES } = require("../../../utilities/security/permissions") describe("/accesslevels", () => { let server @@ -59,7 +59,7 @@ describe("/accesslevels", () => { it("should list custom levels, plus 2 default levels", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -74,11 +74,11 @@ describe("/accesslevels", () => { expect(res.body.length).toBe(3) - const adminLevel = res.body.find(r => r._id === ADMIN_LEVEL_ID) + const adminLevel = res.body.find(r => r._id === BUILTIN_LEVELS.admin._id) expect(adminLevel).toBeDefined() expect(adminLevel.permissions).toEqual(await generateAdminPermissions(appId)) - const powerUserLevel = res.body.find(r => r._id === POWERUSER_LEVEL_ID) + const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVELS.power._id) expect(powerUserLevel).toBeDefined() expect(powerUserLevel.permissions).toEqual(await generatePowerUserPermissions(appId)) @@ -92,7 +92,7 @@ describe("/accesslevels", () => { it("should delete custom access level", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE } ] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) @@ -115,7 +115,7 @@ describe("/accesslevels", () => { it("should add given permissions", async () => { const createRes = await request .post(`/api/accesslevels`) - .send({ name: "user", permissions: [ { itemId: table._id, name: READ_TABLE }] }) + .send({ name: "user", permissions: [BUILTIN_PERMISSION_NAMES.READ_ONLY] }) .set(defaultHeaders(appId)) .expect('Content-Type', /json/) .expect(200) diff --git a/packages/server/src/api/routes/tests/couchTestUtils.js b/packages/server/src/api/routes/tests/couchTestUtils.js index 02a75d77fd..bbb2da903e 100644 --- a/packages/server/src/api/routes/tests/couchTestUtils.js +++ b/packages/server/src/api/routes/tests/couchTestUtils.js @@ -5,7 +5,7 @@ const { ANON_LEVEL_ID, BUILDER_LEVEL_ID, generateAdminPermissions, -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") const packageJson = require("../../../../package") const jwt = require("jsonwebtoken") const env = require("../../../environment") diff --git a/packages/server/src/api/routes/tests/user.spec.js b/packages/server/src/api/routes/tests/user.spec.js index a569902bae..d0c12f2ea1 100644 --- a/packages/server/src/api/routes/tests/user.spec.js +++ b/packages/server/src/api/routes/tests/user.spec.js @@ -9,7 +9,7 @@ const { POWERUSER_LEVEL_ID, LIST_USERS, USER_MANAGEMENT -} = require("../../../utilities/accessLevels") +} = require("../../../utilities/security/accessLevels") describe("/users", () => { let request diff --git a/packages/server/src/api/routes/user.js b/packages/server/src/api/routes/user.js index 5289439e41..9394d842bd 100644 --- a/packages/server/src/api/routes/user.js +++ b/packages/server/src/api/routes/user.js @@ -1,19 +1,39 @@ const Router = require("@koa/router") const controller = require("../controllers/user") const authorized = require("../../middleware/authorized") -const { USER_MANAGEMENT, LIST_USERS } = require("../../utilities/accessLevels") +const { + PermissionLevels, + PermissionTypes, +} = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() router - .get("/api/users", authorized(LIST_USERS), controller.fetch) - .get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find) - .put("/api/users/", authorized(USER_MANAGEMENT), controller.update) - .post("/api/users", authorized(USER_MANAGEMENT), usage, controller.create) + .get( + "/api/users", + authorized(PermissionTypes.USER, PermissionLevels.READ), + controller.fetch + ) + .get( + "/api/users/:username", + authorized(PermissionTypes.USER, PermissionLevels.READ), + controller.find + ) + .put( + "/api/users/", + authorized(PermissionTypes.USER, PermissionLevels.WRITE), + controller.update + ) + .post( + "/api/users", + authorized(PermissionTypes.USER, PermissionLevels.WRITE), + usage, + controller.create + ) .delete( "/api/users/:username", - authorized(USER_MANAGEMENT), + authorized(PermissionTypes.USER, PermissionLevels.WRITE), usage, controller.destroy ) diff --git a/packages/server/src/api/routes/view.js b/packages/server/src/api/routes/view.js index 3657a9e829..17277b346c 100644 --- a/packages/server/src/api/routes/view.js +++ b/packages/server/src/api/routes/view.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const viewController = require("../controllers/view") const rowController = require("../controllers/row") const authorized = require("../../middleware/authorized") -const { BUILDER, READ_VIEW } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionTypes, + PermissionLevels, +} = require("../../utilities/security/permissions") const usage = require("../../middleware/usageQuota") const router = Router() @@ -10,7 +14,7 @@ const router = Router() router .get( "/api/views/:viewName", - authorized(READ_VIEW, ctx => ctx.params.viewName), + authorized(PermissionTypes.VIEW, PermissionLevels.READ), rowController.fetchView ) .get("/api/views", authorized(BUILDER), viewController.fetch) diff --git a/packages/server/src/api/routes/webhook.js b/packages/server/src/api/routes/webhook.js index a7072904ed..fdcf14e490 100644 --- a/packages/server/src/api/routes/webhook.js +++ b/packages/server/src/api/routes/webhook.js @@ -2,7 +2,11 @@ const Router = require("@koa/router") const controller = require("../controllers/webhook") const authorized = require("../../middleware/authorized") const joiValidator = require("../../middleware/joi-validator") -const { BUILDER, EXECUTE_WEBHOOK } = require("../../utilities/accessLevels") +const { + BUILDER, + PermissionTypes, + PermissionLevels, +} = require("../../utilities/security/permissions") const Joi = require("joi") const router = Router() @@ -38,7 +42,7 @@ router ) .post( "/api/webhooks/trigger/:instance/:id", - authorized(EXECUTE_WEBHOOK), + authorized(PermissionTypes.WEBHOOK, PermissionLevels.EXECUTE), controller.trigger ) diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index 07d9f05316..d29144e01d 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -1,4 +1,4 @@ -const accessLevels = require("../../utilities/accessLevels") +const accessLevels = require("../../utilities/security/accessLevels") const userController = require("../../api/controllers/user") const env = require("../../environment") const usage = require("../../utilities/usageQuota") @@ -28,7 +28,7 @@ module.exports.definition = { accessLevelId: { type: "string", title: "Access Level", - enum: accessLevels.ACCESS_LEVELS, + enum: accessLevels.BUILTIN_LEVELS, pretty: Object.values(accessLevels.PRETTY_ACCESS_LEVELS), }, }, diff --git a/packages/server/src/middleware/authenticated.js b/packages/server/src/middleware/authenticated.js index 529581f362..c10f1387cf 100644 --- a/packages/server/src/middleware/authenticated.js +++ b/packages/server/src/middleware/authenticated.js @@ -2,11 +2,8 @@ const jwt = require("jsonwebtoken") const STATUS_CODES = require("../utilities/statusCodes") const accessLevelController = require("../api/controllers/accesslevel") const { - ADMIN_LEVEL_ID, - POWERUSER_LEVEL_ID, - BUILDER_LEVEL_ID, - ANON_LEVEL_ID, -} = require("../utilities/accessLevels") + BUILTIN_LEVEL_IDS, +} = require("../utilities/security/accessLevels") const env = require("../environment") const { AuthTypes } = require("../constants") const { getAppId, getCookieName, setCookie } = require("../utilities") @@ -74,12 +71,7 @@ module.exports = async (ctx, next) => { * @param {*} accessLevelId - the id of the users access level */ const getAccessLevel = async (appId, accessLevelId) => { - if ( - accessLevelId === POWERUSER_LEVEL_ID || - accessLevelId === ADMIN_LEVEL_ID || - accessLevelId === BUILDER_LEVEL_ID || - accessLevelId === ANON_LEVEL_ID - ) { + if (BUILTIN_LEVEL_IDS.indexOf(accessLevelId) !== -1) { return { _id: accessLevelId, name: accessLevelId, diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index 34758903f3..234c7d82fd 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -1,17 +1,14 @@ -const { - adminPermissions, - ADMIN_LEVEL_ID, - POWERUSER_LEVEL_ID, - BUILDER_LEVEL_ID, - BUILDER, -} = require("../utilities/accessLevels") +const { BUILTIN_LEVELS } = require("../utilities/security/accessLevels") +const { PermissionTypes } = require("../utilities/security/permissions") const env = require("../environment") const { apiKeyTable } = require("../db/dynamoClient") const { AuthTypes } = require("../constants") +const ADMIN_PERMS = [BUILTIN_LEVELS.admin._id, BUILTIN_LEVELS.builder._id] + const LOCAL_PASS = new RegExp(["webhooks/trigger", "webhooks/schema"].join("|")) -module.exports = (permName, getItemId) => async (ctx, next) => { +module.exports = (permType, permLevel = null) => async (ctx, next) => { // webhooks can pass locally if (!env.CLOUD && LOCAL_PASS.test(ctx.request.url)) { return next() @@ -37,7 +34,7 @@ module.exports = (permName, getItemId) => async (ctx, next) => { } // don't expose builder endpoints in the cloud - if (env.CLOUD && permName === BUILDER) return + if (env.CLOUD && permType === PermissionTypes.BUILDER) return if (!ctx.auth.authenticated) { ctx.throw(403, "Session not authenticated") @@ -47,41 +44,18 @@ module.exports = (permName, getItemId) => async (ctx, next) => { ctx.throw(403, "User not found") } - if (ctx.user.accessLevel._id === ADMIN_LEVEL_ID) { + if (ADMIN_PERMS.indexOf(ctx.user.accessLevel._id) !== -1) { return next() } - if (ctx.user.accessLevel._id === BUILDER_LEVEL_ID) { - return next() - } - - if (permName === BUILDER) { + if (permType === PermissionTypes.BUILDER) { ctx.throw(403, "Not Authorized") return } - const permissionId = ({ name, itemId }) => name + (itemId ? `-${itemId}` : "") + // TODO: Replace the old permissions system here, check whether + // user has permission to use endpoint they are trying to access + return next() - const thisPermissionId = permissionId({ - name: permName, - itemId: getItemId && getItemId(ctx), - }) - - // power user has everything, except the admin specific perms - if ( - ctx.user.accessLevel._id === POWERUSER_LEVEL_ID && - !adminPermissions.map(permissionId).includes(thisPermissionId) - ) { - return next() - } - - if ( - ctx.user.accessLevel.permissions - .map(permissionId) - .includes(thisPermissionId) - ) { - return next() - } - - ctx.throw(403, "Not Authorized") + //ctx.throw(403, "Not Authorized") } diff --git a/packages/server/src/utilities/accessLevels.js b/packages/server/src/utilities/accessLevels.js deleted file mode 100644 index e38a7cf23f..0000000000 --- a/packages/server/src/utilities/accessLevels.js +++ /dev/null @@ -1,36 +0,0 @@ -// Permissions -module.exports.READ_TABLE = "read-table" -module.exports.WRITE_TABLE = "write-table" -module.exports.READ_VIEW = "read-view" -module.exports.EXECUTE_AUTOMATION = "execute-automation" -module.exports.EXECUTE_WEBHOOK = "execute-webhook" -module.exports.USER_MANAGEMENT = "user-management" -module.exports.BUILDER = "builder" -module.exports.LIST_USERS = "list-users" -// Access Level IDs -module.exports.ADMIN_LEVEL_ID = "ADMIN" -module.exports.POWERUSER_LEVEL_ID = "POWER_USER" -module.exports.BUILDER_LEVEL_ID = "BUILDER" -module.exports.ANON_LEVEL_ID = "ANON" -module.exports.ACCESS_LEVELS = [ - module.exports.ADMIN_LEVEL_ID, - module.exports.POWERUSER_LEVEL_ID, - module.exports.BUILDER_LEVEL_ID, - module.exports.ANON_LEVEL_ID, -] -module.exports.PRETTY_ACCESS_LEVELS = { - [module.exports.ADMIN_LEVEL_ID]: "Admin", - [module.exports.POWERUSER_LEVEL_ID]: "Power user", - [module.exports.BUILDER_LEVEL_ID]: "Builder", -} -module.exports.adminPermissions = [ - { - name: module.exports.USER_MANAGEMENT, - }, -] - -// to avoid circular dependencies this is included later, after exporting all enums -const permissions = require("./permissions") -module.exports.generateAdminPermissions = permissions.generateAdminPermissions -module.exports.generatePowerUserPermissions = - permissions.generatePowerUserPermissions diff --git a/packages/server/src/utilities/builder/setBuilderToken.js b/packages/server/src/utilities/builder/setBuilderToken.js index 8cf6c44379..56969f7d3a 100644 --- a/packages/server/src/utilities/builder/setBuilderToken.js +++ b/packages/server/src/utilities/builder/setBuilderToken.js @@ -1,4 +1,4 @@ -const { BUILDER_LEVEL_ID } = require("../accessLevels") +const { BUILDER_LEVEL_ID } = require("../security/accessLevels") const env = require("../../environment") const CouchDB = require("../../db") const jwt = require("jsonwebtoken") diff --git a/packages/server/src/utilities/permissions.js b/packages/server/src/utilities/permissions.js index e1513fd0fa..3a161f6405 100644 --- a/packages/server/src/utilities/permissions.js +++ b/packages/server/src/utilities/permissions.js @@ -1,7 +1,7 @@ const viewController = require("../api/controllers/view") const tableController = require("../api/controllers/table") const automationController = require("../api/controllers/automation") -const accessLevels = require("./accessLevels") +const accessLevels = require("./security/accessLevels") // this has been broken out to reduce risk of circular dependency from utilities, no enums defined here const generateAdminPermissions = async appId => [ diff --git a/packages/server/src/utilities/security/accessLevels.js b/packages/server/src/utilities/security/accessLevels.js new file mode 100644 index 0000000000..77a2955261 --- /dev/null +++ b/packages/server/src/utilities/security/accessLevels.js @@ -0,0 +1,44 @@ +const { DocumentTypes, SEPARATOR } = require("../../db/utils") + +function makeAccessLevelId(baseId) { + return `${DocumentTypes.ACCESS_LEVEL}${SEPARATOR}${baseId}` +} + +// Permissions +exports.READ_TABLE = "read-table" +exports.WRITE_TABLE = "write-table" +exports.READ_VIEW = "read-view" +exports.EXECUTE_AUTOMATION = "execute-automation" +exports.EXECUTE_WEBHOOK = "execute-webhook" +exports.USER_MANAGEMENT = "user-management" +exports.BUILDER = "builder" +exports.LIST_USERS = "list-users" +// Access Level IDs +exports.ADMIN_LEVEL_ID = "ADMIN" +exports.POWERUSER_LEVEL_ID = "POWER_USER" +exports.BUILDER_LEVEL_ID = "BUILDER" +exports.ANON_LEVEL_ID = "ANON" +exports.BUILTIN_LEVELS = { + admin: { _id: makeAccessLevelId("ADMIN"), name: "Admin" }, + power: { _id: makeAccessLevelId("POWER_USER"), name: "Power user" }, + builder: { _id: makeAccessLevelId("BUILDER"), name: "Builder" }, + anon: { _id: makeAccessLevelId("ANON"), name: "Anonymous" }, +} +exports.BUILTIN_LEVEL_IDS = Object.values(exports.BUILTIN_LEVELS).map( + level => level._id +) +exports.PRETTY_ACCESS_LEVELS = { + [exports.ADMIN_LEVEL_ID]: "Admin", + [exports.POWERUSER_LEVEL_ID]: "Power user", + [exports.BUILDER_LEVEL_ID]: "Builder", +} +exports.adminPermissions = [ + { + name: exports.USER_MANAGEMENT, + }, +] + +// to avoid circular dependencies this is included later, after exporting all enums +const permissions = require("../permissions") +exports.generateAdminPermissions = permissions.generateAdminPermissions +exports.generatePowerUserPermissions = permissions.generatePowerUserPermissions diff --git a/packages/server/src/utilities/security/permissions.js b/packages/server/src/utilities/security/permissions.js new file mode 100644 index 0000000000..7a230611c0 --- /dev/null +++ b/packages/server/src/utilities/security/permissions.js @@ -0,0 +1,101 @@ +const { flatten } = require("lodash") + +exports.READ_TABLE = "read-table" +exports.WRITE_TABLE = "write-table" +exports.READ_VIEW = "read-view" +exports.EXECUTE_AUTOMATION = "execute-automation" +exports.EXECUTE_WEBHOOK = "execute-webhook" +exports.USER_MANAGEMENT = "user-management" +exports.BUILDER = "builder" +exports.LIST_USERS = "list-users" + +const PermissionLevels = { + READ: "read", + WRITE: "write", + EXECUTE: "execute", + ADMIN: "admin", +} + +const PermissionTypes = { + TABLE: "table", + USER: "user", + AUTOMATION: "automation", + WEBHOOK: "webhook", + BUILDER: "builder", + VIEW: "view", +} + +function Permission(type, level) { + this.level = level + this.type = type +} + +/** + * Given the specified permission level for the user return the levels they are allowed to carry out. + * @param {string} userPermLevel The permission level of the user. + * @return {string[]} All the permission levels this user is allowed to carry out. + */ +function getAllowedLevels(userPermLevel) { + switch (userPermLevel) { + case PermissionLevels.READ: + return [PermissionLevels.READ] + case PermissionLevels.WRITE: + return [PermissionLevels.READ, PermissionLevels.WRITE] + case PermissionLevels.EXECUTE: + return [PermissionLevels.EXECUTE] + case PermissionLevels.ADMIN: + return [ + PermissionLevels.READ, + PermissionLevels.WRITE, + PermissionLevels.EXECUTE, + ] + default: + return [] + } +} + +// TODO: need to expand on this +exports.BUILTIN_PERMISSION_NAMES = { + READ_ONLY: "read_only", + WRITE: "write", +} + +exports.BUILTIN_PERMISSIONS = { + READ_ONLY: { + name: exports.BUILTIN_PERMISSION_NAMES.READ_ONLY, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.READ), + new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + ], + }, + WRITE: { + name: exports.BUILTIN_PERMISSION_NAMES.WRITE, + permissions: [ + new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE), + new Permission(PermissionTypes.VIEW, PermissionLevels.READ), + ], + }, +} + +exports.doesHavePermission = (permType, permLevel, userPermissionNames) => { + const builtins = Object.values(exports.BUILTIN_PERMISSIONS) + let permissions = flatten( + builtins + .filter(builtin => userPermissionNames.indexOf(builtin.name) !== -1) + .map(builtin => builtin.permissions) + ) + for (let permission of permissions) { + if ( + permission.type === permType && + getAllowedLevels(permission.level).indexOf(permLevel) !== -1 + ) { + return true + } + } + return false +} + +// utility as a lot of things need simply the builder permission +exports.BUILDER = PermissionTypes.BUILDER +exports.PermissionTypes = PermissionTypes +exports.PermissionLevels = PermissionLevels