/* global __appdir */ const path = require('path') const ExternalBackends = require(path.join(__appdir, 'lib', 'external-backends')) var axios = require('axios') class IdoitBackend extends ExternalBackends { // ############################################################################ // ######################## needed functions ################################# /* * Returns the credential structure / fields, defined in the backends. */ getCredentials () { return [ { type: 'text', id: 1, name: 'API url', icon: 'link' }, { type: 'password', id: 2, name: 'API token', icon: 'vpn_key', show: false }, { type: 'switch', id: 3, name: 'Login', icon: 'lock_open', elements: [ { type: 'text', id: 4, name: 'username', icon: 'person_outline' }, { type: 'password', id: 5, name: 'password', icon: 'lock', show: false } ] } ] } /* * Checks the connection of a given backend. * idoIT: Checks if we can get a valid session id. If so the connection must be successfull. * * return: { success: , status: '', error: '' } */ checkConnection (credentials) { var c = this.mapCredentials(credentials) return this.getSession(c) } // Return the list of object types created in iDoIT. async getObjectTypes (credentials) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] // Headers var headers = { 'X-RPC-Auth-Session': sid } // Params var params = { 'apikey': c.apikey, 'language': 'en' } var result = {} result.types = await this.axiosRequest(c.url, 'cmdb.object_types', params, headers) result.types = result.types.data.result var types = [] result.types.forEach(type => { types.push({ id: type.id, title: type.title }) }) return types } getSyncTypes () { return ['None', 'Two-Way', 'Upload Only', 'Upload Then Delete', 'Upload Mirror', 'Download Only', 'Download Then Delete', 'Download Mirror'] } /* * Gets all objects and searches for the ip. Because idoit cannot search for ip or mac. * */ async getClient (credentials, client) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] // var uuid = client.uuid // Headers var headers = { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } // Params to get all clients. var params = { 'apikey': c.apikey, 'language': 'en', 'filter': { 'type': 'C__OBJTYPE__CLIENT' } } // Get all client objects. var clients = await this.axiosRequest(c.url, 'cmdb.objects', params, headers) clients = clients.data.result var bodies = [] clients.forEach(client => { bodies.push({ 'version': '2.0', 'method': 'cmdb.category.read', 'params': { 'objID': client.id, 'apikey': c.apikey, 'language': 'en', 'category': 'C__CATG__MODEL' }, 'id': client.id }) }) var config = { timeout: 180000, headers: { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } } var requestClient = await axios.post(c.url, bodies, config) requestClient = requestClient.data.filter(x => x.result.length >= 1 && x.result[0].productid === client.uuid) return requestClient.length >= 1 ? { succes: true, data: requestClient[0].result[0].objID } : { success: false, error: 'NO_CLIENT_FOUND', msg: 'There is no client matching with the provided uuid.' } } async getObjects (credentials) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] // Headers var headers = { 'X-RPC-Auth-Session': sid } // Params var params = { 'apikey': c.apikey, 'language': 'en' } var result = {} result.objects = await this.axiosRequest(c.url, 'cmdb.objects', params, headers) result.objects = result.objects.data.result return result } async getObject (credentials, oid) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] // Headers var headers = { 'X-RPC-Auth-Session': sid } // Params var params = { 'id': oid, 'apikey': c.apikey, 'language': 'en' } var result = {} result.object = await this.axiosRequest(c.url, 'cmdb.object.read', params, headers) result.childs = await this.axiosRequest(c.url, 'cmdb.location_tree', params, headers) result.object = result.object.data.result result.childs = result.childs.data.result return result } async deleteObjects (credentials, objectIds) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] var config = { timeout: 180000, headers: { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } } var bodies = [] objectIds.forEach(oid => { // Body bodies.push({ 'version': '2.0', 'method': 'cmdb.object.delete', 'params': { 'id': oid, 'apikey': c.apikey, 'language': 'en' }, 'id': oid }) }) var requestDelete = await axios.post(c.url, bodies, config) return { success: requestDelete.success, data: requestDelete.data } } // Function to use the same session for multiple requests async getDataTree (credentials, objects) { var c = this.mapCredentials(credentials) // LOGIN // Open and get a session const body = { 'version': '2.0', 'method': 'idoit.login', 'params': { 'apikey': c.apikey, 'language': 'en' }, 'id': 1 } // Headers var headers = {} // Optional credentials if (c.login) { headers['X-RPC-Auth-Username'] = c.username headers['X-RPC-Auth-Password'] = c.password } var config = { timeout: 180000, headers: headers } // Make a login request and see if we are authenticated. var log = await axios.post(c.url, body, config) log = log.data.result var sid = log['session-id'] // Headers config.headers = { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } body.method = 'cmdb.location_tree' // Go through the objects and get all the childs. var promises = [] var counter = 0 var index = 0 var gids = {} // Prepare all the batch request bodies. var e for (e in objects) { // Pack 400 requests in one batch request to reduce api calls. if (counter >= 400) { counter = 0 index++ } if (counter === 0) promises[index] = [] counter++ var element = objects[e] const bod = { 'version': '2.0', 'method': 'cmdb.location_tree', 'params': { 'id': element.eid, 'apikey': c.apikey, 'language': 'en' }, 'id': element.eid } promises[index].push(bod) gids[element.eid] = element.gid } // Send all the batch request and post proccess the result into one array. var result = [] var p var counter2 = 1 for (p in promises) { // TODO: Remove // Counter for getting an overview, how far the requests are. console.log(counter2 + '/' + promises.length + ' requests send') counter2++ // Send the request. var requestResult = await axios.post(c.url, promises[p], config) const args = requestResult.data // Post process the data. var a for (a in args) { result.push({ gid: gids[args[a].id], childs: args[a].result }) } } return result } /* * Adds the client to the backend. * * credentials: * The client parameters are all optional. If the client has an id the object is not created but the categories of the object. * client: { * id: , title: , parentId: , * network: { mac: , ip: } * } */ async addClient (credentials, client) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] var config = { timeout: 180000, headers: { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } } var bodies = [] var clientid if (!client.id) { // Create the object in idoIT. var params = { 'type': 'C__OBJTYPE__CLIENT', 'title': client.title, 'apikey': c.apikey, 'language': 'en' } // 1 = Production // 2 = Test // 3 = Quality Assurance // 5 = PVS // 7 = Pool PC // 8 = Mitarbeiter Arbeitsplatz if (client.purpose === 'Pool PC') params.purpose = 7 var requestCreate = await this.axiosRequest(c.url, 'cmdb.object.create', params, config.headers) clientid = requestCreate.data.result.id } else { clientid = client.id // Update the client title var bodyGeneral = { 'version': '2.0', 'method': 'cmdb.object.update', 'params': { 'id': clientid, 'title': client.title, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_title' } if (client.purpose === 'Pool PC') bodyGeneral.params.purpose = 7 bodies.push(bodyGeneral) } if (client.uuid) { // Update the productid to the uuid. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.update', 'params': { 'objID': clientid, 'category': 'C__CATG__MODEL', 'data': { 'productid': client.uuid }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_uuid' }) } if (client.network) { // Update the object. MAC-Address if (client.network.mac) { // First read if there are current entries. // Delete the previous entries. // Finally create the new entry. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': clientid, 'category': 'C__CATG__NETWORK_PORT', 'data': { 'category_id': 1, 'mac': client.network.mac }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'create_mac' }) } // Update the object. IP-Address if (client.network.ip) { bodies.push({ 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': clientid, 'category': 'C__CATG__IP', 'data': { 'category_id': 1, 'ipv4_address': client.network.ip }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'create_ip' }) } } // Update the object. Location if (client.parentId) { bodies.push({ 'version': '2.0', 'method': 'cmdb.category.update', 'params': { 'objID': clientid, 'category': 'C__CATG__LOCATION', 'data': { 'parent': client.parentId }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_parent' }) } var requestUpdate = await axios.post(c.url, bodies, config) var result = { success: true, id: clientid, type: 'C__OBJTYPE__CLIENT', create: requestCreate ? requestCreate.success : false, update: requestUpdate ? requestUpdate.success : false, createData: requestCreate ? requestCreate.data : false, updateData: requestUpdate ? requestUpdate.data : false } return result } /* * Updates the client information in the backend. * * credentials: * The client parameters are all optional. * client: { * id: , title: , parentId: , * system: { model: , manufacturer: , serialnumber: }, * cpu: { model: , manufacturer: , type: , frequency: , cores: }, * ram: [{ title: , manufacturer: , type: , capacity: , unit: }, ...], * drives: [{model: ,}, serial: , capacity: , unit: , type: , formfactor: , connection: ...] * } */ async updateClient (credentials, client) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] var config = { timeout: 180000, headers: { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } } var clientid = client.id var bodies = [] if (client.title) { // Update title of the object bodies.push({ 'version': '2.0', 'method': 'cmdb.object.update', 'params': { 'id': clientid, 'title': client.title, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_title' }) } if (client.uuid) { // Update the productid to the uuid. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.update', 'params': { 'objID': clientid, 'category': 'C__CATG__MODEL', 'data': { 'productid': client.uuid }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_uuid' }) } // Update the object. Location if (client.parentId) { bodies.push({ 'version': '2.0', 'method': 'cmdb.category.update', 'params': { 'objID': clientid, 'category': 'C__CATG__LOCATION', 'data': { 'parent': client.parentId }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_parent' }) } if (client.system) { // Update the object. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.update', 'params': { 'objID': clientid, 'category': 'C__CATG__MODEL', 'data': { 'manufacturer': client.system.manufacturer, 'title': client.system.model, 'serial': client.system.serialnumber }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_model' }) } if (client.cpu) { // TODO: Delete cpu if exists? // Update the object. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': clientid, 'category': 'C__CATG__CPU', 'data': { 'category_id': 1, 'manufacturer': client.cpu.manufacturer, 'title': client.cpu.model, 'type': client.cpu.type, 'frequency': client.cpu.frequency, 'frequency_unit': 3, 'cores': client.cpu.cores }, 'apikey': c.apikey, 'language': 'en' }, 'id': 'update_cpu' }) } if (client.ram) { var counter = 1 for (var memory in client.ram) { var mem = client.ram[memory] var id = 'create_memory_' + counter if (mem.unit === 'MB') mem.capacity = mem.capacity / 1024 // 2 = MB // 3 = GB // Update the object. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': clientid, 'category': 'C__CATG__MEMORY', 'data': { 'title': mem.title, 'manufacturer': mem.manufacturer, 'type': mem.type, 'capacity': mem.capacity, 'unit': 3 }, 'apikey': c.apikey, 'language': 'en' }, 'id': id }) counter++ } } if (client.drives) { var counter = 1 for (var drive in client.drives) { var d = client.drives[drive] var id = 'create_drive_' + counter // UNIT var unit = 0 if (d.unit === 'GB') unit = 3 else if (d.unit === 'TB') unit = 4 else if (d.unit === 'MB') unit = 2 else if (d.unit === 'KB') unit = 1 else if (d.unit === 'B') unit = 0 // Update the object. bodies.push({ 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': clientid, 'category': 'C__CATG__STORAGE_DEVICE', 'data': { 'title': d.model, 'type': d.type, // 'manufacturer': , // 'model': , 'capacity': d.capacity, 'unit': unit, 'serial': d.serial, 'connected': d.connection }, 'apikey': c.apikey, 'language': 'en' }, 'id': id }) } } var requestUpdate = await axios.post(c.url, bodies, config) var result = { success: true, id: clientid, type: 'C__OBJTYPE__CLIENT', update: requestUpdate ? requestUpdate.success : false, updateData: requestUpdate ? requestUpdate.data : false } return result } async uploadFiles (credentials, externalId, files) { var c = this.mapCredentials(credentials) var login = await this.getSession(c) var sid = login.data.result['session-id'] var config = { timeout: 180000, headers: { 'X-RPC-Auth-Session': sid, 'Content-Type': 'application/json' } } var result = [] for (var key in files) { var body = { 'version': '2.0', 'method': 'cmdb.object.create', 'params': { 'type': 'C__OBJTYPE__FILE', 'title': key, 'apikey': c.apikey, 'language': 'en' }, 'id': key } var fileObject = await axios.post(c.url, body, config) result.push(fileObject) var buffer = new Buffer.from(files[key].data) var filename = files[key].name // Upload file to fileobject. body = { 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': fileObject.data.result.id, 'data': { 'file_content': buffer.toString('base64'), 'file_physical': filename, 'file_title': filename, 'version_description': key }, 'category': 'C__CMDB__SUBCAT__FILE_VERSIONS', 'apikey': c.apikey, 'language': 'en' }, 'id': key } var fileupload = await axios.post(c.url, body, config) result.push(fileupload) // Combine fileObject with object. body = { 'version': '2.0', 'method': 'cmdb.category.create', 'params': { 'objID': externalId, 'data': { 'file': fileObject.data.result.id }, 'category': 'C__CATG__FILE', 'apikey': c.apikey, 'language': 'en' }, 'id': 4 } var concat = await axios.post(c.url, body, config) result.push(concat) } return { result: result } } // ############################################################################ // ####################### helper/optional functions ######################### // Helper function, to map the array of credential objects into a single js object. mapCredentials (credentials) { const c = JSON.parse(credentials) const login = c.find(x => x.id === 3) var mapped = { url: c.find(x => x.id === 1).value, apikey: c.find(x => x.id === 2).value, login: login.value } if (mapped.login) { mapped.username = login.elements.find(x => x.id === 4).value mapped.password = login.elements.find(x => x.id === 5).value } return mapped } // Username and password are optional. async getSession (credentials) { // Headers var headers = {} // Optional credentials if (credentials.login) { headers['X-RPC-Auth-Username'] = credentials.username headers['X-RPC-Auth-Password'] = credentials.password } // Params var params = { 'apikey': credentials.apikey, 'language': 'en' } // Make a login request and see if we are authenticated. return this.axiosRequest(credentials.url, 'idoit.login', params, headers) } // Helper function to make the axios http/https requests to reduced copy pasta code in this backend. Including the error handling. async axiosRequest (url, method, params, headers) { const body = { 'version': '2.0', 'method': method, 'params': params, 'id': 1 } var config = { timeout: 180000, headers: headers } config.headers['Content-Type'] = 'application/json' var response = await axios.post(url, body, config) .then(response => { return response }) .catch(error => { console.log(error) return error.response ? error.response : { status: 900, statusText: 'Request failed timeout' } }) // Axios error handling if (response.status !== 200) { return { success: false, error: response.status, msg: response.statusText } } // iDoIT error handling. if (response.data.result) { return { success: true, data: response.data } } else if (response.data.error) { console.log(response.data.error) return { success: false, error: response.data.error.message, msg: response.data.error.data.error } } else { return { success: false, error: 'UNNOWN_ERROR' } } } } module.exports = IdoitBackend