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