Skip to content
Snippets Groups Projects
newcase.js 5.58 KiB
const request = require('request');
//const fs = require('fs');
//const path = require('path');

//const caFile = path.resolve(__dirname, '../../ca.cert.pem'); // TODO resolve where the CA file should be located / configured

export default function (server) {
  server.route({
    path: '/api/thehive_button/new_case',
    method: 'POST',
    handler: newCaseHandler,
  });
  server.route({
    path: '/api/thehive_button/add_observables',
    method: 'POST',
    handler: addObservablesHandler,
  });
}

// Handler of ajax requests to create a new Case in The Hive
function newCaseHandler(req, resp) {
  // Parse the request to get connection parameters
  // (everything is configured in forntend and sent as part of the request,
  //  since I don't know how to configure the backend)
  var base_url = req.payload['base_url'];
  var api_key = req.payload['api_key'];
  var req_body = req.payload['body'];

  // check it's a valid URL with slash at the end
  if (!base_url) {
    return {'error': 'Base URL not set'};
  }
  if (!base_url.match(/https?:\/\/(([a-z\d.-]+)|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*\//i)) {
  //if (!base_url.match(/https?:\/\/.*\//)) {
    return {'error': 'Invalid base URL (it must begin with "http[s]" and end with "/")'};
  }
  if (!api_key) {
    return {'error': 'API key not set'};
  }

  return new Promise( function(resolve, reject) {
    request({
        method: 'POST',
        url: base_url + 'api/case',
        auth: {'bearer': api_key},
        json: true,
        body: req_body,
        //ca: fs.readFileSync(caFile), // TODO resolve the issue with custom CA, where to get its cert?
        rejectUnauthorized: false,
      },
      // handler of the reply from The Hive - just return as reply
      function (error, response, body) {
        // TODO: find out how to set response code, for now we always return sucess and encode original status code in the content
        if (error) {
          console.error("ERROR when trying to send request to The Hive:", error);
          resolve({'error': error.message});
        }
        else {
          if (response.statusCode < 200 || response.statusCode >= 300) {
            console.error("ERROR Unexpected reply received from The Hive:", response.statusCode, response.statusMessage, "\n", body)
          }
          resolve({
            'status_code': response.statusCode,
            'status_msg': response.statusMessage,
            'body': body
          });
        }
      } // handler function
    ); // request()
  }); // Promise()
}

// Note:
// There are two ways to create multiple Observables (artifacts) via The Hive API:
// 1. post one request with an array of observables in "data" field
//    - this allows to create all in one request, but doesn't allow to set 
//      different parameters (IOC, TLP, etc.) to different observables
// 2. post each observable in a separate request
// The second way is used here.

// Handler of ajax requests to add Observables to a Case in The Hive
function addObservablesHandler(req, resp) {
  // Parse the request to get connection parameters
  // (everything is configured in forntend and sent as part of the request,
  //  since I don't know how to configure the backend)
  var base_url = req.payload['base_url'];
  var api_key = req.payload['api_key'];

  // check it's a valid URL with slash at the end
  if (!base_url) {
    return {'error': 'Base URL not set'};
  }
  if (!base_url.match(/https?:\/\/(([a-z\d.-]+)|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*\//i)) {
  //if (!base_url.match(/https?:\/\/.*\//)) {
    return {'error': 'Invalid base URL (it must begin with "http[s]" and end with "/")'};
  }
  // TODO add "/" to the end automatically
  if (!api_key) {
    return {'error': 'API key not set'};
  }

  const caseid = req.payload['caseid'];
  const observables = req.payload['observables']; // array of obersvable specifications

  return new Promise( async function(resolve, reject) {
    // Run one request for each observable
    // (A way to run multiple async tasks sequentially inspired by:
    //  https://jrsinclair.com/articles/2019/how-to-run-async-js-in-parallel-or-sequential/ )
    const starterPromise = Promise.resolve(null);
    await observables.reduce(
      (p, obs) => p.then(() => addObservable(base_url, api_key, caseid, obs)),
      starterPromise
    ).catch((err_msg) => {
        console.error(err_msg); // log whole message
        resolve({'error': err_msg.split("\n", 1)[0]}); // send the first line to frontend
        return;
      }
    );
    resolve({});
  });
}
  
function addObservable(base_url, api_key, caseid, obs) {
  return new Promise( function(resolve, reject) {
    //console.log("Adding observable:", obs);
    request({
        method: 'POST',
        url: base_url + 'api/case/' + caseid + "/artifact",
        auth: {'bearer': api_key},
        json: true,
        body: obs,
        //ca: fs.readFileSync(caFile), // TODO resolve the issue with custom CA, where to get its cert?
        rejectUnauthorized: false,
      },
      // handler of the reply from The Hive - just return as reply
      function (error, response, body) {
        if (error) {
          reject("ERROR when trying to send request to The Hive: " + error);
        }
        else if (response.statusCode < 200 || response.statusCode >= 300) {
          reject("ERROR: Unexpected reply received from The Hive: " + response.statusCode + " " + response.statusMessage + "\n" + JSON.stringify(body));
        }
        else {
          // success - continue with the next observable
          resolve("OK");
          resolve({})
        }
      } // handler function
    ); // request()
  }); //Promise()
}