2017-07-24 04:03:32 +00:00
'use strict' ;
const knex = require ( '../lib/knex' ) ;
2019-07-26 15:05:49 +00:00
const config = require ( '../lib/config' ) ;
2019-01-12 10:21:38 +00:00
const { enforce , castToInteger } = require ( '../lib/helpers' ) ;
2017-07-24 04:03:32 +00:00
const dtHelpers = require ( '../lib/dt-helpers' ) ;
2018-08-03 11:35:55 +00:00
const entitySettings = require ( '../lib/entity-settings' ) ;
2018-11-18 14:38:52 +00:00
const interoperableErrors = require ( '../../shared/interoperable-errors' ) ;
2018-11-03 20:46:23 +00:00
const log = require ( '../lib/log' ) ;
2018-11-18 14:38:52 +00:00
const { getGlobalNamespaceId } = require ( '../../shared/namespaces' ) ;
const { getAdminId } = require ( '../../shared/users' ) ;
2017-07-24 04:03:32 +00:00
2018-01-28 22:59:05 +00:00
// TODO: This would really benefit from some permission cache connected to rebuildPermissions
// A bit of the problem is that the cache would have to expunged as the result of other processes modifying entites/permissions
2017-07-24 04:03:32 +00:00
2017-07-27 14:11:22 +00:00
async function listByEntityDTAjax ( context , entityTypeId , entityId , params ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async ( tx ) => {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-08-13 18:11:58 +00:00
await enforceEntityPermissionTx ( tx , context , entityTypeId , entityId , 'share' ) ;
2017-07-24 04:03:32 +00:00
2017-08-13 18:11:58 +00:00
return await dtHelpers . ajaxListTx (
tx ,
params ,
builder => builder
. from ( entityType . sharesTable )
. innerJoin ( 'users' , entityType . sharesTable + '.user' , 'users.id' )
2019-05-08 17:54:19 +00:00
. innerJoin ( 'generated_role_names' , {
'generated_role_names.role' : entityType . sharesTable + '.role' ,
'generated_role_names.entity_type' : knex . raw ( '?' , [ entityTypeId ] )
} )
2017-08-13 18:11:58 +00:00
. where ( ` ${ entityType . sharesTable } .entity ` , entityId ) ,
[ 'users.username' , 'users.name' , 'generated_role_names.name' , 'users.id' , entityType . sharesTable + '.auto' ]
) ;
} ) ;
2017-07-27 14:11:22 +00:00
}
async function listByUserDTAjax ( context , entityTypeId , userId , params ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async ( tx ) => {
const user = await tx ( 'users' ) . where ( 'id' , userId ) . first ( ) ;
if ( ! user ) {
shares . throwPermissionDenied ( ) ;
}
2017-07-27 14:11:22 +00:00
2017-08-13 18:11:58 +00:00
await enforceEntityPermissionTx ( tx , context , 'namespace' , user . namespace , 'manageUsers' ) ;
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-08-13 18:11:58 +00:00
return await dtHelpers . ajaxListWithPermissionsTx (
tx ,
context ,
[ { entityTypeId } ] ,
params ,
builder => builder
. from ( entityType . sharesTable )
. innerJoin ( entityType . entitiesTable , entityType . sharesTable + '.entity' , entityType . entitiesTable + '.id' )
. innerJoin ( 'generated_role_names' , 'generated_role_names.role' , entityType . sharesTable + '.role' )
. where ( 'generated_role_names.entity_type' , entityTypeId )
. where ( entityType . sharesTable + '.user' , userId ) ,
[ entityType . entitiesTable + '.name' , 'generated_role_names.name' , entityType . entitiesTable + '.id' , entityType . sharesTable + '.auto' ]
) ;
} ) ;
2017-07-24 04:03:32 +00:00
}
2017-07-26 19:42:05 +00:00
async function listUnassignedUsersDTAjax ( context , entityTypeId , entityId , params ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async ( tx ) => {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
await enforceEntityPermissionTx ( tx , context , entityTypeId , entityId , 'share' ) ;
2017-07-24 04:03:32 +00:00
2017-08-13 18:11:58 +00:00
return await dtHelpers . ajaxListTx (
tx ,
params ,
builder => builder
. from ( 'users' )
. whereNotExists ( function ( ) {
return this
. select ( '*' )
. from ( entityType . sharesTable )
. whereRaw ( ` users.id = ${ entityType . sharesTable } .user ` )
. andWhere ( ` ${ entityType . sharesTable } .entity ` , entityId ) ;
} ) ,
[ 'users.id' , 'users.username' , 'users.name' ]
) ;
} ) ;
2017-07-24 04:03:32 +00:00
}
2017-08-13 18:11:58 +00:00
async function listRolesDTAjax ( entityTypeId , params ) {
2017-07-27 14:11:22 +00:00
return await dtHelpers . ajaxList (
params ,
builder => builder
. from ( 'generated_role_names' )
. where ( { entity _type : entityTypeId } ) ,
[ 'role' , 'name' , 'description' ]
) ;
}
2017-07-24 04:03:32 +00:00
2017-07-26 19:42:05 +00:00
async function assign ( context , entityTypeId , entityId , userId , role ) {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-07-26 19:42:05 +00:00
2017-07-24 04:03:32 +00:00
await knex . transaction ( async tx => {
2017-08-11 06:51:30 +00:00
await enforceEntityPermissionTx ( tx , context , entityTypeId , entityId , 'share' ) ;
2017-07-24 04:03:32 +00:00
enforce ( await tx ( 'users' ) . where ( 'id' , userId ) . select ( 'id' ) . first ( ) , 'Invalid user id' ) ;
2018-11-17 01:54:23 +00:00
const extraColumns = entityType . dependentPermissions ? entityType . dependentPermissions . extraColumns : [ ] ;
const entity = await tx ( entityType . entitiesTable ) . where ( 'id' , entityId ) . select ( [ 'id' , ... extraColumns ] ) . first ( ) ;
enforce ( entity , 'Invalid entity id' ) ;
if ( entityType . dependentPermissions ) {
enforce ( ! entityType . dependentPermissions . getParent ( entity ) , 'Cannot share/unshare a dependent entity' ) ;
}
2017-07-24 04:03:32 +00:00
2017-07-27 14:11:22 +00:00
const entry = await tx ( entityType . sharesTable ) . where ( { user : userId , entity : entityId } ) . select ( 'role' ) . first ( ) ;
2017-07-24 04:03:32 +00:00
if ( entry ) {
if ( ! role ) {
2017-07-27 14:11:22 +00:00
await tx ( entityType . sharesTable ) . where ( { user : userId , entity : entityId } ) . del ( ) ;
2017-07-24 04:03:32 +00:00
} else if ( entry . role !== role ) {
2017-07-27 14:11:22 +00:00
await tx ( entityType . sharesTable ) . where ( { user : userId , entity : entityId } ) . update ( 'role' , role ) ;
2017-07-24 04:03:32 +00:00
}
} else {
await tx ( entityType . sharesTable ) . insert ( {
user : userId ,
entity : entityId ,
role
} ) ;
}
await tx ( entityType . permissionsTable ) . where ( { user : userId , entity : entityId } ) . del ( ) ;
2017-07-24 23:14:17 +00:00
if ( entityTypeId === 'namespace' ) {
2017-08-14 20:53:29 +00:00
await rebuildPermissionsTx ( tx , { userId } ) ;
2017-07-24 23:14:17 +00:00
} else if ( role ) {
2017-08-14 20:53:29 +00:00
await rebuildPermissionsTx ( tx , { entityTypeId , entityId , userId } ) ;
2017-07-24 04:03:32 +00:00
}
} ) ;
}
2017-08-14 20:53:29 +00:00
async function rebuildPermissionsTx ( tx , restriction ) {
restriction = restriction || { } ;
2018-08-03 11:35:55 +00:00
const namespaceEntityType = entitySettings . getEntityType ( 'namespace' ) ;
2017-07-24 23:14:17 +00:00
2017-07-26 19:42:05 +00:00
// Collect entity types we care about
2017-07-24 23:14:17 +00:00
let restrictedEntityTypes ;
if ( restriction . entityTypeId ) {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( restriction . entityTypeId ) ;
2017-07-24 23:14:17 +00:00
restrictedEntityTypes = {
2017-07-26 19:42:05 +00:00
[ restriction . entityTypeId ] : entityType
2017-07-24 23:14:17 +00:00
} ;
} else {
2018-09-29 18:08:49 +00:00
restrictedEntityTypes = entitySettings . getEntityTypesWithPermissions ( ) ;
2017-07-24 23:14:17 +00:00
}
2017-07-27 19:41:25 +00:00
// To prevent users locking out themselves, we consider user with id 1 to be the admin and always assign it
// the admin role. The admin role is a global role that has admin===true
// If this behavior is not desired, it is enough to delete the user with id 1.
2018-09-01 19:29:10 +00:00
const adminUser = await tx ( 'users' ) . where ( 'id' , getAdminId ( ) ) . first ( ) ;
2017-07-27 19:41:25 +00:00
if ( adminUser ) {
let adminRole ;
for ( const role in config . roles . global ) {
if ( config . roles . global [ role ] . admin ) {
adminRole = role ;
break ;
}
2017-07-26 19:42:05 +00:00
}
2017-07-27 19:41:25 +00:00
if ( adminRole ) {
2018-09-01 19:29:10 +00:00
await tx ( 'users' ) . update ( 'role' , adminRole ) . where ( 'id' , getAdminId ( ) ) ;
2017-07-27 19:41:25 +00:00
}
2017-07-26 19:42:05 +00:00
}
2019-01-12 10:21:38 +00:00
// Reset root, own and shared namespaces shares as per the user roles
const usersAutoSharesQry = tx ( 'users' )
. select ( [ 'users.id' , 'users.role' , 'users.namespace' ] ) ;
2017-07-26 19:42:05 +00:00
if ( restriction . userId ) {
2019-01-12 10:21:38 +00:00
usersAutoSharesQry . where ( 'users.id' , restriction . userId ) ;
2017-07-26 19:42:05 +00:00
}
2019-01-12 10:21:38 +00:00
const usersAutoShares = await usersAutoSharesQry ;
2017-07-26 19:42:05 +00:00
2019-01-12 10:21:38 +00:00
for ( const user of usersAutoShares ) {
const roleConf = config . roles . global [ user . role ] ;
2017-07-26 19:42:05 +00:00
if ( roleConf ) {
2019-01-12 10:21:38 +00:00
const desiredRoles = new Map ( ) ;
2017-07-26 19:42:05 +00:00
2019-01-12 10:21:38 +00:00
if ( roleConf . sharedNamespaces ) {
for ( const shrKey in roleConf . sharedNamespaces ) {
const shrRole = roleConf . sharedNamespaces [ shrKey ] ;
const shrNsId = castToInteger ( shrKey ) ;
2017-07-26 19:42:05 +00:00
2019-01-12 10:21:38 +00:00
desiredRoles . set ( shrNsId , shrRole ) ;
}
}
2017-07-26 19:42:05 +00:00
2019-01-12 10:21:38 +00:00
if ( roleConf . ownNamespaceRole ) {
desiredRoles . set ( user . namespace , roleConf . ownNamespaceRole ) ;
}
2017-07-24 23:14:17 +00:00
2019-01-12 10:21:38 +00:00
if ( roleConf . rootNamespaceRole ) {
desiredRoles . set ( getGlobalNamespaceId ( ) , roleConf . rootNamespaceRole ) ;
}
for ( const [ nsId , role ] of desiredRoles . entries ( ) ) {
await tx ( namespaceEntityType . sharesTable ) . where ( { user : user . id , entity : nsId } ) . del ( ) ;
await tx ( namespaceEntityType . sharesTable ) . insert ( { user : user . id , entity : nsId , role : role , auto : true } ) ;
2017-07-26 19:42:05 +00:00
}
}
}
// Build the map of all namespaces
2017-07-24 23:14:17 +00:00
// nsMap is a map of namespaces - each of the following shape:
// .id - id of the namespace
// .namespace - id of the parent or null if no parent
// .userPermissions - Map userId -> [entityTypeId] -> array of permissions
// .transitiveUserPermissions - the same as above, but taking into account transitive permission obtained from namespace parents
2017-07-26 19:42:05 +00:00
const namespaces = await tx ( 'namespaces' ) . select ( [ 'id' , 'namespace' ] ) ;
2017-07-24 23:14:17 +00:00
const nsMap = new Map ( ) ;
for ( const namespace of namespaces ) {
namespace . userPermissions = new Map ( ) ;
nsMap . set ( namespace . id , namespace ) ;
}
// This populates .userPermissions
2017-07-26 19:42:05 +00:00
const nsSharesQuery = tx ( namespaceEntityType . sharesTable ) . select ( [ 'entity' , 'user' , 'role' ] ) ;
2017-07-24 23:14:17 +00:00
if ( restriction . userId ) {
2017-07-26 19:42:05 +00:00
nsSharesQuery . where ( 'user' , restriction . userId ) ;
2017-07-24 23:14:17 +00:00
}
const nsShares = await nsSharesQuery ;
for ( const nsShare of nsShares ) {
const ns = nsMap . get ( nsShare . entity ) ;
2017-07-24 04:03:32 +00:00
2017-07-24 23:14:17 +00:00
const userPerms = { } ;
ns . userPermissions . set ( nsShare . user , userPerms ) ;
2017-07-24 04:03:32 +00:00
2017-07-24 23:14:17 +00:00
for ( const entityTypeId in restrictedEntityTypes ) {
if ( config . roles . namespace [ nsShare . role ] &&
2017-07-26 19:42:05 +00:00
config . roles . namespace [ nsShare . role ] . children &&
config . roles . namespace [ nsShare . role ] . children [ entityTypeId ] ) {
2017-07-24 23:14:17 +00:00
2017-07-26 19:42:05 +00:00
userPerms [ entityTypeId ] = new Set ( config . roles . namespace [ nsShare . role ] . children [ entityTypeId ] ) ;
2017-07-24 23:14:17 +00:00
} else {
userPerms [ entityTypeId ] = new Set ( ) ;
2017-07-24 04:03:32 +00:00
}
}
2017-07-24 23:14:17 +00:00
}
// This computes .transitiveUserPermissions
for ( const ns of nsMap . values ( ) ) {
ns . transitiveUserPermissions = new Map ( ) ;
for ( const userPermsPair of ns . userPermissions ) {
const userPerms = { } ;
ns . transitiveUserPermissions . set ( userPermsPair [ 0 ] , userPerms ) ;
for ( const entityTypeId in restrictedEntityTypes ) {
userPerms [ entityTypeId ] = new Set ( userPermsPair [ 1 ] [ entityTypeId ] ) ;
}
}
let parentId = ns . namespace ;
while ( parentId ) {
const parent = nsMap . get ( parentId ) ;
for ( const userPermsPair of parent . userPermissions ) {
const user = userPermsPair [ 0 ] ;
if ( ns . transitiveUserPermissions . has ( user ) ) {
const userPerms = ns . transitiveUserPermissions . get ( user ) ;
for ( const entityTypeId in restrictedEntityTypes ) {
for ( const perm of userPermsPair [ 1 ] [ entityTypeId ] ) {
userPerms [ entityTypeId ] . add ( perm ) ;
}
}
} else {
2017-07-29 19:42:07 +00:00
const userPerms = { } ;
2017-07-24 23:14:17 +00:00
ns . transitiveUserPermissions . set ( user , userPerms ) ;
for ( const entityTypeId in restrictedEntityTypes ) {
userPerms [ entityTypeId ] = new Set ( userPermsPair [ 1 ] [ entityTypeId ] ) ;
}
}
}
parentId = parent . namespace ;
}
}
2017-07-26 19:42:05 +00:00
// This reads direct shares from DB, joins each with the permissions from namespaces and stores the permissions into DB
2017-07-24 23:14:17 +00:00
for ( const entityTypeId in restrictedEntityTypes ) {
const entityType = restrictedEntityTypes [ entityTypeId ] ;
const expungeQuery = tx ( entityType . permissionsTable ) . del ( ) ;
if ( restriction . entityId ) {
2017-07-26 19:42:05 +00:00
expungeQuery . where ( 'entity' , restriction . entityId ) ;
2017-07-24 23:14:17 +00:00
}
if ( restriction . userId ) {
2017-07-26 19:42:05 +00:00
expungeQuery . where ( 'user' , restriction . userId ) ;
2017-07-24 23:14:17 +00:00
}
await expungeQuery ;
2018-11-17 01:54:23 +00:00
const extraColumns = entityType . dependentPermissions ? entityType . dependentPermissions . extraColumns : [ ] ;
const entitiesQuery = tx ( entityType . entitiesTable ) . select ( [ 'id' , 'namespace' , ... extraColumns ] ) ;
const notToBeInserted = new Set ( ) ;
2017-07-24 23:14:17 +00:00
if ( restriction . entityId ) {
2018-11-17 01:54:23 +00:00
if ( restriction . parentId ) {
notToBeInserted . add ( restriction . parentId ) ;
entitiesQuery . whereIn ( 'id' , [ restriction . entityId , restriction . parentId ] ) ;
} else {
entitiesQuery . where ( 'id' , restriction . entityId ) ;
}
2017-07-24 23:14:17 +00:00
}
const entities = await entitiesQuery ;
2018-11-17 01:54:23 +00:00
const parentEntities = new Map ( ) ;
let nonChildEntities ;
if ( entityType . dependentPermissions ) {
nonChildEntities = [ ] ;
for ( const entity of entities ) {
const parent = entityType . dependentPermissions . getParent ( entity ) ;
if ( parent ) {
let childEntities ;
if ( parentEntities . has ( parent ) ) {
childEntities = parentEntities . get ( parent ) ;
} else {
childEntities = [ ] ;
parentEntities . set ( parent , childEntities ) ;
}
childEntities . push ( entity . id ) ;
} else {
nonChildEntities . push ( entity ) ;
}
}
} else {
nonChildEntities = entities ;
}
for ( const entity of nonChildEntities ) {
2017-07-24 23:14:17 +00:00
const permsPerUser = new Map ( ) ;
if ( entity . namespace ) { // The root namespace has not parent namespace, thus the test
const transitiveUserPermissions = nsMap . get ( entity . namespace ) . transitiveUserPermissions ;
for ( const transitivePermsPair of transitiveUserPermissions . entries ( ) ) {
2017-07-26 19:42:05 +00:00
permsPerUser . set ( transitivePermsPair [ 0 ] , new Set ( transitivePermsPair [ 1 ] [ entityTypeId ] ) ) ;
2017-07-24 23:14:17 +00:00
}
}
const directSharesQuery = tx ( entityType . sharesTable ) . select ( [ 'user' , 'role' ] ) . where ( 'entity' , entity . id ) ;
if ( restriction . userId ) {
directSharesQuery . andWhere ( 'user' , restriction . userId ) ;
}
const directShares = await directSharesQuery ;
for ( const share of directShares ) {
let userPerms ;
if ( permsPerUser . has ( share . user ) ) {
userPerms = permsPerUser . get ( share . user ) ;
} else {
userPerms = new Set ( ) ;
permsPerUser . set ( share . user , userPerms ) ;
}
if ( config . roles [ entityTypeId ] [ share . role ] &&
config . roles [ entityTypeId ] [ share . role ] . permissions ) {
for ( const perm of config . roles [ entityTypeId ] [ share . role ] . permissions ) {
userPerms . add ( perm ) ;
}
}
}
2018-11-17 01:54:23 +00:00
if ( ! notToBeInserted . has ( entity . id ) ) {
for ( const userPermsPair of permsPerUser . entries ( ) ) {
const data = [ ] ;
2017-07-24 23:14:17 +00:00
2018-11-17 01:54:23 +00:00
for ( const operation of userPermsPair [ 1 ] ) {
data . push ( { user : userPermsPair [ 0 ] , entity : entity . id , operation } ) ;
}
if ( data . length > 0 ) {
await tx ( entityType . permissionsTable ) . insert ( data ) ;
}
2017-07-24 23:14:17 +00:00
}
2018-11-17 01:54:23 +00:00
}
if ( parentEntities . has ( entity . id ) ) {
const childEntities = parentEntities . get ( entity . id ) ;
2017-07-24 23:14:17 +00:00
2018-11-17 01:54:23 +00:00
for ( const childId of childEntities ) {
for ( const userPermsPair of permsPerUser . entries ( ) ) {
const data = [ ] ;
for ( const operation of userPermsPair [ 1 ] ) {
if ( operation !== 'share' ) {
data . push ( { user : userPermsPair [ 0 ] , entity : childId , operation } ) ;
}
}
if ( data . length > 0 ) {
await tx ( entityType . permissionsTable ) . insert ( data ) ;
}
}
2017-07-24 23:14:17 +00:00
}
}
}
}
2017-07-24 04:03:32 +00:00
}
2017-08-14 20:53:29 +00:00
async function rebuildPermissions ( restriction ) {
await knex . transaction ( async tx => {
await rebuildPermissionsTx ( tx , restriction ) ;
} ) ;
2017-07-26 19:42:05 +00:00
}
2017-07-27 14:11:22 +00:00
async function regenerateRoleNamesTable ( ) {
await knex . transaction ( async tx => {
await tx ( 'generated_role_names' ) . del ( ) ;
2018-09-29 18:08:49 +00:00
const entityTypeIds = [ 'global' , ... Object . keys ( entitySettings . getEntityTypesWithPermissions ( ) ) ] ;
2017-07-27 14:11:22 +00:00
for ( const entityTypeId of entityTypeIds ) {
const roles = config . roles [ entityTypeId ] ;
for ( const role in roles ) {
await tx ( 'generated_role_names' ) . insert ( {
entity _type : entityTypeId ,
role ,
name : roles [ role ] . name ,
description : roles [ role ] . description ,
} ) ;
}
}
} ) ;
}
2017-07-26 19:42:05 +00:00
function throwPermissionDenied ( ) {
2018-04-22 15:33:43 +00:00
throw new interoperableErrors . PermissionDeniedError ( 'Permission denied' ) ;
2017-07-26 19:42:05 +00:00
}
2017-07-27 14:11:22 +00:00
async function removeDefaultShares ( tx , user ) {
2018-08-03 11:35:55 +00:00
const namespaceEntityType = entitySettings . getEntityType ( 'namespace' ) ;
2017-07-27 14:11:22 +00:00
const roleConf = config . roles . global [ user . role ] ;
if ( roleConf ) {
const desiredRole = roleConf . rootNamespaceRole ;
if ( roleConf . ownNamespaceRole ) {
await tx ( namespaceEntityType . sharesTable ) . where ( { user : user . id , entity : user . namespace } ) . del ( ) ;
}
if ( roleConf . rootNamespaceRole ) {
2018-04-22 15:33:43 +00:00
await tx ( namespaceEntityType . sharesTable ) . where ( { user : user . id , entity : getGlobalNamespaceId ( ) } ) . del ( ) ;
2017-07-27 14:11:22 +00:00
}
}
}
2017-08-13 18:11:58 +00:00
function checkGlobalPermission ( context , requiredOperations ) {
2018-03-24 22:55:50 +00:00
if ( ! context . user ) {
return false ;
}
2017-07-26 19:42:05 +00:00
if ( typeof requiredOperations === 'string' ) {
requiredOperations = [ requiredOperations ] ;
}
2018-04-02 09:58:32 +00:00
if ( context . user . restrictedAccessHandler ) {
2018-07-18 17:41:18 +00:00
const originalRequiredOperations = requiredOperations ;
2018-04-02 09:58:32 +00:00
const allowedPerms = context . user . restrictedAccessHandler . globalPermissions ;
if ( allowedPerms ) {
requiredOperations = requiredOperations . filter ( perm => allowedPerms . has ( perm ) ) ;
2018-07-18 17:41:18 +00:00
} else {
requiredOperations = [ ] ;
2018-04-02 09:58:32 +00:00
}
2018-07-18 17:41:18 +00:00
log . verbose ( 'check global permissions with restrictedAccessHandler -- requiredOperations: [' + originalRequiredOperations + '] -> [' + requiredOperations + ']' ) ;
2018-04-02 09:58:32 +00:00
}
if ( requiredOperations . length === 0 ) {
return false ;
}
if ( context . user . admin ) { // This handles the getAdminContext() case
return true ;
}
2017-07-26 19:42:05 +00:00
const roleSpec = config . roles . global [ context . user . role ] ;
2017-08-13 18:11:58 +00:00
let success = false ;
2017-07-26 19:42:05 +00:00
if ( roleSpec ) {
for ( const requiredOperation of requiredOperations ) {
if ( roleSpec . permissions . includes ( requiredOperation ) ) {
2017-08-13 18:11:58 +00:00
success = true ;
break ;
2017-07-26 19:42:05 +00:00
}
}
}
2017-08-13 18:11:58 +00:00
return success ;
}
function enforceGlobalPermission ( context , requiredOperations ) {
if ( ! checkGlobalPermission ( context , requiredOperations ) ) {
throwPermissionDenied ( ) ;
}
2017-07-26 19:42:05 +00:00
}
2017-08-11 06:51:30 +00:00
async function _checkPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) {
2018-03-24 22:55:50 +00:00
if ( ! context . user ) {
return false ;
}
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-07-26 19:42:05 +00:00
2018-04-02 09:58:32 +00:00
if ( typeof requiredOperations === 'string' ) {
requiredOperations = [ requiredOperations ] ;
}
2019-01-04 20:31:01 +00:00
requiredOperations = filterPermissionsByRestrictedAccessHandler ( context , entityTypeId , entityId , requiredOperations , 'checkPermissions' ) ;
2018-04-02 09:58:32 +00:00
if ( requiredOperations . length === 0 ) {
return false ;
}
2017-08-11 06:51:30 +00:00
if ( context . user . admin ) { // This handles the getAdminContext() case. In this case we don't check the permission, but just the existence.
const existsQuery = tx ( entityType . entitiesTable ) ;
if ( entityId ) {
existsQuery . where ( 'id' , entityId ) ;
}
2017-07-26 19:42:05 +00:00
2017-08-11 06:51:30 +00:00
const exists = await existsQuery . first ( ) ;
2017-07-26 19:42:05 +00:00
2017-08-11 06:51:30 +00:00
return ! ! exists ;
} else {
const permsQuery = tx ( entityType . permissionsTable )
. where ( 'user' , context . user . id )
. whereIn ( 'operation' , requiredOperations ) ;
2017-07-26 19:42:05 +00:00
2017-08-11 06:51:30 +00:00
if ( entityId ) {
permsQuery . andWhere ( 'entity' , entityId ) ;
}
const perms = await permsQuery . first ( ) ;
return ! ! perms ;
}
2017-07-26 19:42:05 +00:00
}
async function checkEntityPermission ( context , entityTypeId , entityId , requiredOperations ) {
if ( ! entityId ) {
return false ;
}
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async tx => {
return await _checkPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) ;
2017-08-11 06:51:30 +00:00
} ) ;
2017-07-26 19:42:05 +00:00
}
2018-09-29 18:08:49 +00:00
async function checkEntityPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) {
if ( ! entityId ) {
return false ;
}
return await _checkPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) ;
}
2017-07-26 19:42:05 +00:00
async function checkTypePermission ( context , entityTypeId , requiredOperations ) {
2017-08-13 18:11:58 +00:00
return await knex . transaction ( async tx => {
return await _checkPermissionTx ( tx , context , entityTypeId , null , requiredOperations ) ;
2017-08-11 06:51:30 +00:00
} ) ;
2017-07-26 19:42:05 +00:00
}
async function enforceEntityPermission ( context , entityTypeId , entityId , requiredOperations ) {
2017-08-11 06:51:30 +00:00
if ( ! entityId ) {
throwPermissionDenied ( ) ;
}
await knex . transaction ( async tx => {
const result = await _checkPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) ;
if ( ! result ) {
2018-05-21 17:41:10 +00:00
log . info ( ` Denying permission ${ entityTypeId } . ${ entityId } ${ requiredOperations } ` ) ;
2017-08-11 06:51:30 +00:00
throwPermissionDenied ( ) ;
}
} ) ;
}
async function enforceEntityPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) {
if ( ! entityId ) {
throwPermissionDenied ( ) ;
}
const result = await _checkPermissionTx ( tx , context , entityTypeId , entityId , requiredOperations ) ;
if ( ! result ) {
2018-05-21 17:41:10 +00:00
log . info ( ` Denying permission ${ entityTypeId } . ${ entityId } ${ requiredOperations } ` ) ;
2017-07-26 19:42:05 +00:00
throwPermissionDenied ( ) ;
}
}
async function enforceTypePermission ( context , entityTypeId , requiredOperations ) {
2017-08-11 06:51:30 +00:00
await knex . transaction ( async tx => {
const result = await _checkPermissionTx ( tx , context , entityTypeId , null , requiredOperations ) ;
if ( ! result ) {
2018-05-21 17:41:10 +00:00
log . info ( ` Denying permission ${ entityTypeId } ${ requiredOperations } ` ) ;
2017-08-11 06:51:30 +00:00
throwPermissionDenied ( ) ;
}
} ) ;
}
async function enforceTypePermissionTx ( tx , context , entityTypeId , requiredOperations ) {
const result = await _checkPermissionTx ( tx , context , entityTypeId , null , requiredOperations ) ;
if ( ! result ) {
2018-05-21 17:41:10 +00:00
log . info ( ` Denying permission ${ entityTypeId } ${ requiredOperations } ` ) ;
2017-07-26 19:42:05 +00:00
throwPermissionDenied ( ) ;
}
}
2017-08-13 18:11:58 +00:00
function getGlobalPermissions ( context ) {
2018-03-24 22:55:50 +00:00
if ( ! context . user ) {
return [ ] ;
}
2017-08-13 18:11:58 +00:00
enforce ( ! context . user . admin , 'getPermissions is not supposed to be called by assumed admin' ) ;
return ( config . roles . global [ context . user . role ] || { } ) . permissions || [ ] ;
}
async function getPermissionsTx ( tx , context , entityTypeId , entityId ) {
2018-03-24 22:55:50 +00:00
if ( ! context . user ) {
return [ ] ;
}
2017-08-13 18:11:58 +00:00
enforce ( ! context . user . admin , 'getPermissions is not supposed to be called by assumed admin' ) ;
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( entityTypeId ) ;
2017-08-11 16:16:44 +00:00
const rows = await tx ( entityType . permissionsTable )
. select ( 'operation' )
. where ( 'entity' , entityId )
. where ( 'user' , context . user . id ) ;
2019-01-04 20:31:01 +00:00
const operations = rows . map ( x => x . operation ) ;
return filterPermissionsByRestrictedAccessHandler ( context , entityTypeId , entityId , operations , 'getPermissions' ) ;
}
// If entityId is null, it means that we require that restrictedAccessHandler does not differentiate based on entityId. This is used in ajaxListWithPermissionsTx.
function filterPermissionsByRestrictedAccessHandler ( context , entityTypeId , entityId , permissions , operationMsg ) {
if ( context . user . restrictedAccessHandler ) {
const originalOperations = permissions ;
if ( context . user . restrictedAccessHandler . permissions ) {
const entityPerms = context . user . restrictedAccessHandler . permissions [ entityTypeId ] ;
if ( ! entityPerms ) {
permissions = [ ] ;
} else if ( entityPerms === true ) {
// no change to operations
} else if ( entityPerms instanceof Set ) {
permissions = permissions . filter ( perm => entityPerms . has ( perm ) ) ;
} else {
if ( entityId ) {
const allowedPerms = entityPerms [ entityId ] ;
if ( allowedPerms ) {
permissions = permissions . filter ( perm => allowedPerms . has ( perm ) ) ;
} else {
const allowedPerms = entityPerms [ 'default' ] ;
if ( allowedPerms ) {
permissions = permissions . filter ( perm => allowedPerms . has ( perm ) ) ;
} else {
permissions = [ ] ;
}
}
} else {
const allowedPerms = entityPerms [ 'default' ] ;
if ( allowedPerms ) {
permissions = permissions . filter ( perm => allowedPerms . has ( perm ) ) ;
} else {
permissions = [ ] ;
}
}
}
} else {
permissions = [ ] ;
}
log . verbose ( operationMsg + ' with restrictedAccessHandler -- entityTypeId: ' + entityTypeId + ' entityId: ' + entityId + ' operations: [' + originalOperations + '] -> [' + permissions + ']' ) ;
}
return permissions ;
2017-08-11 16:16:44 +00:00
}
2019-01-04 20:31:01 +00:00
function isAccessibleByRestrictedAccessHandler ( context , entityTypeId , entityId , permissions , operationMsg ) {
return filterPermissionsByRestrictedAccessHandler ( context , entityTypeId , entityId , permissions , operationMsg ) . length > 0 ;
}
2018-09-09 22:55:44 +00:00
module . exports . listByEntityDTAjax = listByEntityDTAjax ;
module . exports . listByUserDTAjax = listByUserDTAjax ;
module . exports . listUnassignedUsersDTAjax = listUnassignedUsersDTAjax ;
module . exports . listRolesDTAjax = listRolesDTAjax ;
module . exports . assign = assign ;
module . exports . rebuildPermissionsTx = rebuildPermissionsTx ;
module . exports . rebuildPermissions = rebuildPermissions ;
module . exports . removeDefaultShares = removeDefaultShares ;
module . exports . enforceEntityPermission = enforceEntityPermission ;
module . exports . enforceEntityPermissionTx = enforceEntityPermissionTx ;
module . exports . enforceTypePermission = enforceTypePermission ;
module . exports . enforceTypePermissionTx = enforceTypePermissionTx ;
2018-09-29 18:08:49 +00:00
module . exports . checkEntityPermissionTx = checkEntityPermissionTx ;
2018-09-09 22:55:44 +00:00
module . exports . checkEntityPermission = checkEntityPermission ;
module . exports . checkTypePermission = checkTypePermission ;
module . exports . enforceGlobalPermission = enforceGlobalPermission ;
module . exports . checkGlobalPermission = checkGlobalPermission ;
module . exports . throwPermissionDenied = throwPermissionDenied ;
module . exports . regenerateRoleNamesTable = regenerateRoleNamesTable ;
module . exports . getGlobalPermissions = getGlobalPermissions ;
module . exports . getPermissionsTx = getPermissionsTx ;
2019-01-04 20:31:01 +00:00
module . exports . filterPermissionsByRestrictedAccessHandler = filterPermissionsByRestrictedAccessHandler ;
module . exports . isAccessibleByRestrictedAccessHandler = isAccessibleByRestrictedAccessHandler ;