mirror of https://github.com/nocodb/nocodb
mertmit
1 year ago
62 changed files with 336 additions and 358 deletions
@ -1,51 +0,0 @@ |
|||||||
import { isString } from '@vue/shared' |
|
||||||
import type { Roles } from 'nocodb-sdk' |
|
||||||
import { extractRolesObj } from 'nocodb-sdk' |
|
||||||
import { createSharedComposable, rolePermissions, useGlobal, useRoles } from '#imports' |
|
||||||
import type { Permission } from '#imports' |
|
||||||
|
|
||||||
const hasPermission = (role: Roles, hasRole: boolean, permission: Permission | string) => { |
|
||||||
const rolePermission = rolePermissions[role] |
|
||||||
|
|
||||||
if (!hasRole || !rolePermission) return false |
|
||||||
|
|
||||||
if (isString(rolePermission) && rolePermission === '*') return true |
|
||||||
|
|
||||||
if ('include' in rolePermission && rolePermission.include) { |
|
||||||
return !!rolePermission.include[permission as keyof typeof rolePermission.include] |
|
||||||
} |
|
||||||
|
|
||||||
return rolePermission[permission as keyof typeof rolePermission] |
|
||||||
} |
|
||||||
|
|
||||||
export const useUIPermission = createSharedComposable(() => { |
|
||||||
const { previewAs } = useGlobal() |
|
||||||
const { allRoles } = useRoles() |
|
||||||
|
|
||||||
const isUIAllowed = ( |
|
||||||
permission: Permission | string, |
|
||||||
skipPreviewAs = false, |
|
||||||
userRoles: string | Record<string, boolean> | string[] | null = null, |
|
||||||
combineWithStateRoles = false, |
|
||||||
) => { |
|
||||||
if (previewAs.value && !skipPreviewAs) { |
|
||||||
return hasPermission(previewAs.value, true, permission) |
|
||||||
} |
|
||||||
|
|
||||||
let roles: Record<string, boolean> = {} |
|
||||||
|
|
||||||
if (!userRoles) { |
|
||||||
if (allRoles.value) roles = allRoles.value |
|
||||||
} else { |
|
||||||
roles = extractRolesObj(userRoles) |
|
||||||
} |
|
||||||
|
|
||||||
if (userRoles && combineWithStateRoles) { |
|
||||||
roles = { ...roles, ...allRoles.value } |
|
||||||
} |
|
||||||
|
|
||||||
return Object.entries(roles).some(([role, hasRole]) => hasPermission(role as Roles, hasRole, permission)) |
|
||||||
} |
|
||||||
|
|
||||||
return { isUIAllowed } |
|
||||||
}) |
|
@ -0,0 +1,148 @@ |
|||||||
|
import { OrgUserRoles, ProjectRoles } from 'nocodb-sdk' |
||||||
|
|
||||||
|
const roleScopes = { |
||||||
|
org: [OrgUserRoles.VIEWER, OrgUserRoles.CREATOR], |
||||||
|
project: [ProjectRoles.VIEWER, ProjectRoles.COMMENTER, ProjectRoles.EDITOR, ProjectRoles.CREATOR, ProjectRoles.OWNER], |
||||||
|
} |
||||||
|
|
||||||
|
interface Perm { |
||||||
|
include?: Record<string, boolean> |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Each permission value means the following |
||||||
|
* `*` - which is wildcard, means all permissions are allowed |
||||||
|
* `include` - which is an object, means only the permissions listed in the object are allowed |
||||||
|
* `undefined` or `{}` - which is the default value, means no permissions are allowed |
||||||
|
* */ |
||||||
|
const rolePermissions = { |
||||||
|
// org level role permissions
|
||||||
|
[OrgUserRoles.SUPER_ADMIN]: '*', |
||||||
|
[OrgUserRoles.CREATOR]: { |
||||||
|
include: { |
||||||
|
projectCreate: true, |
||||||
|
projectMove: true, |
||||||
|
projectDelete: true, |
||||||
|
projectDuplicate: true, |
||||||
|
newUser: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[OrgUserRoles.VIEWER]: { |
||||||
|
include: { |
||||||
|
importRequest: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
|
||||||
|
// Project role permissions
|
||||||
|
[ProjectRoles.OWNER]: { |
||||||
|
include: { |
||||||
|
projectDelete: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[ProjectRoles.CREATOR]: { |
||||||
|
include: { |
||||||
|
baseCreate: true, |
||||||
|
fieldUpdate: true, |
||||||
|
hookList: true, |
||||||
|
tableCreate: true, |
||||||
|
tableRename: true, |
||||||
|
tableDelete: true, |
||||||
|
tableDuplicate: true, |
||||||
|
tableSort: true, |
||||||
|
layoutRename: true, |
||||||
|
layoutDelete: true, |
||||||
|
airtableImport: true, |
||||||
|
jsonImport: true, |
||||||
|
excelImport: true, |
||||||
|
settingsPage: true, |
||||||
|
newUser: true, |
||||||
|
webhook: true, |
||||||
|
fieldEdit: true, |
||||||
|
fieldAdd: true, |
||||||
|
tableIconEdit: true, |
||||||
|
viewCreateOrEdit: true, |
||||||
|
viewShare: true, |
||||||
|
projectShare: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[ProjectRoles.EDITOR]: { |
||||||
|
include: { |
||||||
|
dataInsert: true, |
||||||
|
dataEdit: true, |
||||||
|
sortSync: true, |
||||||
|
filterSync: true, |
||||||
|
filterChildrenRead: true, |
||||||
|
viewFieldEdit: true, |
||||||
|
csvImport: true, |
||||||
|
apiDocs: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[ProjectRoles.COMMENTER]: { |
||||||
|
include: { |
||||||
|
commentEdit: true, |
||||||
|
commentList: true, |
||||||
|
commentCount: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[ProjectRoles.VIEWER]: { |
||||||
|
include: { |
||||||
|
projectSettings: true, |
||||||
|
expandedForm: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
[ProjectRoles.NO_ACCESS]: { |
||||||
|
include: {}, |
||||||
|
}, |
||||||
|
} as Record<OrgUserRoles | ProjectRoles, Perm | '*'> |
||||||
|
|
||||||
|
/* |
||||||
|
We inherit include permissions from previous roles in the same scope (role order) |
||||||
|
To determine role order, we use `roleScopes` object |
||||||
|
|
||||||
|
So for example ProjectRoles.COMMENTER has `commentEdit` permission, |
||||||
|
which means ProjectRoles.EDITOR, ProjectRoles.CREATOR, ProjectRoles.OWNER will also have `commentEdit` permission |
||||||
|
where as ProjectRoles.VIEWER, ProjectRoles.NO_ACCESS will not have `commentEdit` permission. |
||||||
|
|
||||||
|
This is why we are validating that there are no duplicate permissions within the same scope |
||||||
|
even though it is not required for the code to work. It is to keep the code clean and easy to understand. |
||||||
|
*/ |
||||||
|
|
||||||
|
// validate no duplicate permissions within same scope
|
||||||
|
Object.values(roleScopes).forEach((roles) => { |
||||||
|
const scopePermissions: Record<string, boolean> = {} |
||||||
|
const duplicates: string[] = [] |
||||||
|
roles.forEach((role) => { |
||||||
|
const perms = (rolePermissions[role] as Perm).include || {} |
||||||
|
Object.keys(perms).forEach((perm) => { |
||||||
|
if (scopePermissions[perm]) { |
||||||
|
duplicates.push(perm) |
||||||
|
} |
||||||
|
scopePermissions[perm] = true |
||||||
|
}) |
||||||
|
}) |
||||||
|
if (duplicates.length) { |
||||||
|
throw new Error( |
||||||
|
`Duplicate permissions found in roles ${roles.join(', ')}. Please remove duplicate permissions: ${duplicates.join(', ')}`, |
||||||
|
) |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
// inherit include permissions within scope (role order)
|
||||||
|
Object.values(roleScopes).forEach((roles) => { |
||||||
|
let roleIndex = 0 |
||||||
|
for (const role of roles) { |
||||||
|
if (roleIndex === 0) { |
||||||
|
roleIndex++ |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if (rolePermissions[role] === '*') continue |
||||||
|
if ((rolePermissions[role] as Perm).include && (rolePermissions[roles[roleIndex - 1]] as Perm).include) { |
||||||
|
Object.assign((rolePermissions[role] as Perm).include!, (rolePermissions[roles[roleIndex - 1]] as Perm).include) |
||||||
|
} |
||||||
|
|
||||||
|
roleIndex++ |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
export { rolePermissions } |
@ -1,3 +1,4 @@ |
|||||||
export * from './constants' |
export * from './constants' |
||||||
export * from './enums' |
export * from './enums' |
||||||
export * from './types' |
export * from './types' |
||||||
|
export * from './acl' |
||||||
|
Loading…
Reference in new issue