summaryrefslogblamecommitdiffstats
path: root/server/api/registration.js
blob: 39f069f1b6c01b8906245b065d77e65d7c034fc8 (plain) (tree)
1
2
3
4
5
6
7
8

                          



                                                         
                                                                                 
                                                                                               



                
                                
   
                                     
                                              
                 
       
 

                                                           

                                               
                                                               

                                  
       






                                                                                     

  


























































                                                                                                                                                    






                              
                                                     
  
                                            
   

                                           
                  
                                                              


                                                                                                
                                                       






                                                                                                         
                                                                         





                             
 
  
                                                                                                                       
   
                                         
                                    


                            
                          
                              



                                    
                                                               
                                                                                                     
          

                                         
                                                 
                                                                                                                                           
                         
                                        
           

                                            
                                                                            
                                             
                                          
                                                     
                                          

                                                                                              
                                     





                                                                                                                            
                                                                                                              
          

        


    

                                                  

                              

           


                                                   

        


                                                   
                                                  
                                     



























                                                                                                                                                                                    


                                                               
                                         

                                       



                                                                           
                                          

                                                      
                                   

                                                               
                                          



                                                                                                                       






                                                  
  
                                              
   
                                                
                                                                          


                                                   


  

                                                                      
                                                   
                              
                                  
                                                                                    





                                                                                                           

                                                                                    




                                                                                                             




                                        
 

                                                                                           



                                                                 
          
                                       

        

















                                                                                                     
                                               





                             
                                          
 
  







                                                                                                     


                                                                                                                         







                                                  





















                                                                                                   







                                                                      
                                                  
                           


                                                                                     









                                                                 

         
                     

                                                                          



                             

                                                                                    

    


                                                                                                                      
                                                                                       
 
                                                         

                           

                                                                                                                           
                                                                                         

             
 


                            
                    


                                                                                                                  
                                                                                     

                    
                   
                                          
                                                                                                                                   
   

              

                                                    

                                              

                                                      

               


































                                                                            
/* 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
}