Updating to have proper access control via an accessController and nearly ready to spit out the routing structure.
This commit is contained in:
parent
a423664f4c
commit
7f5c3a4688
|
@ -8,7 +8,7 @@ const fs = require("fs-extra")
|
|||
const { join, resolve } = require("../../utilities/centralPath")
|
||||
const packageJson = require("../../../package.json")
|
||||
const { createLinkView } = require("../../db/linkedRows")
|
||||
const { createRoutingView } = require("../../routing")
|
||||
const { createRoutingView } = require("../../utilities/routing")
|
||||
const { downloadTemplate } = require("../../utilities/templates")
|
||||
const {
|
||||
generateAppID,
|
||||
|
|
|
@ -1 +1,17 @@
|
|||
exports.fetch = async ctx => {}
|
||||
const { getRoutingInfo } = require("../../utilities/routing")
|
||||
const { AccessController } = require("../../utilities/security/accessLevels")
|
||||
|
||||
async function getRoutingStructure(appId) {
|
||||
let baseRouting = await getRoutingInfo(appId)
|
||||
return baseRouting
|
||||
}
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
ctx.body = await getRoutingStructure(ctx.appId)
|
||||
}
|
||||
|
||||
exports.clientFetch = async ctx => {
|
||||
const routing = getRoutingStructure(ctx.appId)
|
||||
// use the access controller to pick which access level is applicable to this user
|
||||
const accessController = new AccessController(ctx.appId)
|
||||
}
|
||||
|
|
|
@ -1,20 +1,28 @@
|
|||
const CouchDB = require("../../db")
|
||||
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||
const { AccessController } = require("../../utilities/security/accessLevels")
|
||||
|
||||
exports.fetch = async ctx => {
|
||||
const db = new CouchDB(ctx.user.appId)
|
||||
const appId = ctx.user.appId
|
||||
const db = new CouchDB(appId)
|
||||
|
||||
const screens = await db.allDocs(
|
||||
const screens = (
|
||||
await db.allDocs(
|
||||
getScreenParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map(element => element.doc)
|
||||
|
||||
ctx.body = screens.rows.map(element => element.doc)
|
||||
ctx.body = await new AccessController(appId).checkScreensAccess(
|
||||
screens,
|
||||
ctx.user.accessLevel._id
|
||||
)
|
||||
}
|
||||
|
||||
exports.find = async ctx => {
|
||||
const db = new CouchDB(ctx.user.appId)
|
||||
const appId = ctx.user.appId
|
||||
const db = new CouchDB(appId)
|
||||
|
||||
const screens = await db.allDocs(
|
||||
getScreenParams(ctx.params.pageId, {
|
||||
|
@ -22,7 +30,10 @@ exports.find = async ctx => {
|
|||
})
|
||||
)
|
||||
|
||||
ctx.body = screens.response.rows
|
||||
ctx.body = await new AccessController(appId).checkScreensAccess(
|
||||
screens,
|
||||
ctx.user.accessLevel._id
|
||||
)
|
||||
}
|
||||
|
||||
exports.save = async ctx => {
|
||||
|
|
|
@ -4,26 +4,7 @@ const compress = require("koa-compress")
|
|||
const zlib = require("zlib")
|
||||
const { budibaseAppsDir } = require("../utilities/budibaseDir")
|
||||
const { isDev } = require("../utilities")
|
||||
const {
|
||||
authRoutes,
|
||||
pageRoutes,
|
||||
screenRoutes,
|
||||
userRoutes,
|
||||
deployRoutes,
|
||||
applicationRoutes,
|
||||
rowRoutes,
|
||||
tableRoutes,
|
||||
viewRoutes,
|
||||
staticRoutes,
|
||||
componentRoutes,
|
||||
automationRoutes,
|
||||
accesslevelRoutes,
|
||||
apiKeysRoutes,
|
||||
templatesRoutes,
|
||||
analyticsRoutes,
|
||||
webhookRoutes,
|
||||
routingRoutes,
|
||||
} = require("./routes")
|
||||
const {mainRoutes, authRoutes, staticRoutes} = require("./routes")
|
||||
|
||||
const router = new Router()
|
||||
const env = require("../environment")
|
||||
|
@ -73,58 +54,15 @@ router.use(authRoutes.routes())
|
|||
router.use(authRoutes.allowedMethods())
|
||||
|
||||
// authenticated routes
|
||||
router.use(viewRoutes.routes())
|
||||
router.use(viewRoutes.allowedMethods())
|
||||
|
||||
router.use(tableRoutes.routes())
|
||||
router.use(tableRoutes.allowedMethods())
|
||||
|
||||
router.use(rowRoutes.routes())
|
||||
router.use(rowRoutes.allowedMethods())
|
||||
|
||||
router.use(userRoutes.routes())
|
||||
router.use(userRoutes.allowedMethods())
|
||||
|
||||
router.use(automationRoutes.routes())
|
||||
router.use(automationRoutes.allowedMethods())
|
||||
|
||||
router.use(webhookRoutes.routes())
|
||||
router.use(webhookRoutes.allowedMethods())
|
||||
|
||||
router.use(deployRoutes.routes())
|
||||
router.use(deployRoutes.allowedMethods())
|
||||
|
||||
router.use(templatesRoutes.routes())
|
||||
router.use(templatesRoutes.allowedMethods())
|
||||
// end auth routes
|
||||
|
||||
router.use(pageRoutes.routes())
|
||||
router.use(pageRoutes.allowedMethods())
|
||||
|
||||
router.use(screenRoutes.routes())
|
||||
router.use(screenRoutes.allowedMethods())
|
||||
|
||||
router.use(applicationRoutes.routes())
|
||||
router.use(applicationRoutes.allowedMethods())
|
||||
|
||||
router.use(componentRoutes.routes())
|
||||
router.use(componentRoutes.allowedMethods())
|
||||
|
||||
router.use(accesslevelRoutes.routes())
|
||||
router.use(accesslevelRoutes.allowedMethods())
|
||||
|
||||
router.use(apiKeysRoutes.routes())
|
||||
router.use(apiKeysRoutes.allowedMethods())
|
||||
|
||||
router.use(analyticsRoutes.routes())
|
||||
router.use(analyticsRoutes.allowedMethods())
|
||||
for (let route of mainRoutes) {
|
||||
router.use(route.routes())
|
||||
router.use(route.allowedMethods())
|
||||
}
|
||||
|
||||
// WARNING - static routes will catch everything else after them this must be last
|
||||
router.use(staticRoutes.routes())
|
||||
router.use(staticRoutes.allowedMethods())
|
||||
|
||||
router.use(routingRoutes.routes())
|
||||
router.use(routingRoutes.allowedMethods())
|
||||
|
||||
router.redirect("/", "/_builder")
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -17,9 +17,8 @@ const templatesRoutes = require("./templates")
|
|||
const analyticsRoutes = require("./analytics")
|
||||
const routingRoutes = require("./routing")
|
||||
|
||||
module.exports = {
|
||||
exports.mainRoutes = [
|
||||
deployRoutes,
|
||||
authRoutes,
|
||||
pageRoutes,
|
||||
screenRoutes,
|
||||
userRoutes,
|
||||
|
@ -27,7 +26,6 @@ module.exports = {
|
|||
rowRoutes,
|
||||
tableRoutes,
|
||||
viewRoutes,
|
||||
staticRoutes,
|
||||
componentRoutes,
|
||||
automationRoutes,
|
||||
accesslevelRoutes,
|
||||
|
@ -36,4 +34,7 @@ module.exports = {
|
|||
analyticsRoutes,
|
||||
webhookRoutes,
|
||||
routingRoutes,
|
||||
}
|
||||
]
|
||||
|
||||
exports.authRoutes = authRoutes
|
||||
exports.staticRoutes = staticRoutes
|
||||
|
|
|
@ -5,6 +5,9 @@ const controller = require("../controllers/routing")
|
|||
|
||||
const router = Router()
|
||||
|
||||
router.post("/api/routing", authorized(BUILDER), controller.fetch)
|
||||
// gets the full structure, not just the correct screen ID for your access level
|
||||
router
|
||||
.get("/api/routing", authorized(BUILDER), controller.fetch)
|
||||
.get("/api/routing/client", controller.clientFetch)
|
||||
|
||||
module.exports = router
|
||||
|
|
|
@ -12,10 +12,10 @@ function generateSaveValidation() {
|
|||
return joiValidator.body(Joi.object({
|
||||
_css: Joi.string().allow(""),
|
||||
name: Joi.string().required(),
|
||||
routing: Joi.array().items(Joi.object({
|
||||
routing: Joi.object({
|
||||
route: Joi.string().required(),
|
||||
accessLevelId: Joi.string().required(),
|
||||
})).required(),
|
||||
accessLevelId: Joi.string().required().allow(""),
|
||||
}).required().unknown(true),
|
||||
props: Joi.object({
|
||||
_id: Joi.string().required(),
|
||||
_component: Joi.string().required(),
|
||||
|
|
|
@ -53,8 +53,6 @@ module.exports = (permType, permLevel = null) => async (ctx, next) => {
|
|||
return next()
|
||||
}
|
||||
|
||||
// TODO: need to handle routing security
|
||||
|
||||
if (permType === PermissionTypes.BUILDER) {
|
||||
ctx.throw(403, "Not Authorized")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
const CouchDB = require("../db")
|
||||
const CouchDB = require("../../db")
|
||||
const { createRoutingView } = require("./routingUtils")
|
||||
const { ViewNames, getQueryIndex } = require("../db/utils")
|
||||
const { ViewNames, getQueryIndex, UNICODE_MAX } = require("../../db/utils")
|
||||
|
||||
exports.getRoutingInfo = async appId => {
|
||||
const db = new CouchDB(appId)
|
||||
try {
|
||||
const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING))
|
||||
const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING), {
|
||||
startKey: "",
|
||||
endKey: UNICODE_MAX,
|
||||
})
|
||||
return allRouting.rows.map(row => row.value)
|
||||
} catch (err) {
|
||||
// check if the view doesn't exist, it should for all new instances
|
|
@ -1,19 +1,20 @@
|
|||
const CouchDB = require("../db")
|
||||
const { DocumentTypes, SEPARATOR, ViewNames } = require("../db/utils")
|
||||
const CouchDB = require("../../db")
|
||||
const { DocumentTypes, SEPARATOR, ViewNames } = require("../../db/utils")
|
||||
const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
|
||||
|
||||
exports.createRoutingView = async appId => {
|
||||
const db = new CouchDB(appId)
|
||||
const designDoc = await db.get("_design/database")
|
||||
const view = {
|
||||
map: function(doc) {
|
||||
if (doc._id.startsWith(SCREEN_PREFIX)) {
|
||||
// if using variables in a map function need to inject them before use
|
||||
map: `function(doc) {
|
||||
if (doc._id.startsWith("${SCREEN_PREFIX}")) {
|
||||
emit(doc._id, {
|
||||
id: doc._id,
|
||||
routing: doc.routing,
|
||||
})
|
||||
}
|
||||
}.toString(),
|
||||
}`,
|
||||
}
|
||||
designDoc.views = {
|
||||
...designDoc.views,
|
|
@ -33,36 +33,80 @@ exports.BUILTIN_LEVEL_NAME_ARRAY = Object.values(exports.BUILTIN_LEVELS).map(
|
|||
)
|
||||
|
||||
function isBuiltin(accessLevel) {
|
||||
return BUILTIN_IDS.indexOf(accessLevel) !== -1
|
||||
return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1
|
||||
}
|
||||
|
||||
exports.getAccessLevel = async (appId, accessLevelId) => {
|
||||
class AccessController {
|
||||
constructor(appId) {
|
||||
this.appId = appId
|
||||
this.accessLevels = {}
|
||||
}
|
||||
|
||||
async getAccessLevel(accessLevelId) {
|
||||
if (this.accessLevels[accessLevelId]) {
|
||||
return this.accessLevels[accessLevelId]
|
||||
}
|
||||
let accessLevel
|
||||
if (isBuiltin(accessLevelId)) {
|
||||
return Object.values(exports.BUILTIN_LEVELS).find(
|
||||
accessLevel = Object.values(exports.BUILTIN_LEVELS).find(
|
||||
level => level._id === accessLevelId
|
||||
)
|
||||
} else {
|
||||
const db = new CouchDB(this.appId)
|
||||
accessLevel = await db.get(accessLevelId)
|
||||
}
|
||||
this.accessLevels[accessLevelId] = accessLevel
|
||||
return accessLevel
|
||||
}
|
||||
const db = new CouchDB(appId)
|
||||
return await db.get(accessLevelId)
|
||||
}
|
||||
|
||||
exports.hasAccess = async (appId, tryingAccessLevelId, userAccessLevelId) => {
|
||||
// special first case, if they are equal then access is allowed, no need to try anything
|
||||
if (tryingAccessLevelId === userAccessLevelId) {
|
||||
async hasAccess(tryingAccessLevelId, userAccessLevelId) {
|
||||
// special cases, the screen has no access level, the access levels are the same or the user
|
||||
// is currently in the builder
|
||||
if (
|
||||
tryingAccessLevelId == null ||
|
||||
tryingAccessLevelId === "" ||
|
||||
tryingAccessLevelId === userAccessLevelId ||
|
||||
userAccessLevelId === BUILTIN_IDS.BUILDER
|
||||
) {
|
||||
return true
|
||||
}
|
||||
let userAccess = await exports.getAccessLevel(appId, userAccessLevelId)
|
||||
let userAccess = await this.getAccessLevel(userAccessLevelId)
|
||||
// check if inherited makes it possible
|
||||
while (userAccess.inherits) {
|
||||
if (tryingAccessLevelId === userAccess.inherits) {
|
||||
return true
|
||||
}
|
||||
// go to get the inherited incase it inherits anything
|
||||
userAccess = await exports.getAccessLevel(appId, userAccess.inherits)
|
||||
userAccess = await this.getAccessLevel(userAccess.inherits)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async checkScreensAccess(screens, userAccessLevelId) {
|
||||
let accessibleScreens = []
|
||||
// don't want to handle this with Promise.all as this would mean all custom access levels would be
|
||||
// retrieved at same time, it is likely a custom levels will be re-used and therefore want
|
||||
// to work in sync for performance save
|
||||
for (let screen of screens) {
|
||||
const accessible = await this.checkScreenAccess(screen, userAccessLevelId)
|
||||
if (accessible) {
|
||||
accessibleScreens.push(accessible)
|
||||
}
|
||||
}
|
||||
return accessibleScreens
|
||||
}
|
||||
|
||||
async checkScreenAccess(screen, userAccessLevelId) {
|
||||
const accessLevelId =
|
||||
screen && screen.routing ? screen.routing.accessLevelId : null
|
||||
if (await this.hasAccess(accessLevelId, userAccessLevelId)) {
|
||||
return screen
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
exports.AccessController = AccessController
|
||||
exports.BUILTIN_LEVEL_IDS = BUILTIN_IDS
|
||||
exports.isBuiltin = isBuiltin
|
||||
exports.AccessLevel = AccessLevel
|
||||
|
|
Loading…
Reference in New Issue