298 lines
8.8 KiB
JavaScript
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
|
|
}
|
|
} |