summaryrefslogblamecommitdiffstats
path: root/server/lib/permissions/permissionhelper.js
blob: 5185a6242f780f7226b786fb6558186dc80df9af (plain) (tree)
1
2
3
4
5
6
7


                                                         
                                                                    
 
                                    
 





                                                                                                                               
                                                                                                                   


        
                                      
                                    
                                                                    




                                                                        








                                                       
                                                                                               











                                                                                        
                                                          

                                             
                                    
                                                                      







                                                                                  
                   
                                                 










                                                             

                                                                                  

                                                       

                                       









                                   


   
                                                              
                                                                        

                                             
                                    
                                                                      







                                                                                  
                                                 



                                     

                                                             




                                                       

                                                    

                                                                 


                                                    





                                                                    
     
                


   
                                                                                          
                                                           

                                             
                                    
                                                                      







                                                                                                                                 
                   
                                                 










                                                             

                                                                                  
                                                     

                                       









                                    


   
                                                               
                                                                          

                                             
                                    
                                                                      

                                                                                                                                 
                                 
                                 
                                                          
                                                                    
                                                                        
        
                                                 




                                     
                                                             


                                                                    



                                           

                                                    
                                                                                                  


                                                       
       








                                                        
 




                                                                 

   
 
                                                                
                                                                              
                                                                              
                                                 
                                          
 
                    
                    
                                                                                        
 


                                                        


                                          
                              
                                              
                                                                                                                                 
                                               

                        
     
   






                                          
                                
                                                                         

               

















































































                                                                                                                                                                
/* global __appdir */
const path = require('path')
var db = require(path.join(__appdir, 'lib', 'sequelize'))
var groupHelper = require(path.join(__appdir, 'lib', 'grouphelper'))

module.exports = { exportFunctions }

function exportFunctions (req, res, next) {
  req.user.hasPermission = permissionName => hasPermission(req.user.id, permissionName)
  req.user.getAllowedGroups = permissionName => getAllowedGroups(req.user.id, permissionName)
  req.user.hasPermissionForGroup = (permissionName, groupId) => hasPermissionForGroup(req.user.id, permissionName, groupId)
  req.user.getAllowedClients = permissionName => getAllowedClients(req.user.id, permissionName)
  req.user.hasPermissionForClient = (permissionName, clientId) => hasPermissionForClient(req.user.id, permissionName, clientId)
  req.user.getAllowedChilds = (permissionName, groupIds) => getAllowedChilds(req.user.id, permissionName, groupIds)
  next()
}

async function isSuperadmin (userid) {
  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': 'superadmin' },
    include: [{ as: 'roles', model: db.role, include: ['permissions'] }]
  })
  return user !== null
}

// Check if the user has given permission.
async function hasPermission (userid, permissionName) {
  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return true
  var permission = permissionName.split('.')
  // Wildcards
  var user
  if (permission[1] === '*') {
    user = await db.user.findOne({
      where: { id: userid, '$roles.permissions.name$': { [db.Op.like]: permission[0] + '%' } },
      include: [{ as: 'roles', model: db.role, include: ['permissions'] }]
    })
  } else {
    user = await db.user.findOne({
      where: { id: userid, '$roles.permissions.name$': permissionName },
      include: [{ as: 'roles', model: db.role, include: ['permissions'] }]
    })
  }
  return user !== null
}

// Get a list of GroupIDs the user has the given permission for. 0 means for all groups.
async function getAllowedGroups (userid, permissionName) {
  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return [0]
  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': permissionName },
    include: [{ as: 'roles', model: db.role, include: ['permissions', 'groups'] }]
  })
  // User doesn't have the permission
  if (user === null) return []
  // User has permission, permission is not groupdependent
  else if (!user.roles[0].permissions[0].groupdependent) return [0]
  // User has permission, permission is groupdependent
  else {
    var result = []
    for (let i = 0; i < user.roles.length; i++) {
      var whitelist = []
      var blacklist = []
      // Fill in white- and blacklist
      for (let j = 0; j < user.roles[i].groups.length; j++) {
        if (user.roles[i].groups[j].role_x_group.blacklist) {
          blacklist.push(user.roles[i].groups[j])
        } else {
          whitelist.push(user.roles[i].groups[j])
        }
      }

      // Get childs of whitelist groups, filtered by blacklist
      var whitelistChilds = await groupHelper.getAllChildren(whitelist, blacklist)
      result = result.concat(whitelist)
      result = result.concat(whitelistChilds.subgroups)
    }
    // Filter result for unique entries
    const filteredGroups = []
    const groupMap = new Map()
    for (const item of result) {
      if (!groupMap.has(item.id)) {
        groupMap.set(item.id, true)
        filteredGroups.push(item)
      }
    }
    result = filteredGroups
    return result
  }
}

// Check if the user has a given permission for a given group.
async function hasPermissionForGroup (userid, permissionName, groupId) {
  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return true
  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': permissionName },
    include: [{ as: 'roles', model: db.role, include: ['permissions', 'groups'] }]
  })
  // User doesn't have permission
  if (user === null) return false
  // User has permission, permission is not groupdependent
  else if (!user.roles[0].permissions[0].groupdependent) return true
  // User has permission, permission is groupdependent, check for group
  else {
    for (let i = 0; i < user.roles.length; i++) {
      var whitelist = []
      var blacklist = []
      var blacklistBreak = false
      // Fill in white- and blacklist
      for (let j = 0; j < user.roles[i].groups.length; j++) {
        if (user.roles[i].groups[j].role_x_group.blacklist) {
          // Shortcut, check next role
          if (user.roles[i].groups[j].id === groupId) {
            blacklistBreak = true
            break
          }
          blacklist.push(user.roles[i].groups[j].id)
        } else {
          // Shortcut
          if (user.roles[i].groups[j].id === groupId) return true
          whitelist.push(user.roles[i].groups[j].id)
        }
      }
      // Break by blacklist, do not check parents
      if (blacklistBreak) continue

      // Check parents for white-/blacklist entries
      let result = await checkParents(groupId, whitelist, blacklist)
      if (result) return true
    }
    return false
  }
}

// Get a list of ClientIDs the user has the given permission for. 0 means for all clients.
async function getAllowedClients (userid, permissionName) {
  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return [0]
  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': permissionName },
    include: [{ as: 'roles', model: db.role, include: ['permissions', { as: 'groups', model: db.group, include: ['clients'] }] }]
  })
  // User doesn't have the permission
  if (user === null) return []
  // User has permission, permission is not groupdependent
  else if (!user.roles[0].permissions[0].groupdependent) return [0]
  // User has permission, permission is groupdependent
  else {
    var result = []
    for (let i = 0; i < user.roles.length; i++) {
      var whitelist = []
      var blacklist = []
      // Fill in white- and blacklist
      for (let j = 0; j < user.roles[i].groups.length; j++) {
        if (user.roles[i].groups[j].role_x_group.blacklist) {
          blacklist.push(user.roles[i].groups[j])
        } else {
          whitelist.push(user.roles[i].groups[j])
        }
      }

      // Get childs of whitelist groups, filtered by blacklist
      var whitelistChilds = await groupHelper.getAllChildren(whitelist, blacklist)
      result = result.concat(whitelistChilds.clients)
    }
    // Filter result for unique entries
    const filteredClients = []
    const clientMap = new Map()
    for (const item of result) {
      if (!clientMap.has(item.id)) {
        clientMap.set(item.id, true)
        filteredClients.push(item)
      }
    }
    result = filteredClients
    return result
  }
}

// Check if the user has a given permission for a given client.
async function hasPermissionForClient (userid, permissionName, clientId) {
  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return true
  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': permissionName },
    include: [{ as: 'roles', model: db.role, include: ['permissions', { as: 'groups', model: db.group, include: ['clients'] }] }]
  })
  // User doesn't have permission
  if (user === null) return false
  // User has permission, permission is not groupdependent
  else if (!user.roles[0].permissions[0].groupdependent) return true
  // User has permission, permission is groupdependent, check for client
  else {
    for (let i = 0; i < user.roles.length; i++) {
      var whitelist = []
      var blacklist = []
      var blacklistBreak = false
      var result = false
      // Fill in white- and blacklist
      for (let j = 0; j < user.roles[i].groups.length; j++) {
        var clients = user.roles[i].groups[j].clients.map(c => c.id)
        if (user.roles[i].groups[j].role_x_group.blacklist) {
          // Shortcut
          if (clients.includes(clientId)) {
            blacklistBreak = true
            break
          }
          blacklist.push(user.roles[i].groups[j].id)
        } else {
          // Remember it was found, check if client is in any blacklisted group on same layer tho.
          if (clients.includes(clientId)) result = true
          whitelist.push(user.roles[i].groups[j].id)
        }
      }
      if (blacklistBreak) continue
      // no blacklist shortcut used, but whitelist found
      if (result) return true
      // Get groups the client is assigned to
      var client = await db.client.findOne({
        where: { id: clientId },
        include: [{ as: 'groups', model: db.group }]
      })
      var groupIds = client.groups.map(g => g.id)

      // Check parents for white-/blacklist entries.
      result = await checkParents(groupIds, whitelist, blacklist)
      if (result) return true
    }
    return false
  }
}

// Check if parents of groupIds are in the whitelist / blacklist
// Whitelist returns true, blacklist or no parent in either list returns false
async function checkParents (groupIds, whitelist, blacklist, knownGrps = []) {
  // No whitelist means the group can't be in one
  if (whitelist.length === 0) return false

  var result = false
  var parentIds = []
  var groups = await db.group.findAll({ where: { id: groupIds }, include: ['parents'] })

  for (let i = 0; i < groups.length; i++) {
    for (let j = 0; j < groups[i].parents.length; j++) {
      var id = groups[i].parents[j].id

      if (knownGrps.includes(id)) continue

      // Parent is blacklisted
      if (blacklist.includes(id)) return false
      // Parent is whitelisted, continue loop to see if another parent on SAME LAYER is blacklisted, as blacklisted > whitelisted
      if (whitelist.includes(id)) result = true
      parentIds.push(id)
      knownGrps.push(id)
    }
  }

  // A parent is whitelisted
  if (result) return true

  // No further parents found
  if (parentIds.length === 0) return false

  // Check next layer of parents
  result = await checkParents(parentIds, whitelist, blacklist, knownGrps)
  return result
}

async function getAllowedChilds (userid, permissionName, groupIds) {
  if (Number.isInteger(groupIds) && !Array.isArray(groupIds)) groupIds = [groupIds]

  var superAdmin = await isSuperadmin(userid)
  if (superAdmin) return groupHelper.getAllChildrenByIds(groupIds, {}, {})

  var user = await db.user.findOne({
    where: { id: userid, '$roles.permissions.name$': permissionName },
    include: [{ as: 'roles', model: db.role, include: ['permissions', 'groups'] }]
  })

  // User doesn't have the permission
  if (user === null) return {}

  // User has permission, permission is not groupdependent
  else if (!user.roles[0].permissions[0].groupdependent) return groupHelper.getAllChildrenByIds(groupIds, {}, {})

  // User has permission, permission is groupdependent
  else {
    var subgroups = []
    var clients = []
    var groupList = []

    for (let j = 0; j < groupIds.length; j++) {
      if (await hasPermissionForGroup(userid, permissionName, groupIds[j])) groupList.push(groupIds[j])
    }

    var allChilds = await groupHelper.getAllChildrenByIds(groupIds, {}, {})

    for (let i = 0; i < user.roles.length; i++) {
      var roleWhitelist = []
      var roleBlacklistMap = {}
      var roleGroupList = groupList

      for (let j = 0; j < user.roles[i].groups.length; j++) {
        if (user.roles[i].groups[j].role_x_group.blacklist) {
          roleBlacklistMap[user.roles[i].groups[j].id] = true
        } else {
          roleWhitelist.push(user.roles[i].groups[j])
        }
      }

      // TODO: This is just overwhelming inefficient. Even if whitelist contains only a few ids, if the function is called for the root, allChilds are 18k+ ids.
      for (let j = 0; j < roleWhitelist.length; j++) {
        for (let k = 0; k < allChilds.subgroups.length; k++) {
          if (allChilds.subgroups[k].id === roleWhitelist[j].id) {
            roleGroupList.push(roleWhitelist[j].id)
            subgroups = subgroups.concat(roleWhitelist[j])
          }
        }
      }

      var childs = await groupHelper.getAllChildrenByIds(roleGroupList, roleBlacklistMap, {})
      subgroups = subgroups.concat(childs.subgroups)
      clients = clients.concat(childs.clients)
    }

    // Filter result for unique entries
    const filteredGroups = []
    const groupMap = new Map()
    for (const item of subgroups) {
      if (!groupMap.has(item.id)) {
        groupMap.set(item.id, true)
        filteredGroups.push(item)
      }
    }
    subgroups = filteredGroups

    const filteredClients = []
    const clientMap = new Map()
    for (const item of clients) {
      if (!clientMap.has(item.id)) {
        clientMap.set(item.id, true)
        filteredClients.push(item)
      }
    }
    clients = filteredClients

    return { subgroups, clients }
  }
}