2017-03-02 10:07:55 +01:00

298 lines
8.8 KiB
JavaScript

import { Meteor } from 'meteor/meteor';
import { LibraryFindings } from '../imports/api/library_findings.js';
import { LibraryNonFindings } from '../imports/api/library_non_findings.js';
import { Settings } from '../imports/api/settings.js';
import Findings from '../imports/api/findings.js';
import NonFindings from '../imports/api/nonfindings.js';
var fs = Npm.require('fs');
var util = Npm.require('util');
var cheerio = Npm.require('cheerio');
Meteor.startup(() => {
handle_settings();
handle_findings();
handle_library_findings();
handle_non_findings();
handle_library_non_findings();
});
/**
* Takes a finding object and turns it into XML that can be handled
* by the pentext system to generate a report.
* @param non_finding
* @return string
*/
function get_finding_xml(finding)
{
var xml= '<finding id="" threatLevel="" type=""><title></title><description></description><technicaldescription></technicaldescription><impact></impact><recommendation></recommendation></finding>'
var $ = cheerio.load(xml, {xmlMode: true});
$('finding').attr('id', finding.id);
$('finding').attr('type', finding.type);
$('finding').attr('threatLevel', finding.threat_level);
$('title').html(finding.title);
$('description').html(finding.description);
$('technicaldescription').html(finding.technical_description);
$('impact').html(finding.impact);
$('recommendation').html(finding.recommendation);
return $.xml();
}
/**
* Takes a non-finding object and turns it into XML that can be handled
* by the pentext system to generate a report.
* @param non_finding
* @return string
*/
function get_non_finding_xml(non_finding)
{
var xml= '<non-finding id=""><title></title></non-finding>';
var $ = cheerio.load(xml, {xmlMode: true});
$('non-finding').attr('id', non_finding.id);
$('title').html(non_finding.title);
$('non-finding').append(non_finding.description)
return $.xml();
}
/**
* Sets up library findings. Removes any present at start, and publishes the collection
*/
function handle_library_findings() {
LibraryFindings.remove({});
Meteor.publish("library_findings", function() {
return LibraryFindings.find({});
})
}
/**
* Sets up non library findings. Removes any present at start, and publishes the collection
*/
function handle_library_non_findings() {
LibraryNonFindings.remove({});
Meteor.publish("library_non_findings", function() {
return LibraryNonFindings.find({});
})
}
/**
* This function is called when meteor starts. It will init an empty findings collection.
* Then it will add an "after update" handler that will generate xml from the finding object
* and will store it under the filename supplied by the object.
*/
function handle_findings()
{
Findings.remove({});
// This event handler will generate an XML file from the JS object and store it.
Findings.after.update(function (userid, finding, fieldnames, modifier) {
var xml = get_finding_xml(finding);
var findings_dir = Settings.findOne({_id: "findings_directory"}).value;
fs.writeFileSync(findings_dir + "/" + finding.filename, xml);
Findings.direct.update(finding._id, {$set: {raw: xml}});
});
Meteor.publish("findings", function() {
return Findings.find({});
})
}
/**
* Saves non-findings back to disc
*/
function handle_non_findings() {
NonFindings.remove();
NonFindings.after.update(function (userid, non_finding) {
var xml = get_non_finding_xml(non_finding);
var dir = Settings.findOne({_id: "non_findings_directory"}).value;
fs.writeFileSync(dir + "/" + non_finding.filename, xml);
NonFindings.direct.update(non_finding._id, {$set: {raw: xml}});
});
Meteor.publish("non_findings", function() {
return NonFindings.find({});
})
}
/**
* This function is called when meteor starts. It will check if the needed settings
* are present. If not, it will create a default one. An update hook is attached
* to the settings collection, so that all logic that depends on a change of this
* collection has a chance to update accordingly.
*/
function handle_settings() {
//Settings.remove({});
// Check if findings directory is available
// No ? Create default entry
if (!Settings.findOne({_id: "findings_directory"})) {
Settings.insert({_id: "findings_directory", value: ""})
}
if (!Settings.findOne({_id: "library_findings_directory"})) {
Settings.insert({_id: "library_findings_directory", value: ""})
}
if (!Settings.findOne({_id: "non_findings_directory"})) {
Settings.insert({_id: "non_findings_directory", value: ""})
}
if (!Settings.findOne({_id: "non_findings_library_directory"})) {
Settings.insert({_id: "non_findings_library_directory", value: ""})
}
// Update hook for findings https://github.com/matb33/meteor-collection-hooks
Settings.after.update(function (user, setting) {
if (setting._id == "findings_directory") {
dir = Settings.findOne({_id: "findings_directory"}).value;
read_xml(dir, Findings, parse_finding_xml);
} else if (setting._id == "library_findings_directory") {
dir = Settings.findOne({_id: "library_findings_directory"}).value;
read_xml(dir, LibraryFindings, parse_finding_xml);
} else if (setting._id == "non_findings_directory") {
dir = Settings.findOne({_id: "non_findings_directory"}).value;
read_xml(dir, NonFindings, parse_non_finding_xml);
} else if (setting._id == "non_findings_library_directory") {
dir = Settings.findOne({_id: "non_findings_library_directory"}).value;
read_xml(dir, LibraryNonFindings, parse_non_finding_xml);
}
});
Meteor.publish("settings", function () {
return Settings.find({});
})
}
/**
* This function is used to read findings/non-findings which are stored in xml format on the filesystem.
* This function can read library (non)findings as well as pentest (non)findings.
* @param: dir - the directory where to look for findings
* @param: collection - The collection to add the finding to
*/
function read_xml(dir, collection, func) {
collection.remove({});
try {
var stats = fs.statSync(dir);
} catch (e) {
console.log(e);
return false;
}
if (stats.isDirectory()) {
var files = fs.readdirSync(dir);
for (filename of files) {
var fullpath = dir + "/" + filename;
var stats = fs.statSync(fullpath);
if (stats.isDirectory()) { // Bail out as we can't read directories.
continue;
}
if (!fullpath.endsWith(".xml")) {
continue;
}
var content = fs.readFileSync(fullpath, encoding = "utf8");
var parsed_xml = func(filename, content);
collection.insert(parsed_xml);
}
}
}
function parse_finding_xml(filename, rawxml)
{
var title;
var id;
var threat_level;
var type;
var description;
var technical_description;
var impact;
var recommendation;
var valid = true;
$ = cheerio.load(rawxml, { xmlMode: false});
if ((id = $('finding').attr('id')) == null) {
valid = false;
}
if ((type = $('finding').attr('type')) == null) {
valid = false;
}
if ((threat_level = $('finding').attr('threatlevel')) == null) {
valid = false;
}
if ((title = $('title').html()) == null) {
valid = false;
}
if ((description = $('description').html()) == null) {
valid = false;
}
if ((technical_description = $('technicaldescription').html()) == null) {
valid = false;
}
if ((impact = $('impact').html()) == null) {
valid = false;
}
if ((recommendation = $('recommendation').html()) == null) {
valid = false;
}
return {
"filename": filename,
"title": title,
"id": id,
"threat_level": threat_level,
"type": type,
"description": description,
"technical_description": technical_description,
"impact": impact,
"recommendation": recommendation,
"raw": rawxml,
"valid": valid
}
}
function parse_non_finding_xml(filename, rawxml)
{
var title;
var id;
var description;
var valid = true;
$ = cheerio.load(rawxml, { xmlMode: false});
if ((id = $('non-finding').attr('id')) == null) {
valid = false;
}
if ((title = $('title').html()) == null) {
valid = false;
}
if ((description = $('non-finding').html()) == null) {
valid = false;
}
return {
"filename": filename,
"title": title,
"id": id,
"description": description,
"raw": rawxml,
"valid": valid
}
}