From c947199558299b76bee6e6f2923d535c7a7aad89 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 14 May 2021 16:31:07 +0100 Subject: [PATCH] Adding administration roles API. --- packages/auth/src/db/index.js | 4 ++ packages/auth/src/db/utils.js | 29 +++++++++++++ packages/auth/src/security/roles.js | 43 ++++++++++++++++++- .../server/src/api/controllers/application.js | 9 +--- packages/server/src/api/controllers/role.js | 36 +--------------- packages/server/src/utilities/index.js | 23 +--------- .../worker/src/api/controllers/admin/roles.js | 24 +++++++++++ packages/worker/src/api/routes/admin/roles.js | 11 +++++ packages/worker/src/api/routes/admin/users.js | 1 + 9 files changed, 116 insertions(+), 64 deletions(-) create mode 100644 packages/worker/src/api/controllers/admin/roles.js create mode 100644 packages/worker/src/api/routes/admin/roles.js diff --git a/packages/auth/src/db/index.js b/packages/auth/src/db/index.js index f94fe4afea..163364dbf3 100644 --- a/packages/auth/src/db/index.js +++ b/packages/auth/src/db/index.js @@ -7,3 +7,7 @@ module.exports.setDB = pouch => { module.exports.getDB = dbName => { return new Pouch(dbName) } + +module.exports.getCouch = () => { + return Pouch +} diff --git a/packages/auth/src/db/utils.js b/packages/auth/src/db/utils.js index 87ff10bd46..ecb11824bd 100644 --- a/packages/auth/src/db/utils.js +++ b/packages/auth/src/db/utils.js @@ -1,5 +1,6 @@ const { newid } = require("../hashing") const Replication = require("./Replication") +const { getCouch } = require("./index") const UNICODE_MAX = "\ufff0" const SEPARATOR = "_" @@ -136,6 +137,34 @@ exports.getRoleParams = (roleId = null, otherProps = {}) => { return getDocParams(DocumentTypes.ROLE, roleId, otherProps) } +/** + * Lots of different points in the system need to find the full list of apps, this will + * enumerate the entire CouchDB cluster and get the list of databases (every app). + * NOTE: this operation is fine in self hosting, but cannot be used when hosting many + * different users/companies apps as there is no security around it - all apps are returned. + * @return {Promise} returns the app information document stored in each app database. + */ +exports.getAllApps = async (devApps = false) => { + const CouchDB = getCouch() + let allDbs = await CouchDB.allDbs() + const appDbNames = allDbs.filter(dbName => dbName.startsWith(exports.APP_PREFIX)) + const appPromises = appDbNames.map(db => new CouchDB(db).get(db)) + if (appPromises.length === 0) { + return [] + } else { + const response = await Promise.allSettled(appPromises) + const apps = response + .filter(result => result.status === "fulfilled") + .map(({ value }) => value) + return apps.filter(app => { + if (devApps) { + return app._id.startsWith(exports.APP_DEV_PREFIX) + } + return !app._id.startsWith(exports.APP_DEV_PREFIX) + }) + } +} + /** * Generates a new configuration ID. * @returns {string} The new configuration ID which the config doc can be stored under. diff --git a/packages/auth/src/security/roles.js b/packages/auth/src/security/roles.js index 93e1769cf1..d88be96b2c 100644 --- a/packages/auth/src/security/roles.js +++ b/packages/auth/src/security/roles.js @@ -1,7 +1,7 @@ const { getDB } = require("../db") const { cloneDeep } = require("lodash/fp") const { BUILTIN_PERMISSION_IDS, higherPermission } = require("./permissions") -const { generateRoleID, DocumentTypes, SEPARATOR } = require("../db/utils") +const { generateRoleID, getRoleParams, DocumentTypes, SEPARATOR } = require("../db/utils") const BUILTIN_IDS = { ADMIN: "ADMIN", @@ -11,6 +11,14 @@ const BUILTIN_IDS = { BUILDER: "BUILDER", } +// exclude internal roles like builder +const EXTERNAL_BUILTIN_ROLE_IDS = [ + BUILTIN_IDS.ADMIN, + BUILTIN_IDS.POWER, + BUILTIN_IDS.BASIC, + BUILTIN_IDS.PUBLIC, +] + function Role(id, name) { this._id = id this.name = name @@ -192,6 +200,39 @@ exports.getUserPermissions = async (appId, userRoleId) => { } } +/** + * Given an app ID this will retrieve all of the roles that are currently within that app. + * @param {string} appId The ID of the app to retrieve the roles from. + * @return {Promise} An array of the role objects that were found. + */ +exports.getAllRoles = async appId => { + const db = getDB(appId) + const body = await db.allDocs( + getRoleParams(null, { + include_docs: true, + }) + ) + let roles = body.rows.map(row => row.doc) + const builtinRoles = exports.getBuiltinRoles() + + // need to combine builtin with any DB record of them (for sake of permissions) + for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { + const builtinRole = builtinRoles[builtinRoleId] + const dbBuiltin = roles.filter( + dbRole => exports.getExternalRoleID(dbRole._id) === builtinRoleId + )[0] + if (dbBuiltin == null) { + roles.push(builtinRole) + } else { + // remove role and all back after combining with the builtin + roles = roles.filter(role => role._id !== dbBuiltin._id) + dbBuiltin._id = exports.getExternalRoleID(dbBuiltin._id) + roles.push(Object.assign(builtinRole, dbBuiltin)) + } + } + return roles +} + class AccessController { constructor(appId) { this.appId = appId diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index e9b047ce82..cdc3c59a52 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -121,15 +121,8 @@ async function createInstance(template) { } exports.fetch = async function (ctx) { - let apps = await getAllApps() - const isDev = ctx.query && ctx.query.status === AppStatus.DEV - apps = apps.filter(app => { - if (isDev) { - return app._id.startsWith(DocumentTypes.APP_DEV) - } - return !app._id.startsWith(DocumentTypes.APP_DEV) - }) + const apps = await getAllApps(isDev) // get the locks for all the dev apps if (isDev) { diff --git a/packages/server/src/api/controllers/role.js b/packages/server/src/api/controllers/role.js index a72c6ae55d..1ab368e5c5 100644 --- a/packages/server/src/api/controllers/role.js +++ b/packages/server/src/api/controllers/role.js @@ -1,11 +1,11 @@ const CouchDB = require("../../db") const { getBuiltinRoles, - BUILTIN_ROLE_IDS, Role, getRole, isBuiltin, getExternalRoleID, + getAllRoles, } = require("@budibase/auth/roles") const { generateRoleID, @@ -19,14 +19,6 @@ const UpdateRolesOptions = { REMOVED: "removed", } -// exclude internal roles like builder -const EXTERNAL_BUILTIN_ROLE_IDS = [ - BUILTIN_ROLE_IDS.ADMIN, - BUILTIN_ROLE_IDS.POWER, - BUILTIN_ROLE_IDS.BASIC, - BUILTIN_ROLE_IDS.PUBLIC, -] - async function updateRolesOnUserTable(db, roleId, updateOption) { const table = await db.get(InternalTables.USER_METADATA) const schema = table.schema @@ -51,31 +43,7 @@ async function updateRolesOnUserTable(db, roleId, updateOption) { } exports.fetch = async function (ctx) { - const db = new CouchDB(ctx.appId) - const body = await db.allDocs( - getRoleParams(null, { - include_docs: true, - }) - ) - let roles = body.rows.map(row => row.doc) - const builtinRoles = getBuiltinRoles() - - // need to combine builtin with any DB record of them (for sake of permissions) - for (let builtinRoleId of EXTERNAL_BUILTIN_ROLE_IDS) { - const builtinRole = builtinRoles[builtinRoleId] - const dbBuiltin = roles.filter( - dbRole => getExternalRoleID(dbRole._id) === builtinRoleId - )[0] - if (dbBuiltin == null) { - roles.push(builtinRole) - } else { - // remove role and all back after combining with the builtin - roles = roles.filter(role => role._id !== dbBuiltin._id) - dbBuiltin._id = getExternalRoleID(dbBuiltin._id) - roles.push(Object.assign(builtinRole, dbBuiltin)) - } - } - ctx.body = roles + ctx.body = await getAllRoles(ctx.appId) } exports.find = async function (ctx) { diff --git a/packages/server/src/utilities/index.js b/packages/server/src/utilities/index.js index ac3b668ad5..80852f8c04 100644 --- a/packages/server/src/utilities/index.js +++ b/packages/server/src/utilities/index.js @@ -2,33 +2,14 @@ const env = require("../environment") const { APP_PREFIX } = require("../db/utils") const CouchDB = require("../db") const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") +const { getAllApps } = require("@budibase/auth/db") const BB_CDN = "https://cdn.app.budi.live/assets" exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) exports.isDev = env.isDev - -/** - * Lots of different points in the app need to find the full list of apps, this will - * enumerate the entire CouchDB cluster and get the list of databases (every app). - * NOTE: this operation is fine in self hosting, but cannot be used when hosting many - * different users/companies apps as there is no security around it - all apps are returned. - * @return {Promise} returns the app information document stored in each app database. - */ -exports.getAllApps = async () => { - let allDbs = await CouchDB.allDbs() - const appDbNames = allDbs.filter(dbName => dbName.startsWith(APP_PREFIX)) - const appPromises = appDbNames.map(db => new CouchDB(db).get(db)) - if (appPromises.length === 0) { - return [] - } else { - const response = await Promise.allSettled(appPromises) - return response - .filter(result => result.status === "fulfilled") - .map(({ value }) => value) - } -} +exports.getAllApps = getAllApps /** * Makes sure that a URL has the correct number of slashes, while maintaining the diff --git a/packages/worker/src/api/controllers/admin/roles.js b/packages/worker/src/api/controllers/admin/roles.js new file mode 100644 index 0000000000..30c44a9b18 --- /dev/null +++ b/packages/worker/src/api/controllers/admin/roles.js @@ -0,0 +1,24 @@ +const { getAllRoles } = require("@budibase/auth/roles") +const { getAllApps } = require("@budibase/auth/db") + +exports.fetch = async ctx => { + // always use the dev apps as they'll be most up to date (true) + const apps = await getAllApps(true) + const promises = [] + for (let app of apps) { + promises.push(getAllRoles(app._id)) + } + const roles = await Promise.all(promises) + const response = {} + for (let app of apps) { + response[app._id] = roles.shift() + } + ctx.body = response +} + +exports.find = async ctx => { + const appId = ctx.params.appId + ctx.body = { + roles: await getAllRoles(appId) + } +} diff --git a/packages/worker/src/api/routes/admin/roles.js b/packages/worker/src/api/routes/admin/roles.js new file mode 100644 index 0000000000..8595662ac3 --- /dev/null +++ b/packages/worker/src/api/routes/admin/roles.js @@ -0,0 +1,11 @@ +const Router = require("@koa/router") +const controller = require("../../controllers/admin/roles") + +const router = Router() + + +router + .get("/api/admin/roles", controller.fetch) + .get("/api/admin/roles/:appId", controller.find) + +module.exports = router diff --git a/packages/worker/src/api/routes/admin/users.js b/packages/worker/src/api/routes/admin/users.js index b498fbe291..cac1d5af9c 100644 --- a/packages/worker/src/api/routes/admin/users.js +++ b/packages/worker/src/api/routes/admin/users.js @@ -45,6 +45,7 @@ router .post("/api/admin/users/init", controller.adminUser) .delete("/api/admin/users/:id", controller.destroy) .get("/api/admin/users/:id", controller.find) + .get("/api/admin/roles/:appId") .post("/api/admin/users/invite", buildInviteValidation(), controller.invite) .post( "/api/admin/users/invite/accept",