From 92e2d90dd65dd9b68b6e779c41993d73be5d6e94 Mon Sep 17 00:00:00 2001 From: Jannik Schönartz Date: Tue, 11 Dec 2018 14:51:58 +0000 Subject: Rename registrations in registration. --- server/api/registration.js | 472 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 472 insertions(+) create mode 100644 server/api/registration.js (limited to 'server/api/registration.js') diff --git a/server/api/registration.js b/server/api/registration.js new file mode 100644 index 0000000..29e47bd --- /dev/null +++ b/server/api/registration.js @@ -0,0 +1,472 @@ +/* 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')) +const backendHelper = require(path.join(__appdir, 'lib', 'external-backends', 'backendhelper')) + +// GET requests. + +/* + * TODO: CURRENTLY TEST FUNCTION + */ +noAuthRouter.get('/', (req, res) => { + // backendHelper.deleteClients().then(r => { + // res.send(r) + // }) + + /* + 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) + }) + }) */ + db.backend.findOne({ where: { id: 3 } }).then(result => { + const b = new ExternalBackends() + const instance = b.getInstance(result.type) + instance.uploadTpm(result.credentials, 99696, 'I-123-d12', null).then(result => { + res.status(200).send(result) + }) + }) +}) + +/* + * Returns all registration hooks sorted by sortValue. + * + * @return: List of registration hooks + */ +router.get('/hooks', (req, res) => { + db.registrationhook.findAll({ order: [ ['sortValue', 'ASC'] ], include: [{ model: db.group, as: 'groups', attributes: ['id'] }] }).then(hooks => { + res.send(hooks) + }) +}) + +// POST requests. + +/* + * Reorders the registration hooks based on an array of hook ids. + */ +router.post('/hookorder', async (req, res) => { + var idSortvalueMap = {} + req.body.ids.forEach((id, index) => { + idSortvalueMap[id] = index + }) + var hooks = await db.registrationhook.findAll() + var promises = [] + hooks.forEach(hook => { + promises.push(hook.update({ sortvalue: idSortvalueMap[hook.id] })) + }) + await Promise.all(promises) + res.end() +}) + +router.post(['/hooks', '/hooks/:id'], async (req, res) => { + var item = { + name: req.body.name, + description: req.body.description, + type: req.body.type, + script: req.body.script + } + var hook = null + if (req.params.id > 0) { + hook = await db.registrationhook.findOne({ where: { id: req.params.id } }) + if (hook) await hook.update(item) + } else { + var maxSortvalue = await db.registrationhook.max('sortvalue') + item.sortvalue = maxSortvalue ? maxSortvalue + 1 : 1 + hook = await db.registrationhook.create(item) + } + if (hook) { + hook.setGroups(req.body.groups) + res.send({ id: hook.id }) + } + res.end() +}) + +// DELETE requests. + +router.delete(['/hooks', '/hooks/:id'], (req, res) => { + db.registrationhook.destroy({ where: { id: req.params.id || req.body.ids } }).then(count => { res.send({ count }) }) +}) + +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 to the database and set parents if a parent was selected. Calls addClient for all external-backends. + */ +noAuthRouter.post('/add', (req, res) => { + const feedback = req.body.feedback + const mac = req.body.mac + const uuid = req.body.uuid + const ip = req.body.ip + var name = req.body.name + const parentId = req.body.id + const purpose = req.body.purpose + + if (!name) name = 'Client_' + uuid + + db.client.findOne({ where: { uuid: uuid } }).then(client => { + if (client) res.send(`#!ipxe\nchain https://bas.intra.uni-freiburg.de/api/configloader/\${uuid}`) + else { + var groupids = [] + if (parentId) groupids = [parentId] + getNextHookScript(groupids).then(resId => { + db.client.create({ name: name, description: 'Client', ip: ip, mac: mac, uuid: uuid, registrationState: resId }).then(newClient => { + if (parentId) { + newClient.addGroup(parentId) + } + + // Add the client to the backends. + var c = { title: name, uuid: uuid, network: { mac: mac, ip: ip } } + if (parentId) c.parentId = parentId + if (purpose) c.purpose = purpose + backendHelper.addClient(c).then(result => { + if (feedback) res.send(result) + result.forEach(response => { + // If the object was created we need to make the objectid / external id mapping. + if (response.success) { + db.backend.findOne({ where: { id: response.backendId }, include: ['mappedClients'] }).then(backend => { + backend.addMappedClients(newClient, { through: { externalId: response.id, externalType: response.type } }) + }) + } + }) + }) + if (!feedback) res.send(`#!ipxe\nchain https://bas.intra.uni-freiburg.de/api/configloader/\${uuid}`) + }) + }) + } + }) +}) + +noAuthRouter.post('/:uuid/update', (req, res) => { + const uuid = req.params.uuid + const name = req.body.name + const parentId = req.body.id + + // System + const sysManufacturer = req.body.sys_manufacturer + const sysModel = req.body.sys_model + const sysSerial = req.body.sys_serial + + // CPU + const cpuModel = req.body.cpu_model + const cpuManufacturer = req.body.cpu_manufacturer + const cpuType = req.body.cpu_type + var cpuFrequency = req.body.cpu_frequency / 1000 + const cpuCores = req.body.cpu_cores + + // RAM + var ramSize = req.body.ram_size.split('\n') + var ramManufacturer = req.body.ram_manufacturer.split('\n') + var ramFormfactor = req.body.ram_formfactor.split('\n') + var ramType = req.body.ram_type.split('\n') + var ramIsEcc = req.body.ram_isecc.replace('Error Correction Type: ', '') + var ramModules = [] + // Build ram array + for (var ram in ramSize) { + if (ramSize[ram].replace('Size: ', '') !== 'No Module Installed') { + const size = ramSize[ram].replace('Size: ', '').split(' ') + const title = ramFormfactor[ram].replace('Form Factor: ', '') + if (ramIsEcc === 'Single-bit ECC') title += '-ECC' + + var ramModule = { capacity: size[0], unit: size[1], manufacturer: ramManufacturer[ram].replace('Manufacturer: ', ''), title: title, type: ramType[ram].replace('Type: ', '') } + ramModules.push(ramModule) + } + } + // ramTmpSize = ramSize.split('\n')[0].replace('Size: ', '').split(' ') + // ramSize = ramTmpSize[0] + // ramUnit = ramTmpSize[1] + // ramManufacturer = ramManufacturer.split('\n')[0].replace('Manufacturer: ', '') + // ramType = ramType.split('\n')[0].replace('Type: ', '') + // ramFormfactor = ramFormfactor.split('\n')[0].replace('Form Factor: ', '') + // var ramTitle = ramFormfactor + // if (ramIsEcc === "Single-bit ECC") ramTitle += '-ECC' + // var ram = { capacity: ramSize, manufacturer: ramManufacturer, title: ramTitle, type: ramType, formfactor: ramFormfactor, unit: ramUnit } + + db.client.findOne({ where: { uuid: uuid } }).then(client => { + client.update({ name: name }) + var c = { uuid: uuid, id: client.id } + if (name) c.title = name + if (parentId) c.parentId = parentId + + // System data. Sometime just string with whitespaces only. + c.system = {} + if (/\S/.test(sysManufacturer)) c.system.manufacturer = sysManufacturer + else c.system.manufacturer = "Not set" + + if (/\S/.test(sysModel)) c.system.model = sysModel + else c.system.model = "Not set" + + if (/\S/.test(sysSerial)) c.system.serialnumber = sysSerial + else c.system.serialnumber = "Not set" + + // TODO: MULTI GPU's ?! + c.cpu = { model: cpuModel, manufacturer: cpuManufacturer, type: cpuType, frequency: cpuFrequency, cores: cpuCores } + c.ram = ramModules + + backendHelper.updateClient(c).then(result => { + res.send(result) + }) + }) +}) + +/* + * Mehtod for uploading the tpm key and stuff. + */ +noAuthRouter.put('/:uuid/files', (req, res) => { + db.client.findOne({ where: { uuid: req.params.uuid }}).then(client => { + backendHelper.uploadFiles(client.id, req.files) + res.send() + }) +}) + +/* + * Open api method for setting the registration state of a given uuid. + */ +noAuthRouter.post('/:uuid/success', (req, res) => { + const uuid = req.params.uuid + const id = parseInt(req.body.id) + 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 (id) matches the current state of the client. + if (client.registrationState !== id) { + 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 => { + getNextHookScript(groupids, hook.sortvalue).then(resID => { + // Update the client's registration state + client.updateAttributes({ + registrationState: resID + }) + 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.set('id', client.registrationState) + res.send(hook.script) + } + }) + }) +}) + +module.exports.noAuthRouter = noAuthRouter + +/* + * parentIds: + * sortvalue: + * + */ +function getNextHookScript (groupids, sortvalue) { + // Gets the list of all groupids inclusive the recursive parents. + return getRecursiveParents(groupids).then(gids => { + // Get the list of all hooks where the parent is null or those who fullfill the group dependency. + var options = { where: { '$groups.id$': { $or: [null, gids] } }, include: ['groups'], order: [['sortvalue', 'ASC']] } + if (sortvalue !== undefined) options.where.sortvalue = { $gt: sortvalue } + return db.registrationhook.findAll(options).then(result => { + var resID = null + if (result.length >= 1) resID = result[0].id + return resID + }) + }) +} + +/* + * 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 basUrl = 'https://bas.intra.uni-freiburg.de' + 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 ' + basUrl + '/api/registration/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 ' + basUrl + '/api/registration/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 ' + basUrl + '/api/registration/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 ' + basUrl + '/api/registration/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 +} -- cgit v1.2.3-55-g7522