summaryrefslogblamecommitdiffstats
path: root/server/api/backends.js
blob: 63b4cb94d515bf43cb66f9d66cf61340e141b095 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                     
                            
                                                                                 
                                                         
                                


                                                   

                                                                        
 





                                                                 

                                                                           


                  

                                                                      
























                                                                                                                                                       


                                                           


                                                                                   

  




                                                 
                                             
                          






                                                                 
                                         


















                                                                        







                                                           
                                                     
                          






                                                                                                              







                                                                    
                                                          

                            






                                                                                                              

  




                                                                                                          
                                                         
                          




                                                                   






























                                                              
 































                                                                                                        
 




                                         
 





                                                             

                                                                              
 



                                                                    
 

                                                  
                                                                                      
 






                                                                                                                                                                     
 

                          
 






































                                                                                                                          
                                                          





















                                                                                                                                                                                   

                         










                                                                                                    




                    
          
 


                                                           
          
        
                                                                                                                


    














                                                                                    


                                                                           




                                      



                                                                                             
                                                               

                              









                                                                                                                           


                                
                                                                         


                                      










                                                                                                                           

  








                                     

                                                     
   
                                                         

                          


                                                                           
 



                                                                            
























                                                                                                              











                                     
                                             
                          
 

                                


                                    
                                                                
                                                                                                







                                                                                         
          











                                                                                                                                              
                                                                                             

      
                                            
                                                                                                                                  







                                                                                        
   
 

















                                                                                                                   









                                                                                                        



                            
 





                                       
                                                 













                                                                                               
 




































                                                                                                   


                              
/* global __appdir */
const path = require('path')
const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends'))
var db = require(path.join(__appdir, 'lib', 'sequelize'))
var express = require('express')
const { decorateApp } = require('@awaitjs/express')
var router = decorateApp(express.Router())
var noAuthRouter = decorateApp(express.Router())
const HttpResponse = require(path.join(__appdir, 'lib', 'httpresponse'))
const log = require(path.join(__appdir, 'lib', 'log'))

// TODO DELETE
noAuthRouter.getAsync('/:id/test', async (req, res) => {
  const id = req.params.id
  const backend = await db.backend.findOne({ where: { id: id } })
  const externalBackends = new ExternalBackends()
  const instance = externalBackends.getInstance(backend.type)
  // const result = await instance.test(backend.credentials)
  const result = await instance.checkIp(backend.credentials, '10.21.9.220')
  res.send(result)
})

// Permission check middleware
router.all(['', '/:id', '/:id/:function'], async (req, res, next) => {
  switch (req.method) {
    case 'GET':
      switch (req.params.function) {
        case 'import':
          if (!await req.user.hasPermission('backends.edit')) return res.status(403).send({ error: 'Missing permission', permission: 'backends.edit' })
          break

        default:
          if (!await req.user.hasPermission('backends.view')) return res.status(403).send({ error: 'Missing permission', permission: 'backends.view' })
          break
      }
      break

    case 'POST': case 'PUT':
      if (!await req.user.hasPermission('backends.edit')) return res.status(403).send({ error: 'Missing permission', permission: 'backends.edit' })
      break

    default:
      return res.status(400).send()
  }

  next()
})

// GET requests.
/*
 * @return: Returns a list of all backends saved in the db.
 */
router.getAsync('/', async (req, res) => {
  const backends = await db.backend.findAll({ attributes: ['id', 'name', 'type'] })
  res.status(200).send(backends)
})

/*
 * ?id=<BACKEND_ID>
 *
 * @return: Returns the information of a backend.
 */
router.getAsync('/:id', async (req, res) => {
  const id = req.params.id
  const backend = await db.backend.findOne({ where: { id: id } })

  // Remove password values from credentials.
  const externalBackends = new ExternalBackends()
  const instance = externalBackends.getInstance(backend.type)
  let credentialTypes = instance.getCredentials()

  // Get the ids of the 'password' fields
  let censorIds = []
  credentialTypes.forEach(function f (element) {
    if (element.type === 'switch') {
      element.elements.forEach(f)
    } else if (element.type === 'password') censorIds.push(element.id)
  })

  // Filter the password values. No need for the frontend to have those.
  let credentials = JSON.parse(backend.credentials)
  credentials.forEach(function x (e) {
    if (e.elements) e.elements.forEach(x)
    else if (censorIds.includes(e.id)) e.value = ''
  })

  res.status(200).send({
    id: id,
    name: backend.name,
    type: backend.type,
    credentials: credentials
  })
})

/*
 * ?id=<Backend_ID>
 *
 * @return: Returns a list with all objects of the backend.
 */
router.getAsync('/:id/objects', async (req, res) => {
  const id = req.params.id
  const backend = await db.backend.findOne({ where: { id: id } })
  if (backend) {
    const ba = new ExternalBackends()
    const instance = ba.getInstance(backend.type)
    const result = await instance.getObjects(backend.credentials)
    res.status(200).send(result)
  } else res.status(500).send({ error: 'INVALID_BACKEND_ID', message: 'The provided backend id is invalid.' })
})

/*
 * ?id=<Backend_ID>
 * ?oid=<OBJECT_ID>
 *
 * @return: Returns information about a given object and all childs.
 */
router.getAsync('/:id/objects/:oid', async (req, res) => {
  const id = req.params.id
  const oid = req.params.oid
  const backend = await db.backend.findOne({ where: { id: id } })
  if (backend) {
    const ba = new ExternalBackends()
    const instance = ba.getInstance(backend.type)
    const result = await instance.getObject(backend.credentials, oid)
    res.status(200).send(result)
  } else res.status(500).send({ error: 'INVALID_BACKEND_ID', message: 'The provided backend id is invalid.' })
})

/*
 * ?id=<Backend_ID>
 *
 * @return: Returns a list of all the object types of the given backend. [{id: <id>, title: <title>}, ...]
 */
router.getAsync('/:id/objecttypes', async (req, res) => {
  const id = req.params.id
  const backend = await db.backend.findOne({ where: { id: id } })
  const ba = new ExternalBackends()
  const instance = ba.getInstance(backend.type)
  const result = await instance.getObjectTypes(backend.credentials)
  res.status(200).send(result)
})

/*
 * ?id=<Backend_ID>
 *
 * @return: Returns the sync settings saved in the db.
 */
router.get('/:id/syncsettings', (req, res) => {
  const id = req.params.id
  var types = {}
  db.backend.findOne({ where: { id: id } }).then(backend => {
    types.groups = JSON.parse(backend.groupTypes)
    types.clients = JSON.parse(backend.clientTypes)
    types.sync = backend.sync
    res.status(200).send(types)
  })
})

/*
 * ?id=<Backend_ID>
 *
 * @return: Returns a list of sync types the backend supports.
 */
router.get('/:id/synctypes', (req, res) => {
  const id = req.params.id
  db.backend.findOne({ where: { id: id } }).then(backend => {
    const ba = new ExternalBackends()
    const instance = ba.getInstance(backend.type)
    res.status(200).send(instance.getSyncTypes())
  })
})

// vvvv TEST MEHTOD !!!! <----------------------------------------------------------
noAuthRouter.get('/:id/test/getObjectTree', (req, res) => {
  const id = req.params.id
  db.backend.findOne({ where: { id: id } }).then(backend => {
    if (backend) {
      const ba = new ExternalBackends()
      const instance = ba.getInstance(backend.type)

      // Get the backend with all the mapped groups. ! Only groups can have childs !
      db.backend.findOne({ where: { id: backend.id }, include: ['mappedGroups'] }).then(b => {
        var objectData = []
        // Put all groups in the array to make a one session call which returns all needed informations.
        b.mappedGroups.forEach(mGroup => {
          const mG = mGroup.backend_x_group
          const eid = mG.externalId
          const gid = mGroup.id
          objectData.push({ eid: eid, gid: gid })
        })
        // Get all the information needed from the backend instance. (For all object ids in the array)
        var promise = new Promise(function (resolve) {
          // resolve(instance.getDataTree(backend.credentials, objectData.slice(0, 200)))
          resolve(instance.getDataTree(backend.credentials, objectData))
        })
        promise.then(data => {
          res.send({ length: data.length, result: data })
        })
      })
    } else res.send({ error: 'error' })
  })
})
module.exports.noAuthRouter = noAuthRouter
// ^^^^ TEST MEHTOD !!!! <----------------------------------------------------------

/*
 * id: <BACKEND_ID>
 */
router.get('/:id/import', (req, res) => {
  const id = req.params.id

  // Get the backend where the objects are importet from.
  db.backend.findOne({ where: { id: id } }).then(backend => {
    if (backend) {
      var endRequest = []
      const ba = new ExternalBackends()
      const instance = ba.getInstance(backend.type)
      const groups = JSON.parse(backend.groupTypes).map(x => parseInt(x.id))
      const clients = JSON.parse(backend.clientTypes).map(x => parseInt(x.id))

      // Get a list with all objects in the backend.
      const objectPromise = new Promise(function (resolve, reject) {
        resolve(instance.getObjects(backend.credentials))
      })

      objectPromise.then(result => {
        // Check for the not implemented exception
        if (result.error === 'NOT_IMPLEMENTED_EXCEPTION') res.status(501).send(result)

        // Filter those objects in groups / clients
        var groupObjects = []
        var clientObjects = []
        result.objects.filter(obj => {
          if (groups.find(x => x === obj.type)) groupObjects.push({ id: obj.id, name: obj.title, type: obj.type, typeName: obj.type_title, sysid: obj.sysid })
          else if (clients.find(y => y === obj.type)) clientObjects.push({ id: obj.id, name: obj.title, type: obj.type, typeName: obj.type_title, sysid: obj.sysid })
        })

        var promises = []
        var promises2 = []

        // Add all groups in the database.
        groupObjects.forEach(group => {
          // Insert the group.
          promises.push(db.group.create({ name: group.name, description: group.typeName }).then(g => {
            // Insert the backend_x_group relation.
            promises2.push(backend.addMappedGroups(g, { through: { externalId: group.id, externalType: group.type } }))
          }))
        })

        // Add all clients in the databse.
        clientObjects.forEach(client => {
          // Insert the client.
          promises.push(db.client.create({ name: client.name, description: client.typeName }).then(c => {
            // Insert the backend_x_client relation.
            promises2.push(backend.addMappedClients(c, { through: { externalId: client.id, externalType: client.type } }))
          }))
        })

        // Wait till all clients / groups are created and all mapping operations are done. Then add childs.
        Promise.all(promises).then(() => {
          Promise.all(promises2).then(() => {
            // Get the backend with all the mapped groups. ! Only groups can have childs !
            db.backend.findOne({ where: { id: backend.id }, include: ['mappedGroups'] }).then(b => {
              var objectData = []
              // Put all groups in the array to make a one session call which returns all needed informations.
              b.mappedGroups.forEach(mGroup => {
                const mG = mGroup.backend_x_group
                const eid = mG.externalId
                const gid = mGroup.id
                objectData.push({ eid: eid, gid: gid })
              })

              // Get all the information needed from the backend instance. (For all object ids in the array)
              var promise = new Promise(function (resolve) {
                resolve(instance.getDataTree(backend.credentials, objectData))
              })

              promise.then(data => {
                // Check for the not implemented exception
                if (data.error) res.status(501).send(data)

                data.forEach(obj => {
                  var groupChildsToAdd = []
                  var clientChildsToAdd = []
                  var prom = []

                  // Put all clientChilds in the clientList and all groupChilds in the groupList.
                  obj.childs.forEach(child => {
                    if (groups.find(x => x === child.type)) {
                      // Get the group id out of the externalId.
                      prom.push(db.backend.findOne({ where: { id: backend.id, '$mappedGroups.backend_x_group.externalId$': child.id }, include: ['mappedGroups'] }).then(ba => {
                        // The externalId should only be once in the db.
                        if (ba.mappedGroups.length === 1) {
                          groupChildsToAdd.push(ba.mappedGroups[0].backend_x_group.groupId)
                        }
                      }))
                    } else if (clients.find(x => x === child.type)) {
                      // Get the client id out of the externalId.
                      prom.push(db.backend.findOne({ where: { id: backend.id, '$mappedClients.backend_x_client.externalId$': child.id }, include: ['mappedClients'] }).then(ba => {
                        // The externalId should only be once in the db.
                        if (ba.mappedClients.length === 1) {
                          clientChildsToAdd.push(ba.mappedClients[0].backend_x_client.clientId)
                        }
                      }))
                    }
                  })

                  // After all the group and client ids are collected. Add them as subgroup / client
                  Promise.all(prom).then(() => {
                    endRequest.push(db.group.findOne({ where: { id: obj.gid } }).then(group => {
                      if (group) {
                        group.addSubgroups(groupChildsToAdd)
                        group.addClients(clientChildsToAdd)
                      }
                    }))
                  })
                })
              })
            })
          })
        })

        // If all requests are fullfilled. End the request.
        Promise.all(endRequest).then(() => {
          res.status(200).send({ status: 'SUCCESS' })
        })
      })
    } else res.status(500).send({ error: 'INVALID_BACKEND_ID', message: 'The provided backend id is invalid.' })
  })
})

/*
 * Adds the client mapping from the backend to the client with the same mac address.
 */
router.getAsync('/:id/mapping', async (req, res) => {
  const id = req.params.id
  const backend = await db.backend.findOne({ where: { id: id } })

  if (backend) {
    const b = new ExternalBackends()
    const instance = b.getInstance(backend.type)
    const objects = await instance.getObjects(backend.credentials)

    for (let index in objects) {
      const object = objects[index]
      const client = await db.client.findOne({ where: { mac: object.mac } })
      // TODO: external Type?
      let data = { externalId: object.id }
      if (client) await backend.addMappedClients(client, { through: data })
    }
    return res.status(200).send()
  } else return res.status(500).send()
})

// No auth router for now but those client based methods should better be in the clients api.
// TODO: Move to clients?
// TODO: receive files should be authenticated!
// Gets a list of all the files uploaded and connected to the client.
noAuthRouter.getAsync('/:id/:uuid/files', async (req, res) => {
  const id = req.params.id
  const uuid = req.params.uuid
  const backend = await db.backend.findOne({ where: { id: id, '$mappedClients.uuid$': uuid }, include: ['mappedClients'] })
  if (backend && backend.mappedClients.length === 1) {
    const externalId = backend.mappedClients[0].backend_x_client.externalId
    const b = new ExternalBackends()
    const instance = b.getInstance(backend.type)
    const fileList = await instance.getFileList(backend.credentials, externalId)
    res.send({ success: true, data: { backendId: id, clientUUID: uuid, externalId: externalId, fileList: fileList } })
  } else {
    res.send({ success: false, error: 'CLIENT_NOT_FOUND', message: 'Couldn\'t find the client' })
  }
})

// Returns the content of a file
noAuthRouter.getAsync('/:id/:uuid/files/:filename', async (req, res) => {
  const id = req.params.id
  const uuid = req.params.uuid
  const filename = req.params.filename
  const backend = await db.backend.findOne({ where: { id: id, '$mappedClients.uuid$': uuid }, include: ['mappedClients'] })
  if (backend && backend.mappedClients.length === 1) {
    const externalId = backend.mappedClients[0].backend_x_client.externalId
    const b = new ExternalBackends()
    const instance = b.getInstance(backend.type)
    const file = await instance.getFile(backend.credentials, externalId, filename)
    file.decoded = Buffer.from(file.value, 'base64').toString('UTF-8')
    res.send(file)
  } else {
    res.send({ success: false, error: 'CLIENT_NOT_FOUND', message: 'Couldn\'t find the client' })
  }
})

// POST requests

/*
 * id: <BACKEND_ID>
 * <OR>
 * name: <BACKEND_NAME>
 * type: <BACKEND_TYPE>
 * credentials: <BACKEND_CREDENTIALS>
 *
 * If the id is set, the backend in the db is testet.
 * Else the backend is posted in the request.
 */
router.postAsync('/:id/connection', async (req, res) => {
  const id = req.params.id

  let backend = null
  if (id !== '0') backend = await db.backend.findOne({ where: { id: id } })
  else backend = req.body

  // Creating the backend instance and calling the specific checkConnection.
  const b = new ExternalBackends()
  const instance = b.getInstance(backend.type)
  const response = await instance.checkConnection(backend.credentials)

  if (response.error) {
    console.log(response)
    log({
      category: 'ERROR_BACKEND_CHECKCONNECTION',
      description: '[' + backend.id + '] ' + backend.name + ': Connection error. [' + response.error + ']\n' +
                   'ID: ' + backend.id + '\n' +
                   'Name: ' + backend.name + '\n' +
                   'Type: ' + backend.type + '\n' +
                   'Error: ' + response.error + '\n' +
                   'Message: ' + response.message,
      userId: req.user.id
    })
    return res.status(500).send(response)
  } else if (response) {
    log({
      category: 'BACKEND_CHECKCONNECTION',
      description: '[' + backend.id + '] ' + backend.name + ': Connection successfull.\n' +
                   'ID: ' + backend.id + '\n' +
                   'Name: ' + backend.name + '\n' +
                   'Type: ' + backend.type,
      userId: req.user.id
    })
    res.status(200).send()
  }
})

// PUT requests

/*
 * id: <BACKEND_ID>
 * name: <BACKEND_NAME>
 * type: <BACKEND_TYPE>
 * credentials: <BACKEND_CREDENTIALS>
 *
 * Creates or updates the backend.
 */
router.putAsync('/:id', async (req, res) => {
  const id = req.params.id

  // Save credentials in the db.
  const backend = req.body

  if (id === '0') {
    // Insert new backend in the db.
    const credentialString = JSON.stringify(backend.credentials)
    db.backend.create({ name: backend.name, type: backend.type, credentials: credentialString })
    log({
      category: 'BACKEND_CREATE',
      description: '[' + id + '] ' + backend.name + ': Backend successfully created.\n' +
                   'ID: ' + id + '\n' +
                   'Name: ' + backend.name + '\n' +
                   'Type: ' + backend.type,
      userId: req.user.id
    })
  } else {
    const backendDb = await db.backend.findOne({ where: { id: id } })
    if (!backendDb) return res.status(404).send({ error: 'BACKEND_NOT_FOUND', message: 'No backend was found with the provided backend id.' })

    // Merge the old and the new credential values (passwords are only send if they changed)
    let credentials = JSON.parse(backendDb.credentials)
    let newAttributes = {}
    backend.credentials.forEach(function x (e) {
      if (e.elements) e.elements.forEach(x)
      newAttributes[e.id] = e.value
    })
    credentials.forEach(function f (element) {
      if (element.elements) element.elements.forEach(f)
      if (newAttributes.hasOwnProperty(element.id)) element.value = newAttributes[element.id]
    })

    // Update an existing backend in the db.
    db.backend.update({ name: backend.name, type: backend.type, credentials: JSON.stringify(credentials) }, { where: { id: id } })
    log({
      category: 'BACKEND_EDIT',
      description: '[' + id + '] ' + backend.name + ': Backend successfully edited.\n' +
                   'ID: ' + id + '\n' +
                   'Name: ' + backend.name + '\n' +
                   'Type: ' + backend.type,
      userId: req.user.id
    })
  }

  res.status(200).send('success')
})

/*
 * id: <BACKEND_ID>
 * groups: <JSON_OF_ASSIGNED_GROUPS> (list of backend types)
 * clients: <JSON_OF_ASSIGNED_CLIENTS> (list of backend types)
 * sync: <SYNC_OPTION>
 *
 * Saves the group / clients assigned object types in the database and sync type.
 */
router.put('/:id/syncsettings', (req, res) => {
  const id = req.params.id
  const groups = JSON.stringify(req.body.groups)
  const clients = JSON.stringify(req.body.clients)
  const sync = req.body.sync
  db.backend.findOne({ where: { id: id } }).then(backend => {
    db.backend.update({ groupTypes: groups, clientTypes: clients, sync: sync }, { where: { id: id } }).then(() => {
      log({
        category: 'BACKEND_SYNCSETTINGS_EDIT',
        description: '[' + backend.id + '] ' + backend.name + ': Sync settings successfully edited.\n' +
                     'ID: ' + backend.id + '\n' +
                     'Name: ' + backend.name + '\n' +
                     'Type: ' + backend.type + '\n' +
                     'Groups: ' + req.body.groups + '\n' +
                     'Clients: ' + req.body.clients,
        userId: req.user.id
      })
      res.status(200).send()
    })
  })
})

// DELETE requests // TODO:
/*
 * id: <BACKEND_ID>
 *
 * Deletes the backend to the given id.
 */
router.postAsync('/delete', async (req, res) => {
  req.body.ids = req.body.id
  // await db.backend.destroy({ where: { id: backendIds } })
  // res.status(200).send('success')

  const user = await db.user.findOne({ where: { id: req.user.id } })
  // Only need to log batch request if there is more than one backend to delete.
  if (req.body.ids.length > 1) {
    await log({
      category: 'BACKEND_BATCH_DELETE',
      description: 'Batch deletion of ' + req.body.ids.length + ' backends initiated by user.',
      user,
      userId: req.user.id
    })
  }

  let deletionCounter = 0
  // Delete every backend on its own, to get a better log
  for (let index in req.body.ids) {
    const backend = await db.backend.findOne({ where: { id: req.body.ids[index] } })
    const count = await db.backend.destroy({ where: { id: req.body.ids[index] } })
    if (count !== 1) {
      await log({
        category: 'ERROR_BACKEND_DELETE',
        description: '[' + backend.id + '] ' + backend.name + ': Backend could not be deleted.\n' +
                     'ID: ' + backend.id + '\n' +
                     'Name: ' + backend.name + '\n' +
                     'Type: ' + backend.type,
        user,
        userId: req.user.id
      })
    } else {
      await log({
        category: 'BACKEND_DELETE',
        description: '[' + backend.id + '] ' + backend.name + ': Backend successfully deleted.\n' +
                     'ID: ' + backend.id + '\n' +
                     'Name: ' + backend.name + '\n' +
                     'Type: ' + backend.type,
        user,
        userId: req.user.id
      })
      deletionCounter++
    }
  }
  if (req.body.ids.length > 1) {
    log({
      category: 'BACKEND_BATCH_DELETE',
      description: deletionCounter + '/' + req.body.ids.length + ' backends successfully deleted.',
      user,
      userId: req.user.id
    })
  }
  HttpResponse.successBatch('deleted', 'backend', deletionCounter).send(res)
})

module.exports.router = router