/* 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= * * @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= * * @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= * ?oid= * * @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= * * @return: Returns a list of all the object types of the given backend. [{id: , 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.' }) }) }) // Import objects of a specific type only e.g. Racks for the idoit backend router.getAsync('/:id/import/:type', async (req, res) => { const id = req.params.id const type = req.params.type // Get the backend where the objects are importet from. const backend = await db.backend.findOne({ where: { id: id } }) if (backend) { const ba = new ExternalBackends() const instance = ba.getInstance(backend.type) const groups = JSON.parse(backend.groupTypes).filter(typ => typ.title === type).map(x => parseInt(x.id)) const clients = JSON.parse(backend.clientTypes).filter(typ => typ.title === type).map(x => parseInt(x.id)) // Get a list with all objects in the backend. const objects = await instance.getObjects(backend.credentials) // Filter those objects in groups / clients var groupObjects = [] var clientObjects = [] 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 }) }) // Add all groups in the database. for (const group of groupObjects) { const g = await db.group.create({ name: group.name, description: group.typeName }) await backend.addMappedGroups(g, { through: { externalId: group.id, externalType: group.type } }) } // Add all clients in the databse. for (const client of clientObjects) { const c = await db.client.create({ name: client.name, description: client.typeName }) await backend.addMappedClients(c, { through: { externalId: client.id, externalType: client.type } }) } return 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