mirror of https://github.com/Budibase/budibase.git
35 changed files with 547 additions and 537 deletions
@ -1,52 +0,0 @@ |
|||
const CouchDB = require("../../db") |
|||
const { |
|||
BUILTIN_LEVELS, |
|||
AccessLevel, |
|||
getAccessLevel, |
|||
} = require("../../utilities/security/accessLevels") |
|||
const { |
|||
generateAccessLevelID, |
|||
getAccessLevelParams, |
|||
} = require("../../db/utils") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
const body = await db.allDocs( |
|||
getAccessLevelParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
const customAccessLevels = body.rows.map(row => row.doc) |
|||
|
|||
const staticAccessLevels = [BUILTIN_LEVELS.ADMIN, BUILTIN_LEVELS.POWER] |
|||
ctx.body = [...staticAccessLevels, ...customAccessLevels] |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
ctx.body = await getAccessLevel(ctx.user.appId, ctx.params.levelId) |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
|
|||
let id = ctx.request.body._id || generateAccessLevelID() |
|||
const level = new AccessLevel( |
|||
id, |
|||
ctx.request.body.name, |
|||
ctx.request.body.inherits |
|||
) |
|||
if (ctx.request.body._rev) { |
|||
level._rev = ctx.request.body._rev |
|||
} |
|||
const result = await db.put(level) |
|||
level._rev = result.rev |
|||
ctx.body = level |
|||
ctx.message = `Access Level '${level.name}' created successfully.` |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
await db.remove(ctx.params.levelId, ctx.params.rev) |
|||
ctx.message = `Access Level ${ctx.params.id} deleted successfully` |
|||
ctx.status = 200 |
|||
} |
|||
@ -0,0 +1,6 @@ |
|||
const { BUILTIN_PERMISSIONS } = require("../../utilities/security/permissions") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
// TODO: need to build out custom permissions
|
|||
ctx.body = Object.values(BUILTIN_PERMISSIONS) |
|||
} |
|||
@ -0,0 +1,55 @@ |
|||
const CouchDB = require("../../db") |
|||
const { |
|||
BUILTIN_ROLES, |
|||
Role, |
|||
getRole, |
|||
} = require("../../utilities/security/roles") |
|||
const { generateRoleID, getRoleParams } = require("../../db/utils") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
const body = await db.allDocs( |
|||
getRoleParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
const customRoles = body.rows.map(row => row.doc) |
|||
|
|||
// exclude internal roles like builder
|
|||
const staticRoles = [ |
|||
BUILTIN_ROLES.ADMIN, |
|||
BUILTIN_ROLES.POWER, |
|||
BUILTIN_ROLES.BASIC, |
|||
BUILTIN_ROLES.PUBLIC, |
|||
] |
|||
ctx.body = [...staticRoles, ...customRoles] |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
ctx.body = await getRole(ctx.user.appId, ctx.params.roleId) |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
let { _id, name, inherits, permissionId } = ctx.request.body |
|||
if (!_id) { |
|||
_id = generateRoleID() |
|||
} |
|||
const role = new Role(_id, name) |
|||
.addPermission(permissionId) |
|||
.addInheritance(inherits) |
|||
if (ctx.request.body._rev) { |
|||
role._rev = ctx.request.body._rev |
|||
} |
|||
const result = await db.put(role) |
|||
role._rev = result.rev |
|||
ctx.body = role |
|||
ctx.message = `Role '${role.name}' created successfully.` |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.appId) |
|||
await db.remove(ctx.params.roleId, ctx.params.rev) |
|||
ctx.message = `Role ${ctx.params.id} deleted successfully` |
|||
ctx.status = 200 |
|||
} |
|||
@ -1,18 +0,0 @@ |
|||
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", authorized(BUILDER), controller.save) |
|||
.get("/api/accesslevels", authorized(BUILDER), controller.fetch) |
|||
.get("/api/accesslevels/:levelId", authorized(BUILDER), controller.find) |
|||
.delete( |
|||
"/api/accesslevels/:levelId/:rev", |
|||
authorized(BUILDER), |
|||
controller.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,10 @@ |
|||
const Router = require("@koa/router") |
|||
const controller = require("../controllers/permission") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER } = require("../../utilities/security/permissions") |
|||
|
|||
const router = Router() |
|||
|
|||
router.get("/api/permissions", authorized(BUILDER), controller.fetch) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,30 @@ |
|||
const Router = require("@koa/router") |
|||
const controller = require("../controllers/role") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER } = require("../../utilities/security/permissions") |
|||
const Joi = require("joi") |
|||
const joiValidator = require("../../middleware/joi-validator") |
|||
const { |
|||
BUILTIN_PERMISSION_IDS, |
|||
} = require("../../utilities/security/permissions") |
|||
|
|||
const router = Router() |
|||
|
|||
function generateValidator() { |
|||
// prettier-ignore
|
|||
return joiValidator.body(Joi.object({ |
|||
_id: Joi.string().optional(), |
|||
_rev: Joi.string().optional(), |
|||
name: Joi.string().required(), |
|||
permissionId: Joi.string().valid(...Object.values(BUILTIN_PERMISSION_IDS)).required(), |
|||
inherits: Joi.string().optional(), |
|||
}).unknown(true)) |
|||
} |
|||
|
|||
router |
|||
.post("/api/roles", authorized(BUILDER), generateValidator(), controller.save) |
|||
.get("/api/roles", authorized(BUILDER), controller.fetch) |
|||
.get("/api/roles/:roleId", authorized(BUILDER), controller.find) |
|||
.delete("/api/roles/:roleId/:rev", authorized(BUILDER), controller.destroy) |
|||
|
|||
module.exports = router |
|||
@ -1,110 +0,0 @@ |
|||
const { |
|||
createApplication, |
|||
createTable, |
|||
createView, |
|||
supertest, |
|||
defaultHeaders |
|||
} = require("./couchTestUtils") |
|||
const { |
|||
BUILTIN_LEVEL_IDS, |
|||
} = require("../../../utilities/security/accessLevels") |
|||
|
|||
const accessLevelBody = { name: "user", inherits: BUILTIN_LEVEL_IDS.BASIC } |
|||
|
|||
describe("/accesslevels", () => { |
|||
let server |
|||
let request |
|||
let appId |
|||
let table |
|||
let view |
|||
|
|||
beforeAll(async () => { |
|||
({ request, server } = await supertest()) |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
server.close() |
|||
}) |
|||
|
|||
beforeEach(async () => { |
|||
let app = await createApplication(request) |
|||
appId = app.instance._id |
|||
table = await createTable(request, appId) |
|||
view = await createView(request, appId, table._id) |
|||
}) |
|||
|
|||
describe("create", () => { |
|||
|
|||
it("returns a success message when level is successfully created", async () => { |
|||
const res = await request |
|||
.post(`/api/accesslevels`) |
|||
.send(accessLevelBody) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.res.statusMessage).toEqual("Access Level 'user' created successfully.") |
|||
expect(res.body._id).toBeDefined() |
|||
expect(res.body._rev).toBeDefined() |
|||
}) |
|||
|
|||
}); |
|||
|
|||
describe("fetch", () => { |
|||
|
|||
it("should list custom levels, plus 2 default levels", async () => { |
|||
const createRes = await request |
|||
.post(`/api/accesslevels`) |
|||
.send(accessLevelBody) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const customLevel = createRes.body |
|||
|
|||
const res = await request |
|||
.get(`/api/accesslevels`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.body.length).toBe(3) |
|||
|
|||
const adminLevel = res.body.find(r => r._id === BUILTIN_LEVEL_IDS.ADMIN) |
|||
expect(adminLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.POWER) |
|||
expect(adminLevel).toBeDefined() |
|||
|
|||
const powerUserLevel = res.body.find(r => r._id === BUILTIN_LEVEL_IDS.POWER) |
|||
expect(powerUserLevel.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC) |
|||
expect(powerUserLevel).toBeDefined() |
|||
|
|||
const customLevelFetched = res.body.find(r => r._id === customLevel._id) |
|||
expect(customLevelFetched.inherits).toEqual(BUILTIN_LEVEL_IDS.BASIC) |
|||
expect(customLevelFetched).toBeDefined() |
|||
}) |
|||
|
|||
}); |
|||
|
|||
describe("destroy", () => { |
|||
it("should delete custom access level", async () => { |
|||
const createRes = await request |
|||
.post(`/api/accesslevels`) |
|||
.send({ name: "user" }) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const customLevel = createRes.body |
|||
|
|||
await request |
|||
.delete(`/api/accesslevels/${customLevel._id}/${customLevel._rev}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(200) |
|||
|
|||
await request |
|||
.get(`/api/accesslevels/${customLevel._id}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(404) |
|||
}) |
|||
}) |
|||
}); |
|||
@ -0,0 +1,114 @@ |
|||
const { |
|||
createApplication, |
|||
createTable, |
|||
createView, |
|||
supertest, |
|||
defaultHeaders |
|||
} = require("./couchTestUtils") |
|||
const { |
|||
BUILTIN_ROLE_IDS, |
|||
} = require("../../../utilities/security/roles") |
|||
const { BUILTIN_PERMISSION_IDS } = require("../../../utilities/security/permissions") |
|||
|
|||
const roleBody = { name: "NewRole", inherits: BUILTIN_ROLE_IDS.BASIC, permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY } |
|||
|
|||
describe("/roles", () => { |
|||
let server |
|||
let request |
|||
let appId |
|||
let table |
|||
let view |
|||
|
|||
beforeAll(async () => { |
|||
({ request, server } = await supertest()) |
|||
}); |
|||
|
|||
afterAll(() => { |
|||
server.close() |
|||
}) |
|||
|
|||
beforeEach(async () => { |
|||
let app = await createApplication(request) |
|||
appId = app.instance._id |
|||
table = await createTable(request, appId) |
|||
view = await createView(request, appId, table._id) |
|||
}) |
|||
|
|||
describe("create", () => { |
|||
|
|||
it("returns a success message when role is successfully created", async () => { |
|||
const res = await request |
|||
.post(`/api/roles`) |
|||
.send(roleBody) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.res.statusMessage).toEqual("Role 'NewRole' created successfully.") |
|||
expect(res.body._id).toBeDefined() |
|||
expect(res.body._rev).toBeDefined() |
|||
}) |
|||
|
|||
}); |
|||
|
|||
describe("fetch", () => { |
|||
|
|||
it("should list custom roles, plus 2 default roles", async () => { |
|||
const createRes = await request |
|||
.post(`/api/roles`) |
|||
.send(roleBody) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const customRole = createRes.body |
|||
|
|||
const res = await request |
|||
.get(`/api/roles`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
expect(res.body.length).toBe(3) |
|||
|
|||
const adminRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.ADMIN) |
|||
expect(adminRole).toBeDefined() |
|||
expect(adminRole.inherits).toEqual(BUILTIN_ROLE_IDS.POWER) |
|||
expect(adminRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.ADMIN) |
|||
|
|||
const powerUserRole = res.body.find(r => r._id === BUILTIN_ROLE_IDS.POWER) |
|||
expect(powerUserRole).toBeDefined() |
|||
expect(powerUserRole.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) |
|||
expect(powerUserRole.permissionId).toEqual(BUILTIN_PERMISSION_IDS.POWER) |
|||
|
|||
const customRoleFetched = res.body.find(r => r._id === customRole._id) |
|||
expect(customRoleFetched).toBeDefined() |
|||
expect(customRoleFetched.inherits).toEqual(BUILTIN_ROLE_IDS.BASIC) |
|||
expect(customRoleFetched.permissionId).toEqual(BUILTIN_PERMISSION_IDS.READ_ONLY) |
|||
}) |
|||
|
|||
}); |
|||
|
|||
describe("destroy", () => { |
|||
it("should delete custom roles", async () => { |
|||
const createRes = await request |
|||
.post(`/api/roles`) |
|||
.send({ name: "user", permissionId: BUILTIN_PERMISSION_IDS.READ_ONLY }) |
|||
.set(defaultHeaders(appId)) |
|||
.expect('Content-Type', /json/) |
|||
.expect(200) |
|||
|
|||
const customRole = createRes.body |
|||
|
|||
await request |
|||
.delete(`/api/roles/${customRole._id}/${customRole._rev}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(200) |
|||
|
|||
await request |
|||
.get(`/api/roles/${customRole._id}`) |
|||
.set(defaultHeaders(appId)) |
|||
.expect(404) |
|||
}) |
|||
}) |
|||
}); |
|||
@ -1,150 +0,0 @@ |
|||
const CouchDB = require("../../db") |
|||
const { cloneDeep } = require("lodash/fp") |
|||
|
|||
const BUILTIN_IDS = { |
|||
ADMIN: "ADMIN", |
|||
POWER: "POWER_USER", |
|||
BASIC: "BASIC", |
|||
PUBLIC: "PUBLIC", |
|||
BUILDER: "BUILDER", |
|||
} |
|||
|
|||
function AccessLevel(id, name, inherits) { |
|||
this._id = id |
|||
this.name = name |
|||
if (inherits) { |
|||
this.inherits = inherits |
|||
} |
|||
} |
|||
|
|||
exports.BUILTIN_LEVELS = { |
|||
ADMIN: new AccessLevel(BUILTIN_IDS.ADMIN, "Admin", BUILTIN_IDS.POWER), |
|||
POWER: new AccessLevel(BUILTIN_IDS.POWER, "Power", BUILTIN_IDS.BASIC), |
|||
BASIC: new AccessLevel(BUILTIN_IDS.BASIC, "Basic", BUILTIN_IDS.PUBLIC), |
|||
PUBLIC: new AccessLevel(BUILTIN_IDS.PUBLIC, "Public"), |
|||
BUILDER: new AccessLevel(BUILTIN_IDS.BUILDER, "Builder"), |
|||
} |
|||
|
|||
exports.BUILTIN_LEVEL_ID_ARRAY = Object.values(exports.BUILTIN_LEVELS).map( |
|||
level => level._id |
|||
) |
|||
|
|||
exports.BUILTIN_LEVEL_NAME_ARRAY = Object.values(exports.BUILTIN_LEVELS).map( |
|||
level => level.name |
|||
) |
|||
|
|||
function isBuiltin(accessLevel) { |
|||
return exports.BUILTIN_LEVEL_ID_ARRAY.indexOf(accessLevel) !== -1 |
|||
} |
|||
|
|||
/** |
|||
* Gets the access level object, this is mainly useful for two purposes, to check if the level exists and |
|||
* to check if the access level inherits any others. |
|||
* @param {string} appId The app in which to look for the access level. |
|||
* @param {string|null} accessLevelId The level ID to lookup. |
|||
* @returns {Promise<AccessLevel|object|null>} The access level object, which may contain an "inherits" property. |
|||
*/ |
|||
exports.getAccessLevel = async (appId, accessLevelId) => { |
|||
if (!accessLevelId) { |
|||
return null |
|||
} |
|||
let accessLevel |
|||
if (isBuiltin(accessLevelId)) { |
|||
accessLevel = cloneDeep( |
|||
Object.values(exports.BUILTIN_LEVELS).find( |
|||
level => level._id === accessLevelId |
|||
) |
|||
) |
|||
} else { |
|||
const db = new CouchDB(appId) |
|||
accessLevel = await db.get(accessLevelId) |
|||
} |
|||
return accessLevel |
|||
} |
|||
|
|||
/** |
|||
* Returns an ordered array of the user's inherited access level IDs, this can be used |
|||
* to determine if a user can access something that requires a specific access level. |
|||
* @param {string} appId The ID of the application from which access levels should be obtained. |
|||
* @param {string} userAccessLevelId The user's access level, this can be found in their access token. |
|||
* @returns {Promise<string[]>} returns an ordered array of the access levels, with the first being their |
|||
* highest level of access and the last being the lowest level. |
|||
*/ |
|||
exports.getUserAccessLevelHierarchy = async (appId, userAccessLevelId) => { |
|||
// special case, if they don't have a level then they are a public user
|
|||
if (!userAccessLevelId) { |
|||
return [BUILTIN_IDS.PUBLIC] |
|||
} |
|||
let accessLevelIds = [userAccessLevelId] |
|||
let userAccess = await exports.getAccessLevel(appId, userAccessLevelId) |
|||
// check if inherited makes it possible
|
|||
while ( |
|||
userAccess && |
|||
userAccess.inherits && |
|||
accessLevelIds.indexOf(userAccess.inherits) === -1 |
|||
) { |
|||
accessLevelIds.push(userAccess.inherits) |
|||
// go to get the inherited incase it inherits anything
|
|||
userAccess = await exports.getAccessLevel(appId, userAccess.inherits) |
|||
} |
|||
// add the user's actual level at the end (not at start as that stops iteration
|
|||
return accessLevelIds |
|||
} |
|||
|
|||
class AccessController { |
|||
constructor(appId) { |
|||
this.appId = appId |
|||
this.userHierarchies = {} |
|||
} |
|||
|
|||
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 accessLevelIds = this.userHierarchies[userAccessLevelId] |
|||
if (!accessLevelIds) { |
|||
accessLevelIds = await exports.getUserAccessLevelHierarchy( |
|||
this.appId, |
|||
userAccessLevelId |
|||
) |
|||
this.userHierarchies[userAccessLevelId] = userAccessLevelId |
|||
} |
|||
|
|||
return accessLevelIds.indexOf(tryingAccessLevelId) !== -1 |
|||
} |
|||
|
|||
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 |
|||
@ -0,0 +1,183 @@ |
|||
const CouchDB = require("../../db") |
|||
const { cloneDeep } = require("lodash/fp") |
|||
const { BUILTIN_PERMISSION_IDS } = require("./permissions") |
|||
|
|||
const BUILTIN_IDS = { |
|||
ADMIN: "ADMIN", |
|||
POWER: "POWER_USER", |
|||
BASIC: "BASIC", |
|||
PUBLIC: "PUBLIC", |
|||
BUILDER: "BUILDER", |
|||
} |
|||
|
|||
function Role(id, name) { |
|||
this._id = id |
|||
this.name = name |
|||
} |
|||
|
|||
Role.prototype.addPermission = function(permissionId) { |
|||
this.permissionId = permissionId |
|||
return this |
|||
} |
|||
|
|||
Role.prototype.addInheritance = function(inherits) { |
|||
this.inherits = inherits |
|||
return this |
|||
} |
|||
|
|||
exports.BUILTIN_ROLES = { |
|||
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin") |
|||
.addPermission(BUILTIN_PERMISSION_IDS.ADMIN) |
|||
.addInheritance(BUILTIN_IDS.POWER), |
|||
POWER: new Role(BUILTIN_IDS.POWER, "Power") |
|||
.addPermission(BUILTIN_PERMISSION_IDS.POWER) |
|||
.addInheritance(BUILTIN_IDS.BASIC), |
|||
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic") |
|||
.addPermission(BUILTIN_PERMISSION_IDS.WRITE) |
|||
.addInheritance(BUILTIN_IDS.PUBLIC), |
|||
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission( |
|||
BUILTIN_PERMISSION_IDS.READ_ONLY |
|||
), |
|||
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission( |
|||
BUILTIN_PERMISSION_IDS.ADMIN |
|||
), |
|||
} |
|||
|
|||
exports.BUILTIN_ROLE_ID_ARRAY = Object.values(exports.BUILTIN_ROLES).map( |
|||
level => level._id |
|||
) |
|||
|
|||
exports.BUILTIN_ROLE_NAME_ARRAY = Object.values(exports.BUILTIN_ROLES).map( |
|||
level => level.name |
|||
) |
|||
|
|||
function isBuiltin(role) { |
|||
return exports.BUILTIN_ROLE_ID_ARRAY.indexOf(role) !== -1 |
|||
} |
|||
|
|||
/** |
|||
* Gets the role object, this is mainly useful for two purposes, to check if the level exists and |
|||
* to check if the role inherits any others. |
|||
* @param {string} appId The app in which to look for the role. |
|||
* @param {string|null} roleId The level ID to lookup. |
|||
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property. |
|||
*/ |
|||
exports.getRole = async (appId, roleId) => { |
|||
if (!roleId) { |
|||
return null |
|||
} |
|||
let role |
|||
if (isBuiltin(roleId)) { |
|||
role = cloneDeep( |
|||
Object.values(exports.BUILTIN_ROLES).find(role => role._id === roleId) |
|||
) |
|||
} else { |
|||
const db = new CouchDB(appId) |
|||
role = await db.get(roleId) |
|||
} |
|||
return role |
|||
} |
|||
|
|||
/** |
|||
* Simple function to get all the roles based on the top level user role ID. |
|||
*/ |
|||
async function getAllUserRoles(appId, userRoleId) { |
|||
if (!userRoleId) { |
|||
return [BUILTIN_IDS.PUBLIC] |
|||
} |
|||
let currentRole = await exports.getRole(appId, userRoleId) |
|||
let roles = currentRole ? [currentRole] : [] |
|||
let roleIds = [userRoleId] |
|||
// get all the inherited roles
|
|||
while ( |
|||
currentRole && |
|||
currentRole.inherits && |
|||
roleIds.indexOf(currentRole.inherits) === -1 |
|||
) { |
|||
roleIds.push(currentRole.inherits) |
|||
currentRole = await exports.getRole(appId, currentRole.inherits) |
|||
roles.push(currentRole) |
|||
} |
|||
return roles |
|||
} |
|||
|
|||
/** |
|||
* Returns an ordered array of the user's inherited role IDs, this can be used |
|||
* to determine if a user can access something that requires a specific role. |
|||
* @param {string} appId The ID of the application from which roles should be obtained. |
|||
* @param {string} userRoleId The user's role ID, this can be found in their access token. |
|||
* @returns {Promise<string[]>} returns an ordered array of the roles, with the first being their |
|||
* highest level of access and the last being the lowest level. |
|||
*/ |
|||
exports.getUserRoleHierarchy = async (appId, userRoleId) => { |
|||
// special case, if they don't have a role then they are a public user
|
|||
return (await getAllUserRoles(appId, userRoleId)).map(role => role._id) |
|||
} |
|||
|
|||
/** |
|||
* Get all of the user permissions which could be found across the role hierarchy |
|||
* @param appId The ID of the application from which roles should be obtained. |
|||
* @param userRoleId The user's role ID, this can be found in their access token. |
|||
* @returns {Promise<string[]>} A list of permission IDs these should all be unique. |
|||
*/ |
|||
exports.getUserPermissionIds = async (appId, userRoleId) => { |
|||
return [ |
|||
...new Set( |
|||
(await getAllUserRoles(appId, userRoleId)).map(role => role.permissionId) |
|||
), |
|||
] |
|||
} |
|||
|
|||
class AccessController { |
|||
constructor(appId) { |
|||
this.appId = appId |
|||
this.userHierarchies = {} |
|||
} |
|||
|
|||
async hasAccess(tryingRoleId, userRoleId) { |
|||
// special cases, the screen has no role, the roles are the same or the user
|
|||
// is currently in the builder
|
|||
if ( |
|||
tryingRoleId == null || |
|||
tryingRoleId === "" || |
|||
tryingRoleId === userRoleId || |
|||
tryingRoleId === BUILTIN_IDS.BUILDER |
|||
) { |
|||
return true |
|||
} |
|||
let roleIds = this.userHierarchies[userRoleId] |
|||
if (!roleIds) { |
|||
roleIds = await exports.getUserRoleHierarchy(this.appId, userRoleId) |
|||
this.userHierarchies[userRoleId] = userRoleId |
|||
} |
|||
|
|||
return roleIds.indexOf(tryingRoleId) !== -1 |
|||
} |
|||
|
|||
async checkScreensAccess(screens, userRoleId) { |
|||
let accessibleScreens = [] |
|||
// don't want to handle this with Promise.all as this would mean all custom roles would be
|
|||
// retrieved at same time, it is likely a custom role 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, userRoleId) |
|||
if (accessible) { |
|||
accessibleScreens.push(accessible) |
|||
} |
|||
} |
|||
return accessibleScreens |
|||
} |
|||
|
|||
async checkScreenAccess(screen, userRoleId) { |
|||
const roleId = screen && screen.routing ? screen.routing.roleId : null |
|||
if (await this.hasAccess(roleId, userRoleId)) { |
|||
return screen |
|||
} |
|||
return null |
|||
} |
|||
} |
|||
|
|||
exports.AccessController = AccessController |
|||
exports.BUILTIN_ROLE_IDS = BUILTIN_IDS |
|||
exports.isBuiltin = isBuiltin |
|||
exports.Role = Role |
|||
Loading…
Reference in new issue