database.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. /**
  2. * TraCINg-Server - Gathering and visualizing cyber incidents on the world
  3. *
  4. * Copyright 2013 Matthias Gazzari, Annemarie Mattmann, André Wolski
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var MongoClient = require('mongodb').Db,
  19. ObjectId = require('mongodb').ObjectID,
  20. config = require("../../config.json"),
  21. assert = require('assert'),
  22. _ = require('underscore');
  23. var fields = require("../fields.js");
  24. var db = null;
  25. var collection = null;
  26. var collectionName = "incident";
  27. function safeCallback(callback){
  28. if(callback){
  29. callback.apply(this, Array.prototype.splice.call(arguments, 1));
  30. }
  31. }
  32. function connect(callback){
  33. MongoClient.connect(config.db, function(err, conn){
  34. assert.equal(null, err);
  35. console.log("Connected successfully to the database");
  36. db = conn;
  37. collection = conn.collection(collectionName);
  38. collection.ensureIndex({"src.ll" : "2dsphere"}, function(err, result) {
  39. collection.ensureIndex({"date": 1}, function(err, result){
  40. collection.ensureIndex({"sync_id": 1, "device": 1}, {
  41. unique: true,
  42. sparse: true
  43. }, function (err, result) {
  44. assert.equal(null, err);
  45. safeCallback(callback);
  46. });
  47. });
  48. });
  49. });
  50. }
  51. // CONSTANTS
  52. var DB_NOT_READY = 0;
  53. function createError(number){
  54. var msg = {errno: number, code: ""};
  55. switch(number){
  56. case DB_NOT_READY:
  57. msg.code = "The database is not ready";
  58. break;
  59. }
  60. return msg;
  61. }
  62. function ensureDBReady(callback){
  63. if(!db){
  64. console.log("db is not ready!");
  65. safeCallback(callback, createError(DB_NOT_READY));
  66. return false;
  67. }
  68. return true;
  69. }
  70. function getQueryCriteriaFromFilter(filter){
  71. if(filter.hasOwnProperty("raw") && filter.raw){
  72. return filter.criteria;
  73. }
  74. var criteria = {};
  75. if(filter.hasOwnProperty("start") && filter.hasOwnProperty("end")){
  76. criteria["date"] = {"$gte": new Date(filter["start"]), "$lte": new Date(filter["end"])};
  77. } else if(filter.hasOwnProperty("start")){
  78. criteria["date"] = {"$gte": new Date(filter["start"])};
  79. } else if(filter.hasOwnProperty("end")){
  80. criteria["date"] = {"$lte": new Date(filter["end"])};
  81. }
  82. if(filter.hasOwnProperty("countries")){
  83. var countries = filter["countries"];
  84. if(!_.isArray(countries)) countries = [countries];
  85. criteria["src.cc"] = {"$in": countries};
  86. }
  87. if(filter.hasOwnProperty("countries_dest")){
  88. var countries_dst = filter["countries"];
  89. if(!_.isArray(countries_dst)) countries_dst = [countries_dst];
  90. criteria["dst.cc"] = {"$in": countries_dst};
  91. }
  92. if(filter.hasOwnProperty("location")){
  93. criteria["dst.ll"] = {"$nearSphere": filter.location};
  94. if(filter.hasOwnProperty("distance")){
  95. criteria["dst.ll"]["$maxDistance"] = parseInt(filter.distance, 10) / 6371;
  96. }
  97. }
  98. if(filter.hasOwnProperty("authorized")){
  99. criteria["authorized"] = true;
  100. }
  101. if(filter.hasOwnProperty("types")){
  102. var types = filter["types"];
  103. if(!_.isArray(types)) types = [types];
  104. criteria["type"] = {"$in": _.map(types, _.partial(parseInt, _, 10))};
  105. }
  106. if(filter.hasOwnProperty("sensors")){
  107. var sensors = filter["sensors"];
  108. if(!_.isArray(sensors)) sensors = [sensors];
  109. criteria["sensortype"] = {"$in": sensors};
  110. }
  111. if(filter.hasOwnProperty("query") && filter.query && filter.query.length > 0) {
  112. var regexp = new RegExp('.*' + filter.query + '.*', 'i');
  113. criteria["$or"] = [{
  114. sensorname: regexp
  115. }, {
  116. log: regexp
  117. }, {
  118. md5sum: regexp
  119. }]
  120. }
  121. return criteria;
  122. }
  123. // ------------------------------ PUBLIC / EXPORT ------------------------------
  124. function getSensorTypes(callback){
  125. console.log("called getSensorTypes");
  126. if(!ensureDBReady(callback)) return;
  127. collection.group({'sensortype': 1}, {}, {}, function(){}, function(err, docs){
  128. if(err){
  129. safeCallback(callback, err);
  130. return;
  131. }
  132. var sensors = [];
  133. for(var k in docs){
  134. sensors.push(docs[k]["sensortype"]);
  135. }
  136. safeCallback(callback, null, sensors);
  137. });
  138. }
  139. function insert(items, callback){
  140. //console.log("called insert");
  141. if(!ensureDBReady(callback)) return;
  142. collection.insert(items, {continueOnError: true}, function(err, result){
  143. if(err){
  144. if(err.code !== 11000) {
  145. console.error(err);
  146. safeCallback(callback, err);
  147. return;
  148. }
  149. }
  150. safeCallback(callback, null, result);
  151. });
  152. }
  153. function requestAttacks(filter, callback, options){
  154. console.log("called requestAttacks", filter);
  155. if(!ensureDBReady(callback)) return;
  156. console.log(getQueryCriteriaFromFilter(filter));
  157. if(filter.page && filter.page === 'all'){
  158. var pipeline = [];
  159. var group = {
  160. _id: {
  161. city: "$src.city",
  162. country: "$src.country",
  163. cc: "$src.cc"
  164. },
  165. count: {"$sum": 1},
  166. date: {"$first": "$date"},
  167. location: {$first: "$src.ll"},
  168. destination: {$first: {$concat: ["$dst.city", ", ", "$dst.country"]}},
  169. type: {$first: "$type"},
  170. authorized: {$first: "$authorized"}
  171. };
  172. var criteria = getQueryCriteriaFromFilter(filter);
  173. if(criteria){
  174. pipeline.push({"$match": criteria});
  175. }
  176. pipeline.push({"$group": group});
  177. // we need to aggregate the data
  178. collection.aggregate(pipeline, {allowDiskUse: true}, function(err, res){
  179. if (err) {
  180. console.error(err);
  181. safeCallback(callback, err);
  182. return;
  183. }
  184. safeCallback(callback, null, res);
  185. });
  186. return;
  187. }
  188. var result = collection.find(getQueryCriteriaFromFilter(filter)).sort({date: -1});
  189. if(filter.page >= 0){
  190. var perpage = filter.perpage ? (filter.perpage > 50 ? 50 : filter.perpage) : 10;
  191. result.skip(filter.page).limit(perpage);
  192. }
  193. var defaultOptions = {"type": "array"};
  194. if(!options){
  195. options = defaultOptions;
  196. }
  197. if(options.type === "array") {
  198. result.count(function(error, total){
  199. result.toArray(function (err, docs) {
  200. if (err) {
  201. console.error(err);
  202. safeCallback(callback, err);
  203. return;
  204. }
  205. safeCallback(callback, null, options.sync ? docs : {total: total, data: docs});
  206. });
  207. });
  208. } else if(options.type === "stream") {
  209. var stream = result.stream();
  210. options.stream.pipe(stream);
  211. } else {
  212. var stream = result.stream();
  213. var str = "[";
  214. var first = true;
  215. var response = options.response || null;
  216. if(response){
  217. response.write("[");
  218. }
  219. stream.on('data', function(item){
  220. var prefix = first ? '' : ', ';
  221. if(response){
  222. response.write(prefix + JSON.stringify(item));
  223. } else {
  224. str += prefix + JSON.stringify(item);
  225. }
  226. first = false;
  227. });
  228. stream.on('end', function() {
  229. if(response){
  230. response.write("]");
  231. response.end();
  232. } else {
  233. str += "]";
  234. safeCallback(callback, false, str);
  235. }
  236. });
  237. stream.on('error', function(err){
  238. console.error(err);
  239. safeCallback(callback, err);
  240. })
  241. }
  242. }
  243. function getLog(id, callback){
  244. console.log("called getLog");
  245. if(!ensureDBReady(callback)) return;
  246. collection.findOne({_id: ObjectId(id)}, {log: 1}, function(err, res){
  247. if(err){
  248. console.error(err);
  249. safeCallback(callback, err);
  250. return;
  251. }
  252. console.log(id, err, res);
  253. safeCallback(callback, null, (res && res.log) || "");
  254. });
  255. }
  256. function extractSeries(rows, serieName, serieDisplay){
  257. var series = [];
  258. var convertRow = function(row){
  259. var timestamp = Date.UTC(
  260. row.datum.getUTCFullYear(),
  261. row.datum.getUTCMonth(),
  262. row.datum.getUTCDate()
  263. );
  264. return [
  265. timestamp,
  266. row.count
  267. ];
  268. };
  269. if (!serieName) {
  270. series.push({
  271. name: "total",
  272. data: _.map(rows, convertRow)
  273. });
  274. console.log("total", series);
  275. } else {
  276. var serie = {};
  277. for (var k in rows) {
  278. if(!rows.hasOwnProperty(k)) continue;
  279. var row = rows[k];
  280. var rowSerieValue = row._id[serieName] + "";
  281. if (!serie.hasOwnProperty(rowSerieValue)) {
  282. serie[rowSerieValue] = {data: [], name: ""};
  283. if (serieDisplay)
  284. serie[rowSerieValue].name = row[serieDisplay];
  285. else
  286. serie[rowSerieValue].name = fields.translate(serieName, rowSerieValue);
  287. }
  288. // push this row to the current serie
  289. serie[rowSerieValue].data.push(convertRow(row));
  290. }
  291. series = _.values(serie);
  292. }
  293. return series;
  294. }
  295. /*
  296. converts the dates to unix time in all data of all series
  297. determines minDate and maxDate if not given
  298. fills all series with 0-data on each date between minDate and maxDate if it has no data on that day
  299. */
  300. function convertDates(series, minTimestamp, maxTimestamp){
  301. var checkMinMax = !minTimestamp || !maxTimestamp; // no logical need for this check, but should give performance as in most cases min and max are given
  302. // convert the dates to UTS timestamps for each serie
  303. for(var i = 0; i < series.length; i++){
  304. // convert it for each data entry
  305. for (var j = 0; j < series[i].data.length; j++) {
  306. var timestamp = series[i].data[j][0];
  307. if(!minTimestamp || minTimestamp > timestamp)
  308. minTimestamp = timestamp;
  309. if(!maxTimestamp || maxTimestamp < timestamp)
  310. maxTimestamp = timestamp;
  311. }
  312. }
  313. for(var jj = 0; jj < series.length; jj++){
  314. var currentTimestamp = minTimestamp;
  315. var ii = 0;
  316. var maxI = series[jj].data.length;
  317. var newData = [];
  318. while(currentTimestamp <= maxTimestamp){
  319. //console.log("convertDates.currentTimestamp:", currentTimestamp, new Date(currentTimestamp));
  320. if(ii >= maxI || currentTimestamp < series[jj].data[ii][0]){
  321. newData.push([currentTimestamp, 0]);
  322. }
  323. else{
  324. newData.push([currentTimestamp, series[jj].data[ii][1]]);
  325. ii++;
  326. }
  327. // next day
  328. currentTimestamp += 24 * 60 * 60 * 1000;
  329. }
  330. series[jj].data = newData;
  331. }
  332. }
  333. function getStatistics(options, callback){
  334. console.log("called getStatistics");
  335. var pipeline = [];
  336. var criteria = null;
  337. if(options.filter){
  338. criteria = getQueryCriteriaFromFilter(options.filter);
  339. }
  340. var group = {
  341. _id: {
  342. year: {"$year": "$date"},
  343. month: {"$month": "$date"},
  344. day: {"$dayOfMonth": "$date"}
  345. },
  346. count: {"$sum": 1},
  347. datum: {"$first": "$date"}
  348. };
  349. var project = null;
  350. var order = {date: 1};
  351. var serieName = null;
  352. var serieDisplay = null;
  353. if(options.detail){
  354. switch(options.detailChartType){
  355. case "typeDate":
  356. group._id["type"] = "$type";
  357. order["type"] = 1;
  358. serieName = "type";
  359. break;
  360. case "countryDate":
  361. if(!project){
  362. project = {};
  363. }
  364. group._id["source_cc"] = "$src.cc";
  365. group["source_country"] = {"$first": "$src.country"};
  366. order["src.cc"] = 1;
  367. serieName = "source_cc";
  368. serieDisplay = "source_country";
  369. break;
  370. default:
  371. callback("getStatistics: unknown detailChartType: " + detailChartType);
  372. return;
  373. }
  374. }
  375. pipeline.push({"$sort": order});
  376. //if(project) pipeline.push({"$project": project});
  377. if(criteria){
  378. pipeline.push({"$match": criteria});
  379. }
  380. pipeline.push({"$group": group});
  381. pipeline.push({"$sort": {datum: 1, count: 1}});
  382. collection.aggregate(pipeline, {allowDiskUse: true}, function(err, res){
  383. if(err){
  384. safeCallback(callback, err);
  385. }
  386. if(!res || res.length == 0){
  387. safeCallback(callback, null, []);
  388. return;
  389. }
  390. var series = extractSeries(res, serieName, serieDisplay);
  391. if(options.filter)
  392. convertDates(series, options.filter.start, options.filter.end);
  393. else
  394. convertDates(series);
  395. safeCallback(callback, null, series);
  396. });
  397. }
  398. function getAllCountries(callback){
  399. collection.aggregate([{
  400. $group: {
  401. "_id": {"cc": "$dst.cc", "country": "$dst.country"}
  402. }
  403. }], null, function(err, res){
  404. if(err){
  405. safeCallback(callback, err);
  406. }
  407. safeCallback(callback, null, res.filter(function(d){ return d._id.cc != 0; }).map(function(d){
  408. return d._id;
  409. }));
  410. });
  411. }
  412. function getEntriesFromSyncInfo(sync_request, callback) {
  413. if(!sync_request.hasOwnProperty("condition") || !sync_request.hasOwnProperty("info")){
  414. throw new Error("Invalid sync request");
  415. }
  416. var sync_condition = sync_request.condition;
  417. var sync_info = sync_request.info;
  418. var sync_device_condition = [];
  419. var device_list = [];
  420. for(var i=0; i<sync_info.length; i++){
  421. var entry = sync_info[i];
  422. sync_device_condition.push({
  423. "$and": [
  424. {"sync_id": {"$gt": entry.sync_id}},
  425. {"device": entry.device}
  426. ]
  427. });
  428. device_list.push(entry.device);
  429. }
  430. sync_device_condition.push({"device": {"$nin": device_list}});
  431. var criteria = {
  432. "sensorname": "HosTaGe",
  433. "date": {"$gte": new Date(sync_condition.date || 0)},
  434. "$or": sync_device_condition
  435. };
  436. if(sync_condition.hasOwnProperty('location')){
  437. criteria["dst.ll"] = {"$nearSphere": sync_condition.location};
  438. if(sync_condition.hasOwnProperty("distance")){
  439. criteria["dst.ll"]["$maxDistance"] = parseInt(sync_condition.distance, 10) / 6371;
  440. } else {
  441. criteria["dst.ll"]["$maxDistance"] = 500 / 6371;
  442. }
  443. }
  444. if(sync_condition.hasOwnProperty('country')){
  445. criteria["dst.cc"] = sync_condition.country;
  446. }
  447. requestAttacks({
  448. raw: true,
  449. criteria: criteria
  450. }, function (error, data) {
  451. if(error){
  452. safeCallback(callback, error);
  453. }
  454. safeCallback(callback, null, data);
  455. }, {type: 'array', sync: true});
  456. }
  457. function getDatabase(){
  458. return db;
  459. }
  460. function closeDatabase(){
  461. db.close();
  462. }
  463. module.exports = {
  464. connect: connect,
  465. close: closeDatabase,
  466. getDB: getDatabase,
  467. getSensorTypes: getSensorTypes,
  468. insert: insert,
  469. requestAttacks: requestAttacks,
  470. getLog: getLog,
  471. getStatistics: getStatistics,
  472. getAllCountries: getAllCountries,
  473. getEntriesFromSyncInfo: getEntriesFromSyncInfo
  474. };