summaryrefslogtreecommitdiffstats
path: root/server/api/registration.js
diff options
context:
space:
mode:
authorJannik Schönartz2018-12-11 15:51:58 +0100
committerJannik Schönartz2018-12-11 15:51:58 +0100
commit92e2d90dd65dd9b68b6e779c41993d73be5d6e94 (patch)
tree257ead3922b89512deef8a6dd56e86fd77729933 /server/api/registration.js
parent[registration/idoit] TPM upload functionality + improve hw specs (diff)
downloadbas-92e2d90dd65dd9b68b6e779c41993d73be5d6e94.tar.gz
bas-92e2d90dd65dd9b68b6e779c41993d73be5d6e94.tar.xz
bas-92e2d90dd65dd9b68b6e779c41993d73be5d6e94.zip
Rename registrations in registration.
Diffstat (limited to 'server/api/registration.js')
-rw-r--r--server/api/registration.js472
1 files changed, 472 insertions, 0 deletions
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: <GROUP_ID>, name: <GROUP_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
+}