/* global __appdir */ var path = require('path') var express = require('express') var router = express.Router() var noAuthRouter = express.Router() var db = require(path.join(__appdir, 'lib', 'sequelize')) const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends')) // GET requests. /* * TODO: CURRENTLY TEST FUNCTION */ router.get('/', (req, res) => { db.backend.findOne({ where: { id: 1 } }).then(result => { const b = new ExternalBackends() const instance = b.getInstance(result.type) instance.getClient(result.credentials, {}).then(result => { res.status(200).send(result) }) }) }) module.exports.router = router // GET requests. // POST requests. /* * Returns all root parents or all childs of a group. * * @return: Ipxe menu with a list of groups. */ noAuthRouter.post('/group', (req, res) => { const id = req.body.id var parents = [] if (req.body.parents) parents = JSON.parse(req.body.parents) if (id === '0') { db.group.findAll({ where: { '$parents.id$': null }, include: ['parents'] }).then(groups => { if (groups) { res.send(buildIpxeMenu(id, 'Root', groups, [])) } else { res.status(404).end() } }) } else { db.group.findOne({ where: { id: id }, include: ['parents', 'subgroups', 'clients'] }).then(group => { if (group) { res.send(buildIpxeMenu(id, group.name, group.subgroups, parents)) } else { res.status(404).end() } }) } }) /* * Adds the client in the database and set parents if a parent was selected. */ noAuthRouter.post('/add', (req, res) => { const mac = req.body.mac const uuid = req.body.uuid const ip = req.body.ip const name = req.body.name const parentId = req.body.id db.client.findOne({ where: { uuid: uuid } }).then(client => { if (client) res.status(200).send('#!ipxe\r\necho Client already exists\r\necho Press any key to continue ...\r\nread x\r\nreboot') else { db.client.create({ name: name, ip: ip, mac: mac, uuid: uuid }).then(client => { if (parentId) { client.addGroup(parentId) } res.send('#!ipxe\r\nreboot') }) } }) }) /* * Open api method for setting the registration state of a given uuid. */ noAuthRouter.post('/:uuid/state', (req, res) => { const uuid = req.params.uuid const state = parseInt(req.body.state) db.client.findOne({ where: { uuid: uuid }, include: ['groups'] }).then(client => { // Client not found handling if (client === null) { res.status(404).send({ status: 'INVALID_UUID', error: 'There is no client with the provided UUID.' }) return } // Check if the finished script id (state) matches the current state of the client. if (client.registrationState !== state) { res.status(400).send({ status: 'INVALID_SCRIPT', error: 'This script should not have been executed.' }) return } // If it matches, search for the next script and update the clients registrationState. // Get all group id's of the client. var groupids = [] client.groups.forEach(g => { groupids = [...groupids, g.id] }) // Get the sort value of the current hook. db.registrationhook.findOne({ where: { id: client.registrationState } }).then(hook => { // Gets the list of all groupids inclusive the recursive parents. getRecursiveParents(groupids).then(gids => { // Get the list of all hooks where the parent is null or those who fullfill the group dependency. db.registrationhook.findAll({ where: { '$groups.id$': { $or: [null, gids] }, sortvalue: { $gt: hook.sortvalue } }, include: ['groups'], order: [['sortvalue', 'ASC']] }).then(result => { // Update the client's registration state client.updateAttributes({ registrationState: result[0].id }) res.send({ status: 'SUCCESS' }) }) }) }) }) }) /* * Returns the next bash script for the minilinux. Else empty script. (Empty = bash will make reboot) */ noAuthRouter.get('/:uuid/nexthook', (req, res) => { const uuid = req.params.uuid db.client.findOne({ where: { uuid: uuid } }).then(client => { // Return 404 if the client doesn't exist or it has no registration state. if (client === null || client.registrationState === null) { res.status(404).send() return } db.registrationhook.findOne({ where: { id: client.registrationState } }).then(hook => { if (hook.type !== 'BASH') { res.send() } else { res.send(hook.script) } }) }) }) module.exports.noAuthRouter = noAuthRouter /* * groupids: Array of group ids to get the parents from. * * Returns a list of the grop ids and all recursive ids of their parents. */ function getRecursiveParents (groupIds) { var gids = [] return db.group.findAll({ where: { id: groupIds }, include: ['parents'] }).then(groups => { groups.forEach(group => { group.parents.forEach(parent => { if (!groupIds.includes(parent.id) && !gids.includes(parent.id)) gids = [...gids, parent.id] }) }) if (gids.length === 0) return groupIds else { return getRecursiveParents(gids).then(r => { return groupIds.concat(r) }) } }) } /* * id: id of the current selected location. * name: Name of the current selected location * groups: List of group [{ id: , name: }, ...] * * Build the ipxe menu out of the list of groups. * Used by the manual registration. */ function buildIpxeMenu (id, name, groups, parents) { var script = '#!ipxe\r\n' // script = script.concat(`console --picture \${img} --x 800 --y 600 || shell\r\n`) // Add parent to keep track of the path we clicked through. var parentId = 0 var oldParents = parents.slice(0) if (parents.length > 0) { parentId = oldParents[oldParents.length - 1].id oldParents.length = oldParents.length - 1 } parents.push({ id: id, name: toAscii(name) }) script += `set space:hex 20:20\r\n` script += `set space \${space:string}\r\n` script += `set parents ` + JSON.stringify(parents) + '\r\n\r\n' // Menu var menuscript = '' script += ':start\r\n' script += 'menu Choose the group you want the client to be saved in\r\n' // Parent menu entries. var spacer = '' parents.forEach(parent => { script += 'item --gap ' + spacer + '[' + parent.id + '] ' + parent.name + '\r\n' spacer += `\${space}` }) // Back button script += 'item --key b back ' + spacer + '..\r\n' menuscript += ':back\r\nparams\r\nparam id ' + parentId + '\r\nparam parents ' + JSON.stringify(oldParents) + '\r\n' menuscript += 'chain --replace https://bas.stfu-kthx.net:8888/api/registrations/group##params\r\n\r\n' // Group menu entries. First 1-9 are pressable via key? var counter = '1' groups.forEach(group => { script += 'item --key ' + counter + ' ' + counter + ' ' + spacer + '[' + group.id + '] ' + toAscii(group.name) + '\r\n' menuscript += ':' + counter + '\r\n' + 'params\r\nparam id ' + group.id + `\r\nparam parents \${parents}\r\n` menuscript += 'chain --replace https://bas.stfu-kthx.net:8888/api/registrations/group##params\r\n\r\n' counter++ }) // Menu seperator script += 'item --gap\r\n' // Add client menu script += 'item select Add client to ' + toAscii(name) + '\r\n' menuscript += `:select\r\necho Enter client name\r\nread clientname\r\nparams\r\nparam name \${clientname}\r\n` menuscript += 'param id ' + id + `\r\nparam mac \${net0/mac}\r\nparam uuid \${uuid}\r\nparam ip \${net0/ip}\r\n` menuscript += 'chain --replace https://bas.stfu-kthx.net:8888/api/registrations/add##params\r\n\r\n' // Goto start menu if (id !== '0') { script += 'item reset Go to start\r\n' menuscript += ':reset\r\nparams\r\nparam id ' + 0 + '\r\nchain --replace https://bas.stfu-kthx.net:8888/api/registrations/group##params\r\n\r\n' } // Exit menu script += 'item exit Exit manual registration\r\n' menuscript += ':exit\r\nexit 1\r\n\r\n' // Concat script + menuscript and return it. script += `choose target && goto \${target}\r\n\r\n` script += menuscript return script } function toAscii (string) { string = string.replace('ü', 'ue') string = string.replace('ö', 'oe') string = string.replace('ä', 'ae') return ascii(string) } /* eslint-disable */ var escapable = /[\\"\x00-\x1f\x7f-\uffff]/g /* eslint-enable */ var meta = { // table of character substitutions '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' } function ascii (string) { // If the string contains no control characters, no quote characters, and no // backslash characters, then we can safely slap some quotes around it. // Otherwise we must also replace the offending characters with safe escape // sequences. escapable.lastIndex = 0 return escapable.test(string) ? string.replace(escapable, function (a) { var c = meta[a] return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4) }) : string }