Adding administration roles API.

This commit is contained in:
mike12345567 2021-05-14 16:31:07 +01:00
parent cea82f9335
commit c947199558
9 changed files with 116 additions and 64 deletions

View File

@ -7,3 +7,7 @@ module.exports.setDB = pouch => {
module.exports.getDB = dbName => { module.exports.getDB = dbName => {
return new Pouch(dbName) return new Pouch(dbName)
} }
module.exports.getCouch = () => {
return Pouch
}

View File

@ -1,5 +1,6 @@
const { newid } = require("../hashing") const { newid } = require("../hashing")
const Replication = require("./Replication") const Replication = require("./Replication")
const { getCouch } = require("./index")
const UNICODE_MAX = "\ufff0" const UNICODE_MAX = "\ufff0"
const SEPARATOR = "_" const SEPARATOR = "_"
@ -136,6 +137,34 @@ exports.getRoleParams = (roleId = null, otherProps = {}) => {
return getDocParams(DocumentTypes.ROLE, roleId, 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<object[]>} 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. * Generates a new configuration ID.
* @returns {string} The new configuration ID which the config doc can be stored under. * @returns {string} The new configuration ID which the config doc can be stored under.

View File

@ -1,7 +1,7 @@
const { getDB } = require("../db") const { getDB } = require("../db")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { BUILTIN_PERMISSION_IDS, higherPermission } = require("./permissions") 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 = { const BUILTIN_IDS = {
ADMIN: "ADMIN", ADMIN: "ADMIN",
@ -11,6 +11,14 @@ const BUILTIN_IDS = {
BUILDER: "BUILDER", 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) { function Role(id, name) {
this._id = id this._id = id
this.name = name 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<object[]>} 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 { class AccessController {
constructor(appId) { constructor(appId) {
this.appId = appId this.appId = appId

View File

@ -121,15 +121,8 @@ async function createInstance(template) {
} }
exports.fetch = async function (ctx) { exports.fetch = async function (ctx) {
let apps = await getAllApps()
const isDev = ctx.query && ctx.query.status === AppStatus.DEV const isDev = ctx.query && ctx.query.status === AppStatus.DEV
apps = apps.filter(app => { const apps = await getAllApps(isDev)
if (isDev) {
return app._id.startsWith(DocumentTypes.APP_DEV)
}
return !app._id.startsWith(DocumentTypes.APP_DEV)
})
// get the locks for all the dev apps // get the locks for all the dev apps
if (isDev) { if (isDev) {

View File

@ -1,11 +1,11 @@
const CouchDB = require("../../db") const CouchDB = require("../../db")
const { const {
getBuiltinRoles, getBuiltinRoles,
BUILTIN_ROLE_IDS,
Role, Role,
getRole, getRole,
isBuiltin, isBuiltin,
getExternalRoleID, getExternalRoleID,
getAllRoles,
} = require("@budibase/auth/roles") } = require("@budibase/auth/roles")
const { const {
generateRoleID, generateRoleID,
@ -19,14 +19,6 @@ const UpdateRolesOptions = {
REMOVED: "removed", 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) { async function updateRolesOnUserTable(db, roleId, updateOption) {
const table = await db.get(InternalTables.USER_METADATA) const table = await db.get(InternalTables.USER_METADATA)
const schema = table.schema const schema = table.schema
@ -51,31 +43,7 @@ async function updateRolesOnUserTable(db, roleId, updateOption) {
} }
exports.fetch = async function (ctx) { exports.fetch = async function (ctx) {
const db = new CouchDB(ctx.appId) ctx.body = await getAllRoles(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
} }
exports.find = async function (ctx) { exports.find = async function (ctx) {

View File

@ -2,33 +2,14 @@ const env = require("../environment")
const { APP_PREFIX } = require("../db/utils") const { APP_PREFIX } = require("../db/utils")
const CouchDB = require("../db") const CouchDB = require("../db")
const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants") const { OBJ_STORE_DIRECTORY, ObjectStoreBuckets } = require("../constants")
const { getAllApps } = require("@budibase/auth/db")
const BB_CDN = "https://cdn.app.budi.live/assets" const BB_CDN = "https://cdn.app.budi.live/assets"
exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms)) exports.wait = ms => new Promise(resolve => setTimeout(resolve, ms))
exports.isDev = env.isDev exports.isDev = env.isDev
exports.getAllApps = getAllApps
/**
* 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<object[]>} 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)
}
}
/** /**
* Makes sure that a URL has the correct number of slashes, while maintaining the * Makes sure that a URL has the correct number of slashes, while maintaining the

View File

@ -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)
}
}

View File

@ -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

View File

@ -45,6 +45,7 @@ router
.post("/api/admin/users/init", controller.adminUser) .post("/api/admin/users/init", controller.adminUser)
.delete("/api/admin/users/:id", controller.destroy) .delete("/api/admin/users/:id", controller.destroy)
.get("/api/admin/users/:id", controller.find) .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", buildInviteValidation(), controller.invite)
.post( .post(
"/api/admin/users/invite/accept", "/api/admin/users/invite/accept",