MISP (core software) - Open Source Threat Intelligence and Sharing Platform (formely known as Malware Information Sharing Platform) https://www.misp-project.org/
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

7351 lines
332 KiB

  1. <?php
  2. App::uses('AppModel', 'Model');
  3. App::uses('CakeEmail', 'Network/Email');
  4. App::uses('RandomTool', 'Tools');
  5. App::uses('AttachmentTool', 'Tools');
  6. App::uses('TmpFileTool', 'Tools');
  7. /**
  8. * @property User $User
  9. * @property Attribute $Attribute
  10. * @property ShadowAttribute $ShadowAttribute
  11. * @property EventTag $EventTag
  12. */
  13. class Event extends AppModel
  14. {
  15. public $actsAs = array(
  16. 'SysLogLogable.SysLogLogable' => array(
  17. 'userModel' => 'User',
  18. 'userKey' => 'user_id',
  19. 'change' => 'full'),
  20. 'Trim',
  21. 'Containable',
  22. );
  23. public $displayField = 'id';
  24. public $virtualFields = array();
  25. public $mispVersion = '2.4.0';
  26. public $fieldDescriptions = array(
  27. 'threat_level_id' => array('desc' => 'Risk levels: *low* means mass-malware, *medium* means APT malware, *high* means sophisticated APT malware or 0-day attack', 'formdesc' => 'Risk levels: low: mass-malware medium: APT malware high: sophisticated APT malware or 0-day attack'),
  28. 'classification' => array('desc' => 'Set the Traffic Light Protocol classification. <ol><li><em>TLP:AMBER</em>- Share only within the organization on a need-to-know basis</li><li><em>TLP:GREEN:NeedToKnow</em>- Share within your constituency on the need-to-know basis.</li><li><em>TLP:GREEN</em>- Share within your constituency.</li></ol>'),
  29. 'submittedioc' => array('desc' => '', 'formdesc' => ''),
  30. 'analysis' => array('desc' => 'Analysis Levels: *Initial* means the event has just been created, *Ongoing* means that the event is being populated, *Complete* means that the event\'s creation is complete', 'formdesc' => 'Analysis levels: Initial: event has been started Ongoing: event population is in progress Complete: event creation has finished'),
  31. 'distribution' => array('desc' => 'Describes who will have access to the event.')
  32. );
  33. public $analysisDescriptions = array(
  34. 0 => array('desc' => '*Initial* means the event has just been created', 'formdesc' => 'Event has just been created and is in an initial state'),
  35. 1 => array('desc' => '*Ongoing* means that the event is being populated', 'formdesc' => 'The analysis is still ongoing'),
  36. 2 => array('desc' => '*Complete* means that the event\'s creation is complete', 'formdesc' => 'The event creator considers the analysis complete')
  37. );
  38. public $distributionDescriptions = array(
  39. 0 => array('desc' => 'This field determines the current distribution of the event', 'formdesc' => "This setting will only allow members of your organisation on this server to see it."),
  40. 1 => array('desc' => 'This field determines the current distribution of the event', 'formdesc' => "Organisations that are part of this MISP community will be able to see the event."),
  41. 2 => array('desc' => 'This field determines the current distribution of the event', 'formdesc' => "Organisations that are either part of this MISP community or part of a directly connected MISP community will be able to see the event."),
  42. 3 => array('desc' => 'This field determines the current distribution of the event', 'formdesc' => "This will share the event with all MISP communities, allowing the event to be freely propagated from one server to the next."),
  43. 4 => array('desc' => 'This field determines the current distribution of the event', 'formdesc' => "This distribution of this event will be handled by the selected sharing group."),
  44. );
  45. public $analysisLevels = array(
  46. 0 => 'Initial', 1 => 'Ongoing', 2 => 'Completed'
  47. );
  48. public $distributionLevels = array(
  49. 0 => 'Your organisation only', 1 => 'This community only', 2 => 'Connected communities', 3 => 'All communities', 4 => 'Sharing group'
  50. );
  51. public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' sharing Group');
  52. public $export_types = array(
  53. 'json' => array(
  54. 'extension' => '.json',
  55. 'type' => 'JSON',
  56. 'scope' => 'Event',
  57. 'requiresPublished' => 0,
  58. 'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'json'),
  59. 'description' => 'Click this to download all events and attributes that you have access to in MISP JSON format.',
  60. ),
  61. 'xml' => array(
  62. 'extension' => '.xml',
  63. 'type' => 'XML',
  64. 'scope' => 'Event',
  65. 'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'xml'),
  66. 'requiresPublished' => 0,
  67. 'description' => 'Click this to download all events and attributes that you have access to in MISP XML format.',
  68. ),
  69. 'csv_sig' => array(
  70. 'extension' => '.csv',
  71. 'type' => 'CSV_Sig',
  72. 'scope' => 'Event',
  73. 'requiresPublished' => 1,
  74. 'params' => array('published' => 1, 'to_ids' => 1, 'returnFormat' => 'csv'),
  75. 'description' => 'Click this to download all attributes that are indicators and that you have access to <small>(except file attachments)</small> in CSV format.',
  76. ),
  77. 'csv_all' => array(
  78. 'extension' => '.csv',
  79. 'type' => 'CSV_All',
  80. 'scope' => 'Event',
  81. 'requiresPublished' => 0,
  82. 'params' => array('ignore' => 1, 'returnFormat' => 'csv'),
  83. 'description' => 'Click this to download all attributes that you have access to <small>(except file attachments)</small> in CSV format.',
  84. ),
  85. 'suricata' => array(
  86. 'extension' => '.rules',
  87. 'type' => 'Suricata',
  88. 'scope' => 'Attribute',
  89. 'requiresPublished' => 1,
  90. 'params' => array('returnFormat' => 'suricata'),
  91. 'description' => 'Click this to download all network related attributes that you have access to under the Suricata rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
  92. ),
  93. 'snort' => array(
  94. 'extension' => '.rules',
  95. 'type' => 'Snort',
  96. 'scope' => 'Attribute',
  97. 'requiresPublished' => 1,
  98. 'params' => array('returnFormat' => 'snort'),
  99. 'description' => 'Click this to download all network related attributes that you have access to under the Snort rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
  100. ),
  101. 'bro' => array(
  102. 'extension' => '.intel',
  103. 'type' => 'Bro',
  104. 'scope' => 'Attribute',
  105. 'requiresPublished' => 1,
  106. 'params' => array('returnFormat' => 'bro'),
  107. 'description' => 'Click this to download all network related attributes that you have access to under the Bro rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.',
  108. ),
  109. 'stix' => array(
  110. 'extension' => '.xml',
  111. 'type' => 'STIX',
  112. 'scope' => 'Event',
  113. 'requiresPublished' => 1,
  114. 'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
  115. 'description' => 'Click this to download a STIX document containing the STIX version of all events and attributes that you have access to.'
  116. ),
  117. 'stix-json' => array(
  118. 'extension' => '.json',
  119. 'type' => 'STIX',
  120. 'scope' => 'Event',
  121. 'requiresPublished' => 1,
  122. 'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
  123. 'description' => 'Click this to download a STIX document containing the STIX version of all events and attributes that you have access to.'
  124. ),
  125. 'stix2' => array(
  126. 'extension' => '.json',
  127. 'type' => 'STIX2',
  128. 'scope' => 'Event',
  129. 'requiresPublished' => 1,
  130. 'params' => array('returnFormat' => 'stix2', 'includeAttachments' => 1),
  131. 'description' => 'Click this to download a STIX2 document containing the STIX2 version of all events and attributes that you have access to.'
  132. ),
  133. 'rpz' => array(
  134. 'extension' => '.txt',
  135. 'type' => 'RPZ',
  136. 'scope' => 'Attribute',
  137. 'requiresPublished' => 1,
  138. 'params' => array('returnFormat' => 'rpz'),
  139. 'description' => 'Click this to download an RPZ Zone file generated from all ip-src/ip-dst, hostname, domain attributes. This can be useful for DNS level firewalling. Only published events and attributes marked as IDS Signature are exported.'
  140. ),
  141. 'text' => array(
  142. 'extension' => '.txt',
  143. 'type' => 'TEXT',
  144. 'scope' => 'Attribute',
  145. 'requiresPublished' => 1,
  146. 'params' => array('returnFormat' => 'text', 'includeAttachments' => 1),
  147. 'description' => 'Click on one of the buttons below to download all the attributes with the matching type. This list can be used to feed forensic software when searching for susipicious files. Only published events and attributes marked as IDS Signature are exported.'
  148. ),
  149. 'yara' => array(
  150. 'extension' => '.yara',
  151. 'type' => 'Yara',
  152. 'scope' => 'Event',
  153. 'requiresPublished' => 1,
  154. 'params' => array('returnFormat' => 'yara'),
  155. 'description' => 'Click this to download Yara rules generated from all relevant attributes.'
  156. ),
  157. 'yara-json' => array(
  158. 'extension' => '.json',
  159. 'type' => 'Yara',
  160. 'scope' => 'Event',
  161. 'requiresPublished' => 1,
  162. 'params' => array('returnFormat' => 'yara-json'),
  163. 'description' => 'Click this to download Yara rules generated from all relevant attributes. Rules are returned in a JSON format with information about origin (generated or parsed) and validity.'
  164. ),
  165. );
  166. public $validFormats = array(
  167. 'attack' => array('html', 'AttackExport', 'html'),
  168. 'attack-sightings' => array('json', 'AttackSightingsExport', 'json'),
  169. 'cache' => array('txt', 'CacheExport', 'cache'),
  170. 'count' => array('txt', 'CountExport', 'txt'),
  171. 'csv' => array('csv', 'CsvExport', 'csv'),
  172. 'hashes' => array('txt', 'HashesExport', 'txt'),
  173. 'json' => array('json', 'JsonExport', 'json'),
  174. 'netfilter' => array('txt', 'NetfilterExport', 'sh'),
  175. 'opendata' => array('txt', 'OpendataExport', 'txt'),
  176. 'openioc' => array('xml', 'OpeniocExport', 'ioc'),
  177. 'rpz' => array('txt', 'RPZExport', 'rpz'),
  178. 'snort' => array('txt', 'NidsSnortExport', 'rules'),
  179. 'stix' => array('xml', 'Stix1Export', 'xml'),
  180. 'stix-json' => array('json', 'Stix1Export', 'json'),
  181. 'stix2' => array('json', 'Stix2Export', 'json'),
  182. 'suricata' => array('txt', 'NidsSuricataExport', 'rules'),
  183. 'text' => array('text', 'TextExport', 'txt'),
  184. 'xml' => array('xml', 'XmlExport', 'xml'),
  185. 'yara' => array('txt', 'YaraExport', 'yara'),
  186. 'yara-json' => array('json', 'YaraExport', 'json')
  187. );
  188. public $csv_event_context_fields_to_fetch = array(
  189. 'event_info' => array('object' => false, 'var' => 'info'),
  190. 'event_member_org' => array('object' => 'Org', 'var' => 'name'),
  191. 'event_source_org' => array('object' => 'Orgc', 'var' => 'name'),
  192. 'event_distribution' => array('object' => false, 'var' => 'distribution'),
  193. 'event_threat_level_id' => array('object' => 'ThreatLevel', 'var' => 'name'),
  194. 'event_analysis' => array('object' => false, 'var' => 'analysis'),
  195. 'event_date' => array('object' => false, 'var' => 'date'),
  196. 'event_tag' => array('object' => 'Tag', 'var' => 'name')
  197. );
  198. public $validate = array(
  199. 'org_id' => array(
  200. 'valueNotEmpty' => array(
  201. 'rule' => array('valueNotEmpty'),
  202. ),
  203. 'numeric' => array(
  204. 'rule' => array('numeric'),
  205. ),
  206. ),
  207. 'orgc_id' => array(
  208. 'valueNotEmpty' => array(
  209. 'rule' => array('valueNotEmpty'),
  210. ),
  211. 'numeric' => array(
  212. 'rule' => array('numeric'),
  213. ),
  214. ),
  215. 'date' => array(
  216. 'date' => array(
  217. 'rule' => array('date'),
  218. 'message' => 'Expected date format: YYYY-MM-DD',
  219. //'allowEmpty' => false,
  220. 'required' => true,
  221. //'last' => false, // Stop validation after this rule
  222. //'on' => 'create', // Limit validation to 'create' or 'update' operations
  223. ),
  224. ),
  225. 'threat_level_id' => array(
  226. 'rule' => array('inList', array('1', '2', '3', '4')),
  227. 'message' => 'Options : 1, 2, 3, 4 (for High, Medium, Low, Undefined)',
  228. 'required' => true
  229. ),
  230. 'distribution' => array(
  231. 'not_empty_if_sg' => array(
  232. 'rule' => array('inList', array('0', '1', '2', '3', '4')),
  233. 'message' => 'Options : Your organisation only, This community only, Connected communities, All communities',
  234. //'allowEmpty' => false,
  235. 'required' => true,
  236. //'last' => false, // Stop validation after this rule
  237. //'on' => 'create', // Limit validation to 'create' or 'update' operations
  238. )
  239. ),
  240. 'sharing_group_id' => array(
  241. 'rule' => array('sharingGroupRequired'),
  242. 'message' => 'If the distribution is set to "Sharing Group", a sharing group has to be selected.',
  243. //'required' => true,
  244. //'allowEmpty' => true
  245. ),
  246. 'analysis' => array(
  247. 'rule' => array('inList', array('0', '1', '2')),
  248. 'message' => 'Options : 0, 1, 2 (for Initial, Ongoing, Completed)',
  249. //'allowEmpty' => false,
  250. 'required' => true,
  251. //'last' => false, // Stop validation after this rule
  252. //'on' => 'create', // Limit validation to 'create' or 'update' operations
  253. ),
  254. 'info' => array(
  255. 'valueNotEmpty' => array(
  256. 'rule' => array('valueNotEmpty'),
  257. 'required' => true
  258. ),
  259. ),
  260. 'user_id' => array(
  261. 'numeric' => array(
  262. 'rule' => array('numeric')
  263. )
  264. ),
  265. 'published' => array(
  266. 'boolean' => array(
  267. 'rule' => array('boolean'),
  268. //'message' => 'Your custom message here',
  269. //'allowEmpty' => false,
  270. //'required' => false,
  271. //'last' => false, // Stop validation after this rule
  272. //'on' => 'create', // Limit validation to 'create' or 'update' operations
  273. ),
  274. ),
  275. 'uuid' => array(
  276. 'uuid' => array(
  277. 'rule' => 'uuid',
  278. 'message' => 'Please provide a valid RFC 4122 UUID'
  279. ),
  280. ),
  281. 'extends_uuid' => array(
  282. 'uuid' => array(
  283. 'rule' => 'uuid',
  284. 'message' => 'Please provide a valid UUID',
  285. 'allowEmpty' => true
  286. )
  287. )
  288. );
  289. // The Associations below have been created with all possible keys, those that are not needed can be removed
  290. public $belongsTo = array(
  291. 'User' => array(
  292. 'className' => 'User',
  293. 'foreignKey' => 'user_id',
  294. 'conditions' => '',
  295. 'fields' => '',
  296. 'order' => ''
  297. ),
  298. 'ThreatLevel' => array(
  299. 'className' => 'ThreatLevel',
  300. 'foreignKey' => 'threat_level_id'
  301. ),
  302. 'Org' => array(
  303. 'className' => 'Organisation',
  304. 'foreignKey' => 'org_id'
  305. ),
  306. 'Orgc' => array(
  307. 'className' => 'Organisation',
  308. 'foreignKey' => 'orgc_id'
  309. ),
  310. 'SharingGroup' => array(
  311. 'className' => 'SharingGroup',
  312. 'foreignKey' => 'sharing_group_id'
  313. )
  314. );
  315. public $hasMany = array(
  316. 'Attribute' => array(
  317. 'className' => 'Attribute',
  318. 'foreignKey' => 'event_id',
  319. 'dependent' => true, // cascade deletes
  320. 'conditions' => '',
  321. 'fields' => '',
  322. 'order' => array('Attribute.category ASC', 'Attribute.type ASC'),
  323. 'limit' => '',
  324. 'offset' => '',
  325. 'exclusive' => '',
  326. 'finderQuery' => '',
  327. 'counterQuery' => ''
  328. ),
  329. 'ShadowAttribute' => array(
  330. 'className' => 'ShadowAttribute',
  331. 'foreignKey' => 'event_id',
  332. 'dependent' => true, // cascade deletes
  333. 'conditions' => '',
  334. 'fields' => '',
  335. 'order' => array('ShadowAttribute.old_id DESC', 'ShadowAttribute.old_id DESC'),
  336. 'limit' => '',
  337. 'offset' => '',
  338. 'exclusive' => '',
  339. 'finderQuery' => '',
  340. 'counterQuery' => ''
  341. ),
  342. 'Object' => array(
  343. 'className' => 'MispObject',
  344. 'foreignKey' => 'event_id',
  345. 'dependent' => true,
  346. 'conditions' => '',
  347. 'fields' => '',
  348. 'order' => false,
  349. 'limit' => '',
  350. 'offset' => '',
  351. 'exclusive' => '',
  352. 'finderQuery' => '',
  353. 'counterQuery' => ''
  354. ),
  355. 'EventTag' => array(
  356. 'className' => 'EventTag',
  357. 'dependent' => true,
  358. ),
  359. 'Sighting' => array(
  360. 'className' => 'Sighting',
  361. 'dependent' => true,
  362. ),
  363. 'EventReport' => array(
  364. 'className' => 'EventReport',
  365. 'dependent' => true,
  366. )
  367. );
  368. public function __construct($id = false, $table = null, $ds = null)
  369. {
  370. parent::__construct($id, $table, $ds);
  371. $this->export_types = array(
  372. 'json' => array(
  373. 'extension' => '.json',
  374. 'type' => 'JSON',
  375. 'scope' => 'Event',
  376. 'requiresPublished' => 0,
  377. 'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'json'),
  378. 'description' => __('Click this to download all events and attributes that you have access to in MISP JSON format.'),
  379. ),
  380. 'xml' => array(
  381. 'extension' => '.xml',
  382. 'type' => 'XML',
  383. 'scope' => 'Event',
  384. 'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'xml'),
  385. 'requiresPublished' => 0,
  386. 'description' => __('Click this to download all events and attributes that you have access to in MISP XML format.'),
  387. ),
  388. 'csv_sig' => array(
  389. 'extension' => '.csv',
  390. 'type' => 'CSV_Sig',
  391. 'scope' => 'Event',
  392. 'requiresPublished' => 1,
  393. 'params' => array('published' => 1, 'to_ids' => 1, 'returnFormat' => 'csv'),
  394. 'description' => __('Click this to download all attributes that are indicators and that you have access to (except file attachments) in CSV format.'),
  395. ),
  396. 'csv_all' => array(
  397. 'extension' => '.csv',
  398. 'type' => 'CSV_All',
  399. 'scope' => 'Event',
  400. 'requiresPublished' => 0,
  401. 'params' => array('ignore' => 1, 'returnFormat' => 'csv'),
  402. 'description' => __('Click this to download all attributes that you have access to (except file attachments) in CSV format.'),
  403. ),
  404. 'suricata' => array(
  405. 'extension' => '.rules',
  406. 'type' => 'Suricata',
  407. 'scope' => 'Attribute',
  408. 'requiresPublished' => 1,
  409. 'params' => array('returnFormat' => 'suricata'),
  410. 'description' => __('Click this to download all network related attributes that you have access to under the Suricata rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.'),
  411. ),
  412. 'snort' => array(
  413. 'extension' => '.rules',
  414. 'type' => 'Snort',
  415. 'scope' => 'Attribute',
  416. 'requiresPublished' => 1,
  417. 'params' => array('returnFormat' => 'snort'),
  418. 'description' => __('Click this to download all network related attributes that you have access to under the Snort rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.'),
  419. ),
  420. 'bro' => array(
  421. 'extension' => '.intel',
  422. 'type' => 'Bro',
  423. 'scope' => 'Attribute',
  424. 'requiresPublished' => 1,
  425. 'params' => array('returnFormat' => 'bro'),
  426. 'description' => __('Click this to download all network related attributes that you have access to under the Bro rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a allowedlist containing host, domain name and IP numbers to exclude from the NIDS export.'),
  427. ),
  428. 'stix' => array(
  429. 'extension' => '.xml',
  430. 'type' => 'STIX',
  431. 'scope' => 'Event',
  432. 'requiresPublished' => 1,
  433. 'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
  434. 'description' => __('Click this to download an a STIX document containing the STIX version of all events and attributes that you have access to.')
  435. ),
  436. 'stix2' => array(
  437. 'extension' => '.json',
  438. 'type' => 'STIX2',
  439. 'scope' => 'Event',
  440. 'requiresPublished' => 1,
  441. 'params' => array('returnFormat' => 'stix2', 'includeAttachments' => 1),
  442. 'description' => __('Click this to download an a STIX2 document containing the STIX2 version of all events and attributes that you have access to.')
  443. ),
  444. 'rpz' => array(
  445. 'extension' => '.txt',
  446. 'type' => 'RPZ',
  447. 'scope' => 'Attribute',
  448. 'requiresPublished' => 1,
  449. 'params' => array('returnFormat' => 'rpz'),
  450. 'description' => __('Click this to download an RPZ Zone file generated from all ip-src/ip-dst, hostname, domain attributes. This can be useful for DNS level firewalling. Only published events and attributes marked as IDS Signature are exported.')
  451. ),
  452. 'text' => array(
  453. 'extension' => '.txt',
  454. 'type' => 'TEXT',
  455. 'scope' => 'Attribute',
  456. 'requiresPublished' => 1,
  457. 'params' => array('returnFormat' => 'text', 'includeAttachments' => 1),
  458. 'description' => __('Click on one of the buttons below to download all the attributes with the matching type. This list can be used to feed forensic software when searching for susipicious files. Only published events and attributes marked as IDS Signature are exported.')
  459. ),
  460. 'yara' => array(
  461. 'extension' => '.yara',
  462. 'type' => 'Yara',
  463. 'scope' => 'Event',
  464. 'requiresPublished' => 1,
  465. 'params' => array('returnFormat' => 'yara'),
  466. 'description' => __('Click this to download Yara rules generated from all relevant attributes.')
  467. ),
  468. 'yara-json' => array(
  469. 'extension' => '.json',
  470. 'type' => 'Yara',
  471. 'scope' => 'Event',
  472. 'requiresPublished' => 1,
  473. 'params' => array('returnFormat' => 'yara-json'),
  474. 'description' => __('Click this to download Yara rules generated from all relevant attributes. Rules are returned in a JSON format with information about origin (generated or parsed) and validity.')
  475. ),
  476. );
  477. }
  478. public function beforeDelete($cascade = true)
  479. {
  480. // blocklist the event UUID if the feature is enabled
  481. if (Configure::read('MISP.enableEventBlocklisting') !== false && empty($this->skipBlocklist)) {
  482. $this->EventBlocklist = ClassRegistry::init('EventBlocklist');
  483. $orgc = $this->Orgc->find('first', array('conditions' => array('Orgc.id' => $this->data['Event']['orgc_id']), 'recursive' => -1, 'fields' => array('Orgc.name')));
  484. $this->EventBlocklist->create();
  485. $this->EventBlocklist->save(array(
  486. 'event_uuid' => $this->data['Event']['uuid'],
  487. 'event_info' => $this->data['Event']['info'],
  488. 'event_orgc' => $orgc['Orgc']['name'],
  489. 'comment' => __('Automatically blocked by deleting event'),
  490. ));
  491. if (!empty($this->data['Event']['id'])) {
  492. if (Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
  493. $pubSubTool = $this->getPubSubTool();
  494. $pubSubTool->event_save(array('Event' => $this->data['Event']), 'delete');
  495. }
  496. if (Configure::read('Plugin.Kafka_enable')) {
  497. $kafkaEventTopic = Configure::read('Plugin.Kafka_event_notifications_topic');
  498. if(Configure::read('Plugin.Kafka_event_notifications_enable') && !empty($kafkaEventTopic)) {
  499. $kafkaPubTool = $this->getKafkaPubTool();
  500. $kafkaPubTool->publishJson($kafkaEventTopic, array('Event' => $this->data['Event']), 'delete');
  501. }
  502. $kafkaPubTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
  503. if (!empty($this->data['Event']['published']) && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaPubTopic)) {
  504. $hostOrg = $this->Org->find('first', array('conditions' => array('name' => Configure::read('MISP.org')), 'fields' => array('id')));
  505. if (!empty($hostOrg)) {
  506. $user = array('org_id' => $hostOrg['Org']['id'], 'Role' => array('perm_sync' => 0, 'perm_audit' => 0, 'perm_site_admin' => 0), 'Organisation' => $hostOrg['Org']);
  507. $params = array('eventid' => $this->data['Event']['id']);
  508. if (Configure::read('Plugin.Kafka_include_attachments')) {
  509. $params['includeAttachments'] = 1;
  510. }
  511. $fullEvent = $this->fetchEvent($user, $params);
  512. if (!empty($fullEvent)) {
  513. $kafkaPubTool = $this->getKafkaPubTool();
  514. $kafkaPubTool->publishJson($kafkaPubTopic, $fullEvent[0], 'delete');
  515. }
  516. }
  517. }
  518. }
  519. }
  520. }
  521. // delete all of the event->tag combinations that involve the deleted event
  522. $this->EventTag->deleteAll(array('event_id' => $this->id));
  523. try {
  524. $this->loadAttachmentTool()->deleteAll($this->id);
  525. } catch (Exception $e) {
  526. $this->logException('Delete of event file directory failed.', $e);
  527. throw new InternalErrorException('Delete of event file directory failed. Please report to administrator.');
  528. }
  529. }
  530. public function beforeValidate($options = array())
  531. {
  532. parent::beforeValidate();
  533. // analysis - setting correct vars
  534. if (isset($this->data['Event']['analysis'])) {
  535. switch ($this->data['Event']['analysis']) {
  536. case 'Initial':
  537. $this->data['Event']['analysis'] = 0;
  538. break;
  539. case 'Ongoing':
  540. $this->data['Event']['analysis'] = 1;
  541. break;
  542. case 'Completed':
  543. $this->data['Event']['analysis'] = 2;
  544. break;
  545. }
  546. } else {
  547. $this->data['Event']['analysis'] = 0;
  548. }
  549. if (!isset($this->data['Event']['threat_level_id'])) {
  550. $this->data['Event']['threat_level_id'] = Configure::read('MISP.default_event_threat_level') ? Configure::read('MISP.default_event_threat_level') : 4;
  551. }
  552. // generate UUID if it doesn't exist
  553. if (empty($this->data['Event']['uuid'])) {
  554. $this->data['Event']['uuid'] = CakeText::uuid();
  555. } else {
  556. $this->data['Event']['uuid'] = strtolower($this->data['Event']['uuid'] );
  557. }
  558. // Convert event ID to uuid if needed
  559. if (!empty($this->data['Event']['extends_uuid']) && is_numeric($this->data['Event']['extends_uuid'])) {
  560. $extended_event = $this->find('first', array(
  561. 'recursive' => -1,
  562. 'conditions' => array('Event.id' => $this->data['Event']['extends_uuid']),
  563. 'fields' => array('Event.uuid')
  564. ));
  565. if (empty($extended_event)) {
  566. $this->data['Event']['extends_uuid'] = '';
  567. } else {
  568. $this->data['Event']['extends_uuid'] = $extended_event['Event']['uuid'];
  569. }
  570. } else if (!empty($this->data['Event']['extends_uuid'])) {
  571. $this->data['Event']['extends_uuid'] = strtolower($this->data['Event']['extends_uuid']);
  572. }
  573. // generate timestamp if it doesn't exist
  574. if (empty($this->data['Event']['timestamp'])) {
  575. $date = new DateTime();
  576. $this->data['Event']['timestamp'] = $date->getTimestamp();
  577. }
  578. if (isset($this->data['Event']['publish_timestamp']) && empty($this->data['Event']['publish_timestamp'])) {
  579. $this->data['Event']['publish_timestamp'] = 0;
  580. }
  581. if (empty($this->data['Event']['date'])) {
  582. $this->data['Event']['date'] = date('Y-m-d');
  583. }
  584. if (!isset($this->data['Event']['distribution']) || $this->data['Event']['distribution'] != 4) {
  585. $this->data['Event']['sharing_group_id'] = 0;
  586. }
  587. }
  588. public function afterSave($created, $options = array())
  589. {
  590. if (!Configure::read('MISP.completely_disable_correlation') && !$created) {
  591. $db = $this->getDataSource();
  592. $updateCorrelation = array();
  593. if (isset($this->data['Event']['date'])) {
  594. $updateCorrelation['Correlation.date'] = $db->value($this->data['Event']['date']);
  595. }
  596. if (isset($this->data['Event']['info'])) {
  597. $updateCorrelation['Correlation.info'] = $db->value($this->data['Event']['info']);
  598. }
  599. if (isset($this->data['Event']['distribution'])) {
  600. $updateCorrelation['Correlation.distribution'] = $db->value($this->data['Event']['distribution']);
  601. }
  602. if (isset($this->data['Event']['sharing_group_id'])) {
  603. $updateCorrelation['Correlation.sharing_group_id'] = $db->value($this->data['Event']['sharing_group_id']);
  604. }
  605. if (!empty($updateCorrelation)) {
  606. $this->Correlation = ClassRegistry::init('Correlation');
  607. $this->Correlation->updateAll($updateCorrelation, array('Correlation.event_id' => intval($this->data['Event']['id'])));
  608. }
  609. }
  610. if (empty($this->data['Event']['unpublishAction']) && empty($this->data['Event']['skip_zmq']) && Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
  611. $pubSubTool = $this->getPubSubTool();
  612. $event = $this->quickFetchEvent($this->data['Event']['id']);
  613. if (!empty($event)) {
  614. $pubSubTool->event_save($event, $created ? 'add' : 'edit');
  615. }
  616. }
  617. if (empty($this->data['Event']['unpublishAction']) && empty($this->data['Event']['skip_kafka'])) {
  618. $this->publishKafkaNotification('event', $this->quickFetchEvent($this->data['Event']['id']), $created ? 'add' : 'edit');
  619. }
  620. }
  621. public function attachtagsToEvents($events)
  622. {
  623. $tagsToFetch = array();
  624. foreach ($events as $k => $event) {
  625. if (!empty($event['EventTag'])) {
  626. foreach ($event['EventTag'] as $et) {
  627. $tagsToFetch[$et['tag_id']] = $et['tag_id'];
  628. }
  629. }
  630. }
  631. $tags = $this->EventTag->Tag->find('all', array(
  632. 'conditions' => array('Tag.id' => $tagsToFetch),
  633. 'recursive' => -1,
  634. 'order' => false
  635. ));
  636. $tags = Set::combine($tags, '{n}.Tag.id', '{n}');
  637. foreach ($events as $k => $event) {
  638. if (!empty($event['EventTag'])) {
  639. foreach ($event['EventTag'] as $k2 => $et) {
  640. $events[$k]['EventTag'][$k2]['Tag'] = $tags[$et['tag_id']]['Tag'];
  641. }
  642. }
  643. }
  644. return $events;
  645. }
  646. // gets the logged in user + an array of events, attaches the correlation count to each
  647. public function attachCorrelationCountToEvents($user, $events)
  648. {
  649. $sgids = $this->SharingGroup->fetchAllAuthorised($user);
  650. if (!isset($sgids) || empty($sgids)) {
  651. $sgids = array(-1);
  652. }
  653. $this->Correlation = ClassRegistry::init('Correlation');
  654. $eventIds = Set::extract('/Event/id', $events);
  655. $conditionsCorrelation = $this->__buildEventConditionsCorrelation($user, $eventIds, $sgids);
  656. $correlations = $this->Correlation->find('all', array(
  657. 'fields' => array('Correlation.1_event_id', 'count(distinct(Correlation.event_id)) as count'),
  658. 'conditions' => $conditionsCorrelation,
  659. 'recursive' => -1,
  660. 'group' => array('Correlation.1_event_id'),
  661. ));
  662. $correlations = Hash::combine($correlations, '{n}.Correlation.1_event_id', '{n}.0.count');
  663. foreach ($events as &$event) {
  664. $event['Event']['correlation_count'] = (isset($correlations[$event['Event']['id']])) ? $correlations[$event['Event']['id']] : 0;
  665. }
  666. return $events;
  667. }
  668. public function attachSightingsCountToEvents($user, $events)
  669. {
  670. $eventIds = Set::extract('/Event/id', $events);
  671. $this->Sighting = ClassRegistry::init('Sighting');
  672. $sightings = $this->Sighting->find('all', array(
  673. 'fields' => array('Sighting.event_id', 'count(distinct(Sighting.id)) as count'),
  674. 'conditions' => array('event_id' => $eventIds),
  675. 'recursive' => -1,
  676. 'group' => array('event_id')
  677. ));
  678. $sightings = Hash::combine($sightings, '{n}.Sighting.event_id', '{n}.0.count');
  679. foreach ($events as $key => $event) {
  680. $events[$key]['Event']['sightings_count'] = (isset($sightings[$event['Event']['id']])) ? $sightings[$event['Event']['id']] : 0;
  681. }
  682. return $events;
  683. }
  684. public function attachProposalsCountToEvents($user, $events)
  685. {
  686. $eventIds = Set::extract('/Event/id', $events);
  687. $proposals = $this->ShadowAttribute->find('all', array(
  688. 'fields' => array('ShadowAttribute.event_id', 'count(distinct(ShadowAttribute.id)) as count'),
  689. 'conditions' => array('event_id' => $eventIds, 'deleted' => 0),
  690. 'recursive' => -1,
  691. 'group' => array('event_id')
  692. ));
  693. $proposals = Hash::combine($proposals, '{n}.ShadowAttribute.event_id', '{n}.0.count');
  694. foreach ($events as $key => $event) {
  695. $events[$key]['Event']['proposals_count'] = (isset($proposals[$event['Event']['id']])) ? $proposals[$event['Event']['id']] : 0;
  696. }
  697. return $events;
  698. }
  699. public function attachDiscussionsCountToEvents($user, $events)
  700. {
  701. $eventIds = Set::extract('/Event/id', $events);
  702. $this->Thread = ClassRegistry::init('Thread');
  703. $threads = $this->Thread->find('list', array(
  704. 'conditions' => array('Thread.event_id' => $eventIds),
  705. 'fields' => array('Thread.event_id', 'Thread.id')
  706. ));
  707. $posts = $this->Thread->Post->find('all', array(
  708. 'conditions' => array('Post.thread_id' => $threads),
  709. 'recursive' => -1,
  710. 'fields' => array('Count(id) AS post_count', 'thread_id', 'max(date_modified) as last_post'),
  711. 'group' => array('Post.thread_id')
  712. ));
  713. $event_threads = array();
  714. foreach ($posts as $k => $v) {
  715. foreach ($threads as $k2 => $v2) {
  716. if ($v2 == $v['Post']['thread_id']) {
  717. $event_threads[$k2] = array(
  718. 'post_count' => $v[0]['post_count'],
  719. 'last_post' => strtotime($v[0]['last_post'])
  720. );
  721. }
  722. }
  723. }
  724. foreach ($events as $k => $v) {
  725. $events[$k]['Event']['post_count'] = !empty($event_threads[$events[$k]['Event']['id']]) ? $event_threads[$events[$k]['Event']['id']]['post_count'] : 0;
  726. $events[$k]['Event']['last_post'] = !empty($event_threads[$events[$k]['Event']['id']]) ? $event_threads[$events[$k]['Event']['id']]['last_post'] : 0;
  727. }
  728. return $events;
  729. }
  730. private function __buildEventConditionsCorrelation($user, $eventIds, $sgids)
  731. {
  732. if (!is_array($eventIds)) {
  733. $eventIds = array($eventIds);
  734. }
  735. if (!$user['Role']['perm_site_admin']) {
  736. $conditionsCorrelation = array(
  737. 'AND' => array(
  738. 'Correlation.1_event_id' => $eventIds,
  739. array(
  740. 'OR' => array(
  741. 'Correlation.org_id' => $user['org_id'],
  742. 'AND' => array(
  743. array(
  744. 'OR' => array(
  745. array(
  746. 'AND' => array(
  747. 'Correlation.distribution >' => 0,
  748. 'Correlation.distribution <' => 4,
  749. ),
  750. ),
  751. array(
  752. 'AND' => array(
  753. 'Correlation.distribution' => 4,
  754. 'Correlation.sharing_group_id' => $sgids
  755. ),
  756. ),
  757. ),
  758. ),
  759. array(
  760. 'OR' => array(
  761. 'Correlation.a_distribution' => 5,
  762. array(
  763. 'AND' => array(
  764. 'Correlation.a_distribution >' => 0,
  765. 'Correlation.a_distribution <' => 4,
  766. ),
  767. ),
  768. array(
  769. 'AND' => array(
  770. 'Correlation.a_distribution' => 4,
  771. 'Correlation.a_sharing_group_id' => $sgids
  772. ),
  773. ),
  774. ),
  775. ),
  776. ),
  777. ),
  778. ),
  779. ),
  780. );
  781. } else {
  782. $conditionsCorrelation = array('Correlation.1_event_id' => $eventIds);
  783. }
  784. return $conditionsCorrelation;
  785. }
  786. private function getRelatedEvents($user, $eventId, $sgids)
  787. {
  788. if (!isset($sgids) || empty($sgids)) {
  789. $sgids = array(-1);
  790. }
  791. $this->Correlation = ClassRegistry::init('Correlation');
  792. // search the correlation table for the event ids of the related events
  793. // Rules:
  794. // 1. Event is owned by the user (org_id matches)
  795. // 2. User is allowed to see both the event and the org:
  796. // a. Event:
  797. // i. Event has a distribution between 1-3 (community only, connected communities, all orgs)
  798. // ii. Event has a sharing group that the user is accessible to view
  799. // b. Attribute:
  800. // i. Attribute has a distribution of 5 (inheritance of the event, for this the event check has to pass anyway)
  801. // ii. Atttibute has a distribution between 1-3 (community only, connected communities, all orgs)
  802. // iii. Attribute has a sharing group that the user is accessible to view
  803. $conditionsCorrelation = $this->__buildEventConditionsCorrelation($user, $eventId, $sgids);
  804. $correlations = $this->Correlation->find('list', array(
  805. 'fields' => array('Correlation.event_id', 'Correlation.event_id'),
  806. 'conditions' => $conditionsCorrelation,
  807. 'recursive' => 0,
  808. 'group' => 'Correlation.event_id',
  809. 'order' => array('Correlation.event_id DESC')));
  810. if (empty($correlations)) {
  811. return [];
  812. }
  813. $relatedEventIds = array_values($correlations);
  814. // now look up the event data for these attributes
  815. $conditions = $this->createEventConditions($user);
  816. $conditions['AND'][] = array('Event.id' => $relatedEventIds);
  817. $fields = array('id', 'date', 'threat_level_id', 'info', 'published', 'uuid', 'analysis', 'timestamp', 'distribution', 'org_id', 'orgc_id');
  818. $orgfields = array('id', 'name', 'uuid');
  819. $relatedEvents = $this->find(
  820. 'all',
  821. array('conditions' => $conditions,
  822. 'recursive' => -1,
  823. 'order' => 'Event.date DESC',
  824. 'fields' => $fields,
  825. 'contain' => array(
  826. 'Org' => array(
  827. 'fields' => $orgfields
  828. ),
  829. 'Orgc' => array(
  830. 'fields' => $orgfields
  831. )
  832. )
  833. )
  834. );
  835. $fieldsToRearrange = array('Org', 'Orgc');
  836. foreach ($relatedEvents as $k => $relatedEvent) {
  837. foreach ($fieldsToRearrange as $field) {
  838. if (isset($relatedEvent[$field])) {
  839. $relatedEvents[$k]['Event'][$field] = $relatedEvent[$field];
  840. unset($relatedEvents[$k][$field]);
  841. }
  842. }
  843. }
  844. return $relatedEvents;
  845. }
  846. public function getRelatedAttributes($user, $id, $sgids, $shadowAttribute = false, $scope = 'event')
  847. {
  848. if ($shadowAttribute) {
  849. $settings = array('model' => 'ShadowAttribute', 'correlationModel' => 'ShadowAttributeCorrelation', 'parentIdField' => '1_shadow_attribute_id');
  850. } else {
  851. $settings = array('model' => 'Attribute', 'correlationModel' => 'Correlation', 'parentIdField' => '1_attribute_id');
  852. }
  853. if (!isset($sgids) || empty($sgids)) {
  854. $sgids = array(-1);
  855. }
  856. $this->{$settings['correlationModel']} = ClassRegistry::init($settings['correlationModel']);
  857. if (!$user['Role']['perm_site_admin']) {
  858. $conditionsCorrelation = array(
  859. 'AND' => array(
  860. $settings['correlationModel'] . '.1_' . $scope . '_id' => $id,
  861. array(
  862. 'OR' => array(
  863. $settings['correlationModel'] . '.org_id' => $user['org_id'],
  864. 'AND' => array(
  865. array(
  866. 'OR' => array(
  867. array(
  868. 'AND' => array(
  869. $settings['correlationModel'] . '.distribution >' => 0,
  870. $settings['correlationModel'] . '.distribution <' => 4,
  871. ),
  872. ),
  873. array(
  874. 'AND' => array(
  875. $settings['correlationModel'] . '.distribution' => 4,
  876. $settings['correlationModel'] . '.sharing_group_id' => $sgids
  877. ),
  878. ),
  879. ),
  880. ),
  881. array(
  882. 'OR' => array(
  883. $settings['correlationModel'] . '.a_distribution' => 5,
  884. array(
  885. 'AND' => array(
  886. $settings['correlationModel'] . '.a_distribution >' => 0,
  887. $settings['correlationModel'] . '.a_distribution <' => 4,
  888. ),
  889. ),
  890. array(
  891. 'AND' => array(
  892. $settings['correlationModel'] . '.a_distribution' => 4,
  893. $settings['correlationModel'] . '.a_sharing_group_id' => $sgids
  894. ),
  895. ),
  896. ),
  897. ),
  898. ),
  899. )
  900. )
  901. )
  902. );
  903. } else {
  904. $conditionsCorrelation = array($settings['correlationModel'] . '.1_' . $scope . '_id' => $id);
  905. }
  906. $max_correlations = Configure::read('MISP.max_correlations_per_event') ?: 5000;
  907. $correlations = $this->{$settings['correlationModel']}->find('all', array(
  908. 'fields' => ['event_id', 'attribute_id', 'value', $settings['parentIdField']],
  909. 'conditions' => $conditionsCorrelation,
  910. 'recursive' => -1,
  911. 'order' => false,
  912. 'limit' => $max_correlations
  913. ));
  914. if (empty($correlations)) {
  915. return array();
  916. }
  917. $eventIds = [];
  918. foreach ($correlations as $correlation) {
  919. $eventIds[] = $correlation[$settings['correlationModel']]['event_id'];
  920. }
  921. $conditions = $this->createEventConditions($user);
  922. $conditions['AND']['Event.id'] = $eventIds;
  923. $events = $this->find('all', array(
  924. 'recursive' => -1,
  925. 'conditions' => $conditions,
  926. 'fields' => array('Event.id', 'Event.orgc_id', 'Event.info', 'Event.date'),
  927. ));
  928. $eventInfos = array();
  929. foreach ($events as $event) {
  930. $eventInfos[$event['Event']['id']] = $event['Event'];
  931. }
  932. $relatedAttributes = array();
  933. foreach ($correlations as $correlation) {
  934. $correlation = $correlation[$settings['correlationModel']];
  935. // User don't have access to correlated attribute event, skip.
  936. if (!isset($eventInfos[$correlation['event_id']])) {
  937. continue;
  938. }
  939. $eventInfo = $eventInfos[$correlation['event_id']];
  940. $current = array(
  941. 'id' => $correlation['event_id'],
  942. 'attribute_id' => $correlation['attribute_id'],
  943. 'value' => $correlation['value'],
  944. 'org_id' => $eventInfo['orgc_id'],
  945. 'info' => $eventInfo['info'],
  946. 'date' => $eventInfo['date'],
  947. );
  948. $parentId = $correlation[$settings['parentIdField']];
  949. $relatedAttributes[$parentId][] = $current;
  950. }
  951. return $relatedAttributes;
  952. }
  953. /**
  954. * Clean up an Event Array that was received by an XML request.
  955. * The structure needs to be changed a little bit to be compatible with what CakePHP expects
  956. *
  957. * This function receives the reference of the variable, so no return is required as it directly
  958. * modifies the original data.
  959. */
  960. public function cleanupEventArrayFromXML(&$data)
  961. {
  962. $objects = array('Attribute', 'ShadowAttribute', 'Object');
  963. foreach ($objects as $object) {
  964. // Workaround for different structure in XML/array than what CakePHP expects
  965. if (isset($data['Event'][$object]) && is_array($data['Event'][$object]) && count($data['Event'][$object])) {
  966. if (!is_numeric(implode(array_keys($data['Event'][$object]), ''))) {
  967. // single attribute
  968. $data['Event'][$object] = array(0 => $data['Event'][$object]);
  969. }
  970. $data['Event'][$object] = array_values($data['Event'][$object]);
  971. }
  972. }
  973. $objects = array('Org', 'Orgc', 'SharingGroup');
  974. foreach ($objects as $object) {
  975. if (isset($data['Event'][$object][0])) {
  976. $data['Event'][$object] = $data['Event'][$object][0];
  977. }
  978. }
  979. return $data;
  980. }
  981. private function __resolveErrorCode($code, &$event, &$server)
  982. {
  983. $error = false;
  984. switch ($code) {
  985. case 403:
  986. return 'The distribution level of this event blocks it from being pushed.';
  987. case 405:
  988. $error = 'The sync user on the remote instance does not have the required privileges to handle this event.';
  989. break;
  990. }
  991. if ($error) {
  992. $newTextBody = 'Uploading Event (' . $event['Event']['id'] . ') to Server (' . $server['Server']['id'] . ')';
  993. $this->__logUploadResult($server, $event, $newTextBody);
  994. }
  995. return $error;
  996. }
  997. private function __executeRestfulEventToServer($event, $server, $resourceId, &$newLocation, &$newTextBody, $HttpSocket, $scope)
  998. {
  999. $result = $this->restfulEventToServer($event, $server, $resourceId, $newLocation, $newTextBody, $HttpSocket, $scope);
  1000. if (is_numeric($result)) {
  1001. $error = $this->__resolveErrorCode($result, $event, $server);
  1002. if ($error) {
  1003. return $error . ' Error code: ' . $result;
  1004. }
  1005. }
  1006. return true;
  1007. }
  1008. public function uploadSightingsToServer($sightings, $server, $event_uuid, $HttpSocket = null)
  1009. {
  1010. $HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
  1011. $request = $this->setupSyncRequest($server);
  1012. $uri = $server['Server']['url'] . '/sightings/bulkSaveSightings/' . $event_uuid;
  1013. foreach ($sightings as &$sighting) {
  1014. if (!isset($sighting['org_id'])) {
  1015. $sighting['org_id'] = '0';
  1016. }
  1017. }
  1018. $data = json_encode($sightings);
  1019. if (!empty(Configure::read('Security.sync_audit'))) {
  1020. $pushLogEntry = sprintf(
  1021. "==============================================================\n\n[%s] Pushing Sightings for Event #%s to Server #%d:\n\n%s\n\n",
  1022. date("Y-m-d H:i:s"),
  1023. $event_uuid,
  1024. $server['Server']['id'],
  1025. $data
  1026. );
  1027. file_put_contents(APP . 'files/scripts/tmp/debug_server_' . $server['Server']['id'] . '.log', $pushLogEntry, FILE_APPEND);
  1028. }
  1029. $response = $HttpSocket->post($uri, $data, $request);
  1030. return $this->__handleRestfulEventToServerResponse($response, $newLocation, $newTextBody);
  1031. }
  1032. public function uploadEventToServer($event, $server, $HttpSocket = null, $scope = 'events')
  1033. {
  1034. $this->Server = ClassRegistry::init('Server');
  1035. $push = $this->Server->checkVersionCompatibility($server['Server']['id'], false, $HttpSocket);
  1036. if ($scope === 'events' && empty($push['canPush'])) {
  1037. return 'The remote user is not a sync user - the upload of the event has been blocked.';
  1038. } elseif ($scope === 'sightings' && empty($push['canPush']) && empty($push['canSight'])) {
  1039. return 'The remote user is not a sightings user - the upload of the sightings has been blocked.';
  1040. }
  1041. if (!empty($server['Server']['unpublish_event'])) {
  1042. $event['Event']['published'] = 0;
  1043. }
  1044. $updated = null;
  1045. $newLocation = $newTextBody = '';
  1046. $result = $this->__executeRestfulEventToServer($event, $server, null, $newLocation, $newTextBody, $HttpSocket, $scope);
  1047. if ($result !== true) {
  1048. return $result;
  1049. }
  1050. if (strlen($newLocation)) { // HTTP/1.1 302 Found and Location: http://<newLocation>
  1051. $result = $this->__executeRestfulEventToServer($event, $server, $newLocation, $newLocation, $newTextBody, $HttpSocket, $scope);
  1052. if ($result !== true) {
  1053. return $result;
  1054. }
  1055. }
  1056. try {
  1057. $this->jsonDecode($newTextBody);
  1058. } catch (Exception $e) {
  1059. $this->logException("Invalid JSON returned when pushing to remote server {$server['Server']['id']}", $e);
  1060. return $this->__logUploadResult($server, $event, $newTextBody);
  1061. }
  1062. return 'Success';
  1063. }
  1064. private function __prepareForPushToServer($event, $server)
  1065. {
  1066. if ($event['Event']['distribution'] == 4) {
  1067. if (!empty($event['SharingGroup']['SharingGroupServer'])) {
  1068. $found = false;
  1069. foreach ($event['SharingGroup']['SharingGroupServer'] as $sgs) {
  1070. if ($sgs['server_id'] == $server['Server']['id']) {
  1071. $found = true;
  1072. }
  1073. }
  1074. if (!$found) {
  1075. return 403;
  1076. }
  1077. } else if (empty($event['SharingGroup']['roaming'])) {
  1078. return 403;
  1079. }
  1080. }
  1081. $serverModel = ClassRegistry::init('Server');
  1082. $server = $serverModel->eventFilterPushableServers($event, array($server));
  1083. if (empty($server)) {
  1084. return 403;
  1085. }
  1086. $server = $server[0];
  1087. if ($this->checkDistributionForPush($event, $server, 'Event')) {
  1088. $event = $this->__updateEventForSync($event, $server);
  1089. } else {
  1090. return 403;
  1091. }
  1092. return $event;
  1093. }
  1094. private function __getLastUrlPathComponent($urlPath)
  1095. {
  1096. if (!empty($urlPath)) {
  1097. $pieces = explode('/', $urlPath);
  1098. return '/' . end($pieces);
  1099. }
  1100. return '';
  1101. }
  1102. private function __handleRestfulEventToServerResponse($response, &$newLocation, &$newTextBody)
  1103. {
  1104. switch ($response->code) {
  1105. case '200': // 200 (OK) + entity-action-result
  1106. $newTextBody = $response->body();
  1107. return true;
  1108. case '302': // Found
  1109. $newLocation = $response->headers['Location'];
  1110. $newTextBody = $response->body();
  1111. return true;
  1112. case '404': // Not Found
  1113. $newLocation = $response->headers['Location'];
  1114. $newTextBody = $response->body();
  1115. return 404;
  1116. case '405':
  1117. $newTextBody = $response->body();
  1118. return 405;
  1119. case '403': // Not authorised
  1120. $newTextBody = $response->body();
  1121. return 403;
  1122. }
  1123. }
  1124. // Uploads the event and the associated Attributes to another Server
  1125. public function restfulEventToServer($event, $server, $urlPath, &$newLocation, &$newTextBody, $HttpSocket = null, $scope)
  1126. {
  1127. $event = $this->__prepareForPushToServer($event, $server);
  1128. if (is_numeric($event)) {
  1129. return $event;
  1130. }
  1131. $url = $server['Server']['url'];
  1132. $HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
  1133. $request = $this->setupSyncRequest($server);
  1134. if ($scope === 'sightings') {
  1135. $scope .= '/bulkSaveSightings';
  1136. $urlPath = $event['Event']['uuid'];
  1137. }
  1138. $uri = $url . '/' . $scope . $this->__getLastUrlPathComponent($urlPath);
  1139. $data = json_encode($event);
  1140. if (!empty(Configure::read('Security.sync_audit'))) {
  1141. $pushLogEntry = sprintf(
  1142. "==============================================================\n\n[%s] Pushing Event #%d to Server #%d:\n\n%s\n\n",
  1143. date("Y-m-d H:i:s"),
  1144. $event['Event']['id'],
  1145. $server['Server']['id'],
  1146. $data
  1147. );
  1148. file_put_contents(APP . 'files/scripts/tmp/debug_server_' . $server['Server']['id'] . '.log', $pushLogEntry, FILE_APPEND);
  1149. }
  1150. $response = $HttpSocket->post($uri, $data, $request);
  1151. return $this->__handleRestfulEventToServerResponse($response, $newLocation, $newTextBody);
  1152. }
  1153. private function __rearrangeEventStructureForSync($event)
  1154. {
  1155. // rearrange things to be compatible with the Xml::fromArray()
  1156. $objectsToRearrange = array('Attribute', 'Object', 'Orgc', 'SharingGroup', 'EventTag', 'Org', 'ShadowAttribute', 'EventReport');
  1157. foreach ($objectsToRearrange as $o) {
  1158. if (isset($event[$o])) {
  1159. $event['Event'][$o] = $event[$o];
  1160. unset($event[$o]);
  1161. }
  1162. }
  1163. // cleanup the array from things we do not want to expose
  1164. foreach (array('Org', 'org_id', 'orgc_id', 'proposal_email_lock', 'org', 'orgc') as $field) {
  1165. unset($event['Event'][$field]);
  1166. }
  1167. return $event;
  1168. }
  1169. // since we fetch the event and filter on tags after / server, we need to cull all of the non exportable tags
  1170. private function __removeNonExportableTags($data, $dataType, $server = [])
  1171. {
  1172. if (isset($data[$dataType . 'Tag'])) {
  1173. if (!empty($data[$dataType . 'Tag'])) {
  1174. foreach ($data[$dataType . 'Tag'] as $k => $tag) {
  1175. if (!$tag['Tag']['exportable'] || (!empty($tag['local']) && empty($server['Server']['internal']))) {
  1176. unset($data[$dataType . 'Tag'][$k]);
  1177. } else {
  1178. unset($tag['org_id']);
  1179. $data['Tag'][] = $tag['Tag'];
  1180. }
  1181. }
  1182. }
  1183. unset($data[$dataType . 'Tag']);
  1184. }
  1185. return $data;
  1186. }
  1187. private function __prepareAttributesForSync($data, $server)
  1188. {
  1189. // prepare attribute for sync
  1190. if (!empty($data['Attribute'])) {
  1191. foreach ($data['Attribute'] as $key => $attribute) {
  1192. $data['Attribute'][$key] = $this->__updateAttributeForSync($attribute, $server);
  1193. if (empty($data['Attribute'][$key])) {
  1194. unset($data['Attribute'][$key]);
  1195. } else {
  1196. $data['Attribute'][$key] = $this->__removeNonExportableTags($data['Attribute'][$key], 'Attribute', $server);
  1197. }
  1198. }
  1199. $data['Attribute'] = array_values($data['Attribute']);
  1200. }
  1201. return $data;
  1202. }
  1203. private function __prepareObjectsForSync($data, $server)
  1204. {
  1205. // prepare Object for sync
  1206. if (!empty($data['Object'])) {
  1207. foreach ($data['Object'] as $key => $object) {
  1208. $data['Object'][$key] = $this->__updateObjectForSync($object, $server);
  1209. if (empty($data['Object'][$key])) {
  1210. unset($data['Object'][$key]);
  1211. } else {
  1212. $data['Object'][$key] = $this->__prepareAttributesForSync($data['Object'][$key], $server);
  1213. }
  1214. }
  1215. $data['Object'] = array_values($data['Object']);
  1216. }
  1217. return $data;
  1218. }
  1219. private function __prepareEventReportForSync($data, $server)
  1220. {
  1221. if (!empty($data['EventReport'])) {
  1222. foreach ($data['EventReport'] as $key => $report) {
  1223. $data['EventReport'][$key] = $this->__updateEventReportForSync($report, $server);
  1224. if (empty($data['EventReport'][$key])) {
  1225. unset($data['EventReport'][$key]);
  1226. }
  1227. }
  1228. $data['EventReport'] = array_values($data['EventReport']);
  1229. }
  1230. if (isset($data['EventReport']) && empty($data['EventReport'])) {
  1231. unset($data['EventReport']);
  1232. }
  1233. return $data;
  1234. }
  1235. private function __updateEventForSync($event, $server)
  1236. {
  1237. $event = $this->__rearrangeEventStructureForSync($event);
  1238. $event['Event'] = $this->__removeNonExportableTags($event['Event'], 'Event', $server);
  1239. // Add the local server to the list of instances in the SG
  1240. if (isset($event['Event']['SharingGroup']) && isset($event['Event']['SharingGroup']['SharingGroupServer'])) {
  1241. foreach ($event['Event']['SharingGroup']['SharingGroupServer'] as &$s) {
  1242. if ($s['server_id'] == 0) {
  1243. $s['Server'] = array(
  1244. 'id' => 0,
  1245. 'url' => $this->__getAnnounceBaseurl(),
  1246. 'name' => $this->__getAnnounceBaseurl()
  1247. );
  1248. }
  1249. }
  1250. }
  1251. $event['Event'] = $this->__prepareAttributesForSync($event['Event'], $server);
  1252. $event['Event'] = $this->__prepareObjectsForSync($event['Event'], $server);
  1253. $event['Event'] = $this->__prepareEventReportForSync($event['Event'], $server);
  1254. // Downgrade the event from connected communities to community only
  1255. if (!$server['Server']['internal'] && $event['Event']['distribution'] == 2) {
  1256. $event['Event']['distribution'] = 1;
  1257. }
  1258. return $event;
  1259. }
  1260. private function __updateObjectForSync($object, $server)
  1261. {
  1262. if (!$server['Server']['internal'] && $object['distribution'] < 2) {
  1263. return false;
  1264. }
  1265. // Downgrade the object from connected communities to community only
  1266. if (!$server['Server']['internal'] && $object['distribution'] == 2) {
  1267. $object['distribution'] = 1;
  1268. }
  1269. // If the object has a sharing group attached, make sure it can be transferred
  1270. if ($object['distribution'] == 4) {
  1271. if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('Object' => $object), $server, 'Object') === false) {
  1272. return false;
  1273. }
  1274. // Add the local server to the list of instances in the SG
  1275. if (isset($object['SharingGroup']['SharingGroupServer'])) {
  1276. foreach ($object['SharingGroup']['SharingGroupServer'] as &$s) {
  1277. if ($s['server_id'] == 0) {
  1278. $s['Server'] = array(
  1279. 'id' => 0,
  1280. 'url' => $this->__getAnnounceBaseurl(),
  1281. 'name' => $this->__getAnnounceBaseurl()
  1282. );
  1283. }
  1284. }
  1285. }
  1286. }
  1287. return $object;
  1288. }
  1289. private function __getAnnounceBaseurl()
  1290. {
  1291. $baseurl = '';
  1292. if (!empty(Configure::read('MISP.external_baseurl'))) {
  1293. $baseurl = Configure::read('MISP.external_baseurl');
  1294. } else if (!empty(Configure::read('MISP.baseurl'))) {
  1295. $baseurl = Configure::read('MISP.baseurl');
  1296. }
  1297. return $baseurl;
  1298. }
  1299. private function __updateAttributeForSync($attribute, $server)
  1300. {
  1301. // do not keep attributes that are private, nor cluster
  1302. if (!$server['Server']['internal'] && $attribute['distribution'] < 2) {
  1303. return false;
  1304. }
  1305. // Downgrade the attribute from connected communities to community only
  1306. if (!$server['Server']['internal'] && $attribute['distribution'] == 2) {
  1307. $attribute['distribution'] = 1;
  1308. }
  1309. // If the attribute has a sharing group attached, make sure it can be transferred
  1310. if ($attribute['distribution'] == 4) {
  1311. if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('Attribute' => $attribute), $server, 'Attribute') === false) {
  1312. return false;
  1313. }
  1314. // Add the local server to the list of instances in the SG
  1315. if (!empty($attribute['SharingGroup']['SharingGroupServer'])) {
  1316. foreach ($attribute['SharingGroup']['SharingGroupServer'] as &$s) {
  1317. if ($s['server_id'] == 0) {
  1318. $s['Server'] = array(
  1319. 'id' => 0,
  1320. 'url' => $this->__getAnnounceBaseurl(),
  1321. 'name' => $this->__getAnnounceBaseurl()
  1322. );
  1323. }
  1324. }
  1325. }
  1326. }
  1327. // also add the encoded attachment
  1328. if ($this->Attribute->typeIsAttachment($attribute['type'])) {
  1329. $attribute['data'] = $this->Attribute->base64EncodeAttachment($attribute);
  1330. }
  1331. // Passing the attribute ID together with the attribute could cause the deletion of attributes after a publish/push
  1332. // Basically, if the attribute count differed between two instances, and the instance with the lower attribute
  1333. // count pushed, the old attributes with the same ID got overwritten. Unsetting the ID before pushing it
  1334. // solves the issue and a new attribute is always created.
  1335. unset($attribute['id']);
  1336. // remove value1 and value2 from the output
  1337. unset($attribute['value1']);
  1338. unset($attribute['value2']);
  1339. return $attribute;
  1340. }
  1341. private function __updateEventReportForSync($report, $server)
  1342. {
  1343. if (!$server['Server']['internal'] && $report['distribution'] < 2) {
  1344. return false;
  1345. }
  1346. // check if remote version support event reports
  1347. $eventReportSupportedByRemote = false;
  1348. $uri = $server['Server']['url'] . '/eventReports/add';
  1349. $HttpSocket = $this->setupHttpSocket($server, null);
  1350. $request = $this->setupSyncRequest($server);
  1351. try {
  1352. $response = $HttpSocket->get($uri, false, $request);
  1353. if ($response->isOk()) {
  1354. $apiDescription = json_decode($response->body, true);
  1355. $eventReportSupportedByRemote = !empty($apiDescription['description']);
  1356. }
  1357. } catch (Exception $e) {
  1358. $this->Log = ClassRegistry::init('Log');
  1359. $message = __('Remote version does not support event report.');
  1360. $this->Log->createLogEntry('SYSTEM', $action, 'Server', $id, $message);
  1361. }
  1362. if (!$eventReportSupportedByRemote) {
  1363. return [];
  1364. }
  1365. // Downgrade the object from connected communities to community only
  1366. if (!$server['Server']['internal'] && $report['distribution'] == 2) {
  1367. $report['distribution'] = 1;
  1368. }
  1369. // If the object has a sharing group attached, make sure it can be transferred
  1370. if ($report['distribution'] == 4) {
  1371. if (!$server['Server']['internal'] && $this->checkDistributionForPush(array('EventReport' => $report), $server, 'EventReport') === false) {
  1372. return false;
  1373. }
  1374. // Add the local server to the list of instances in the SG
  1375. if (isset($object['SharingGroup']['SharingGroupServer'])) {
  1376. foreach ($object['SharingGroup']['SharingGroupServer'] as &$s) {
  1377. if ($s['server_id'] == 0) {
  1378. $s['Server'] = array(
  1379. 'id' => 0,
  1380. 'url' => $this->__getAnnounceBaseurl(),
  1381. 'name' => $this->__getAnnounceBaseurl()
  1382. );
  1383. }
  1384. }
  1385. }
  1386. }
  1387. return $report;
  1388. }
  1389. /**
  1390. * Download event from remote server.
  1391. *
  1392. * @param int $eventId
  1393. * @param array $server
  1394. * @param null|HttpSocket $HttpSocket
  1395. * @param boolean $metadataOnly, if True, we only retrieve the metadata
  1396. * without attributes and attachments which is much faster
  1397. * @return array
  1398. * @throws Exception
  1399. */
  1400. public function downloadEventFromServer($eventId, $server, $HttpSocket=null, $metadataOnly=false)
  1401. {
  1402. $url = $server['Server']['url'];
  1403. $HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
  1404. $request = $this->setupSyncRequest($server);
  1405. if ($metadataOnly) {
  1406. $uri = $url . '/events/index';
  1407. $data = ['eventid' => $eventId];
  1408. $data = json_encode($data);
  1409. $response = $HttpSocket->post($uri, $data, $request);
  1410. } else {
  1411. $uri = $url . '/events/view/' . $eventId . '/deleted[]:0/deleted[]:1/excludeGalaxy:1';
  1412. if (!empty($server['Server']['internal'])) {
  1413. $uri = $uri . '/excludeLocalTags:1';
  1414. }
  1415. $response = $HttpSocket->get($uri, $data = '', $request);
  1416. }
  1417. if ($response === false) {
  1418. throw new Exception("Could not reach '$uri'.");
  1419. } else if (!$response->isOk()) {
  1420. throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
  1421. }
  1422. $event = json_decode($response->body, true);
  1423. if ($event === null) {
  1424. throw new Exception('Could not parse event JSON: ' . json_last_error_msg(), json_last_error());
  1425. }
  1426. return $event;
  1427. }
  1428. public function quickDelete($event)
  1429. {
  1430. $id = $event['Event']['id'];
  1431. $this->Thread = ClassRegistry::init('Thread');
  1432. $thread = $this->Thread->find('first', array(
  1433. 'conditions' => array('Thread.event_id' => $id),
  1434. 'fields' => array('Thread.id'),
  1435. 'recursive' => -1
  1436. ));
  1437. $thread_id = !empty($thread) ? $thread['Thread']['id'] : false;
  1438. $relations = array(
  1439. array(
  1440. 'table' => 'attributes',
  1441. 'foreign_key' => 'event_id',
  1442. 'value' => $id
  1443. ),
  1444. array(
  1445. 'table' => 'shadow_attributes',
  1446. 'foreign_key' => 'event_id',
  1447. 'value' => $id
  1448. ),
  1449. array(
  1450. 'table' => 'event_tags',
  1451. 'foreign_key' => 'event_id',
  1452. 'value' => $id
  1453. ),
  1454. array(
  1455. 'table' => 'attribute_tags',
  1456. 'foreign_key' => 'event_id',
  1457. 'value' => $id
  1458. ),
  1459. array(
  1460. 'table' => 'threads',
  1461. 'foreign_key' => 'event_id',
  1462. 'value' => $id
  1463. ),
  1464. array(
  1465. 'table' => 'sightings',
  1466. 'foreign_key' => 'event_id',
  1467. 'value' => $id
  1468. ),
  1469. array(
  1470. 'table' => 'event_delegations',
  1471. 'foreign_key' => 'event_id',
  1472. 'value' => $id
  1473. ),
  1474. array(
  1475. 'table' => 'objects',
  1476. 'foreign_key' => 'event_id',
  1477. 'value' => $id
  1478. ),
  1479. array(
  1480. 'table' => 'object_references',
  1481. 'foreign_key' => 'event_id',
  1482. 'value' => $id
  1483. ),
  1484. array(
  1485. 'table' => 'event_reports',
  1486. 'foreign_key' => 'event_id',
  1487. 'value' => $id
  1488. )
  1489. );
  1490. if ($thread_id) {
  1491. $relations[] = array(
  1492. 'table' => 'posts',
  1493. 'foreign_key' => 'thread_id',
  1494. 'value' => $thread_id
  1495. );
  1496. }
  1497. if (!Configure::read('MISP.completely_disable_correlation')) {
  1498. array_push(
  1499. $relations,
  1500. array(
  1501. 'table' => 'correlations',
  1502. 'foreign_key' => 'event_id',
  1503. 'value' => $id
  1504. ),
  1505. array(
  1506. 'table' => 'correlations',
  1507. 'foreign_key' => '1_event_id',
  1508. 'value' => $id
  1509. )
  1510. );
  1511. }
  1512. App::uses('QueryTool', 'Tools');
  1513. $queryTool = new QueryTool();
  1514. foreach ($relations as $relation) {
  1515. $queryTool->quickDelete($relation['table'], $relation['foreign_key'], $relation['value'], $this);
  1516. }
  1517. return $this->delete($id, false);
  1518. }
  1519. public function createEventConditions($user)
  1520. {
  1521. $conditions = array();
  1522. if (!$user['Role']['perm_site_admin']) {
  1523. $sgids = $this->cacheSgids($user, true);
  1524. $unpublishedPrivate = Configure::read('MISP.unpublishedprivate');
  1525. $conditions['AND']['OR'] = array(
  1526. 'Event.org_id' => $user['org_id'],
  1527. array(
  1528. 'AND' => array(
  1529. 'Event.distribution >' => 0,
  1530. 'Event.distribution <' => 4,
  1531. $unpublishedPrivate ? array('Event.published' => 1) : array(),
  1532. ),
  1533. ),
  1534. array(
  1535. 'AND' => array(
  1536. 'Event.sharing_group_id' => $sgids,
  1537. 'Event.distribution' => 4,
  1538. $unpublishedPrivate ? array('Event.published' => 1) : array(),
  1539. )
  1540. )
  1541. );
  1542. }
  1543. return $conditions;
  1544. }
  1545. public function set_filter_wildcard(&$params, $conditions, $options)
  1546. {
  1547. $tempConditions = array();
  1548. $tempConditions[] = array('Event.info LIKE' => $params['wildcard']);
  1549. $attributeParams = array('value1', 'value2', 'comment');
  1550. foreach ($attributeParams as $attributeParam) {
  1551. $subQueryOptions = array(
  1552. 'conditions' => array('Attribute.' . $attributeParam . ' LIKE' => $params['wildcard']),
  1553. 'fields' => array('event_id')
  1554. );
  1555. $tempConditions[] = $this->subQueryGenerator($this->Attribute, $subQueryOptions, 'Event.id');
  1556. }
  1557. $tagScopes = array('Event', 'Attribute');
  1558. $this->AttributeTag = ClassRegistry::init('AttributeTag');
  1559. $tagIds = $this->AttributeTag->Tag->find('list', array(
  1560. 'recursive' => -1,
  1561. 'conditions' => array('Tag.name LIKE' => $params['wildcard']),
  1562. 'fields' => array('Tag.id')
  1563. ));
  1564. if (!empty($tagIds)) {
  1565. foreach ($tagScopes as $tagScope) {
  1566. $subQueryOptions = array(
  1567. 'conditions' => array(
  1568. 'tag_id' => $tagIds,
  1569. ),
  1570. 'fields' => array('event_id')
  1571. );
  1572. $tempConditions[] = $this->subQueryGenerator($this->{$tagScope . 'Tag'}, $subQueryOptions, 'Event.id');
  1573. }
  1574. }
  1575. return $tempConditions;
  1576. }
  1577. public function set_filter_wildcard_attributes(&$params, $conditions, $options)
  1578. {
  1579. $tempConditions = array();
  1580. $tempConditions[] = array('Event.info LIKE' => $params['wildcard']);
  1581. $attributeParams = array('value1', 'value2', 'comment');
  1582. foreach ($attributeParams as $attributeParam) {
  1583. $tempConditions[] = array('Attribute.' . $attributeParam . ' LIKE' => $params['wildcard']);
  1584. }
  1585. $tagScopes = array('Event', 'Attribute');
  1586. $this->AttributeTag = ClassRegistry::init('AttributeTag');
  1587. $tagIds = $this->AttributeTag->Tag->find('list', array(
  1588. 'recursive' => -1,
  1589. 'conditions' => array('Tag.name LIKE' => $params['wildcard']),
  1590. 'fields' => array('Tag.id')
  1591. ));
  1592. if (!empty($tagIds)) {
  1593. $subQueryOptions = array(
  1594. 'conditions' => array(
  1595. 'tag_id' => $tagIds,
  1596. ),
  1597. 'fields' => array('event_id')
  1598. );
  1599. $tempConditions[] = $this->subQueryGenerator($this->EventTag, $subQueryOptions, 'Attribute.event_id');
  1600. $subQueryOptions = array(
  1601. 'conditions' => array(
  1602. 'tag_id' => $tagIds,
  1603. ),
  1604. 'fields' => array('attribute_id')
  1605. );
  1606. $tempConditions[] = $this->subQueryGenerator($this->AttributeTag, $subQueryOptions, 'Attribute.id');
  1607. }
  1608. return $tempConditions;
  1609. }
  1610. public function filterEventIds($user, &$params = array(), &$result_count = 0)
  1611. {
  1612. $conditions = $this->createEventConditions($user);
  1613. if (isset($params['wildcard'])) {
  1614. $temp = array();
  1615. $options = array(
  1616. 'filter' => 'wildcard',
  1617. 'scope' => 'Event',
  1618. 'pop' => false,
  1619. 'context' => 'Event'
  1620. );
  1621. $conditions['AND'][] = array('OR' => $this->set_filter_wildcard($params, $temp, $options));
  1622. } else {
  1623. $simple_params = array(
  1624. 'Event' => array(
  1625. 'eventid' => array('function' => 'set_filter_eventid', 'pop' => true),
  1626. 'eventinfo' => array('function' => 'set_filter_eventinfo'),
  1627. 'ignore' => array('function' => 'set_filter_ignore'),
  1628. 'tags' => array('function' => 'set_filter_tags'),
  1629. 'from' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1630. 'to' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1631. 'date' => array('function' => 'set_filter_date', 'pop' => true),
  1632. 'last' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1633. 'timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1634. 'event_timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1635. 'publish_timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
  1636. 'org' => array('function' => 'set_filter_org', 'pop' => true),
  1637. 'uuid' => array('function' => 'set_filter_uuid', 'pop' => true),
  1638. 'published' => array('function' => 'set_filter_published', 'pop' => true),
  1639. 'threat_level_id' => array('function' => 'set_filter_threat_level_id', 'pop' => true)
  1640. ),
  1641. 'Object' => array(
  1642. 'object_name' => array('function' => 'set_filter_object_name'),
  1643. 'object_template_uuid' => array('function' => 'set_filter_object_template_uuid'),
  1644. 'object_template_version' => array('function' => 'set_filter_object_template_version'),
  1645. 'deleted' => array('function' => 'set_filter_deleted')
  1646. ),
  1647. 'Attribute' => array(
  1648. 'value' => array('function' => 'set_filter_value'),
  1649. 'category' => array('function' => 'set_filter_simple_attribute'),
  1650. 'type' => array('function' => 'set_filter_type'),
  1651. 'object_relation' => array('function' => 'set_filter_simple_attribute'),
  1652. 'tags' => array('function' => 'set_filter_tags', 'pop' => true),
  1653. 'ignore' => array('function' => 'set_filter_ignore'),
  1654. 'deleted' => array('function' => 'set_filter_deleted'),
  1655. 'to_ids' => array('function' => 'set_filter_to_ids'),
  1656. 'comment' => array('function' => 'set_filter_comment')
  1657. )
  1658. );
  1659. foreach ($params as $param => $paramData) {
  1660. foreach ($simple_params as $scope => $simple_param_scoped) {
  1661. if (isset($simple_param_scoped[$param]) && $params[$param] !== false) {
  1662. $options = array(
  1663. 'filter' => $param,
  1664. 'scope' => $scope,
  1665. 'pop' => !empty($simple_param_scoped[$param]['pop']),
  1666. 'context' => 'Event'
  1667. );
  1668. if ($scope === 'Event') {
  1669. $conditions = $this->{$simple_param_scoped[$param]['function']}($params, $conditions, $options);
  1670. } else {
  1671. $temp = array();
  1672. $temp = $this->{$simple_param_scoped[$param]['function']}($params, $temp, $options);
  1673. if (!empty($temp)) {
  1674. $subQueryOptions = array(
  1675. 'conditions' => $temp,
  1676. 'fields' => array(
  1677. 'event_id'
  1678. )
  1679. );
  1680. $subQuery = $this->subQueryGenerator($this->{$scope}, $subQueryOptions, 'Event.id');
  1681. if ($param === 'value') {
  1682. $subQuery[0] = explode('WHERE', $subQuery[0]);
  1683. $subQuery[0][0] .= ' USE INDEX (value1, value2) ';
  1684. $subQuery[0] = implode('WHERE', $subQuery[0]);
  1685. }
  1686. $conditions['AND'][] = $subQuery;
  1687. }
  1688. }
  1689. }
  1690. }
  1691. }
  1692. }
  1693. $fields = array('Event.id');
  1694. if (!empty($params['include_attribute_count'])) {
  1695. $fields[] = 'Event.attribute_count';
  1696. }
  1697. $find_params = array(
  1698. 'conditions' => $conditions,
  1699. 'recursive' => -1,
  1700. 'fields' => $fields
  1701. );
  1702. if (isset($params['order'])) {
  1703. $find_params['order'] = $params['order'];
  1704. }
  1705. if (isset($params['limit'])) {
  1706. // Get the count (but not the actual data) of results for paginators
  1707. $result_count = $this->find('count', $find_params);
  1708. $find_params['limit'] = $params['limit'];
  1709. if (isset($params['page'])) {
  1710. $find_params['page'] = $params['page'];
  1711. }
  1712. }
  1713. $results = $this->find('list', $find_params);
  1714. if (!isset($params['limit'])) {
  1715. $result_count = count($results);
  1716. }
  1717. return $results;
  1718. }
  1719. public function fetchSimpleEventIds($user, $params = array())
  1720. {
  1721. $conditions = $this->createEventConditions($user);
  1722. $conditions['AND'][] = $params['conditions'];
  1723. $results = array_values($this->find('list', array(
  1724. 'conditions' => $conditions,
  1725. 'recursive' => -1,
  1726. 'fields' => array('Event.id')
  1727. )));
  1728. return $results;
  1729. }
  1730. /**
  1731. * @param array $user
  1732. * @param string|int $id Event ID or UUID
  1733. * @param array $params
  1734. * @return array|null
  1735. */
  1736. public function fetchSimpleEvent(array $user, $id, array $params = array())
  1737. {
  1738. $conditions = $this->createEventConditions($user);
  1739. if (is_numeric($id)) {
  1740. $conditions['AND'][]['Event.id'] = $id;
  1741. } else if (Validation::uuid($id)) {
  1742. $conditions['AND'][]['Event.uuid'] = $id;
  1743. } else {
  1744. return null;
  1745. }
  1746. if (isset($params['conditions'])) {
  1747. $conditions['AND'][] = $params['conditions'];
  1748. }
  1749. $params['conditions'] = $conditions;
  1750. $params['recursive'] = -1;
  1751. return $this->find('first', $params);
  1752. }
  1753. /**
  1754. * @param array $user
  1755. * @param array $params
  1756. * @param bool $includeOrgc
  1757. * @return array
  1758. */
  1759. public function fetchSimpleEvents(array $user, array $params, $includeOrgc = false)
  1760. {
  1761. $conditions = $this->createEventConditions($user);
  1762. $conditions['AND'][] = $params['conditions'];
  1763. $params = array(
  1764. 'conditions' => $conditions,
  1765. 'recursive' => -1
  1766. );
  1767. if ($includeOrgc) {
  1768. $params['contain'] = array('Orgc.name');
  1769. }
  1770. return $this->find('all', $params);
  1771. }
  1772. public function fetchEventIds($user, $from = false, $to = false, $last = false, $list = false, $timestamp = false, $publish_timestamp = false, $eventIdList = false)
  1773. {
  1774. // restricting to non-private or same org if the user is not a site-admin.
  1775. $conditions = $this->createEventConditions($user);
  1776. $fields = array('Event.id', 'Event.org_id', 'Event.distribution', 'Event.sharing_group_id');
  1777. if ($from) {
  1778. $conditions['AND'][] = array('Event.date >=' => $from);
  1779. }
  1780. if ($to) {
  1781. $conditions['AND'][] = array('Event.date <=' => $to);
  1782. }
  1783. if ($last) {
  1784. $conditions['AND'][] = array('Event.publish_timestamp >=' => $last);
  1785. }
  1786. if ($timestamp) {
  1787. $conditions['AND'][] = array('Event.timestamp >=' => $timestamp);
  1788. }
  1789. if ($publish_timestamp) {
  1790. $conditions['AND'][] = array('Event.publish_timestamp >=' => $publish_timestamp);
  1791. }
  1792. if ($eventIdList) {
  1793. $conditions['AND'][] = array('Event.id' => $eventIdList);
  1794. }
  1795. if ($list) {
  1796. $params = array(
  1797. 'conditions' => $conditions,
  1798. 'recursive' => -1,
  1799. );
  1800. $results = array_values($this->find('list', $params));
  1801. } else {
  1802. $params = array(
  1803. 'conditions' => $conditions,
  1804. 'recursive' => -1,
  1805. 'fields' => $fields,
  1806. );
  1807. $results = $this->find('all', $params);
  1808. }
  1809. return $results;
  1810. }
  1811. /*
  1812. * Unlike the other fetchers, this one foregoes any ACL checks.
  1813. * the objective is simple: Fetch the given event with all related objects needed for the ZMQ output,
  1814. * standardising on this function for fetching the event to be passed to the pubsub handler
  1815. */
  1816. public function quickFetchEvent($id)
  1817. {
  1818. $event = $this->find('first', array(
  1819. 'recursive' => -1,
  1820. 'conditions' => array('Event.id' => $id),
  1821. 'contain' => array(
  1822. 'Orgc' => array(
  1823. 'fields' => array('Orgc.id', 'Orgc.uuid', 'Orgc.name')
  1824. ),
  1825. 'EventTag' => array(
  1826. 'Tag' => array('fields' => array('Tag.id', 'Tag.name', 'Tag.colour', 'Tag.exportable'))
  1827. )
  1828. )
  1829. ));
  1830. return $event;
  1831. }
  1832. //Once the data about the user is gathered from the appropriate sources, fetchEvent is called from the controller or background process.
  1833. // Possible options:
  1834. // eventid: single event ID
  1835. // idList: array with event IDs
  1836. // tags: string with the usual tag syntax
  1837. // from: date string (YYYY-MM-DD)
  1838. // to: date string (YYYY-MM-DD)
  1839. // includeAllTags: true will include the tags that are marked as non-exportable
  1840. // includeAttachments: true will attach the attachments to the attributes in the data field
  1841. public function fetchEvent($user, $options = array(), $useCache = false)
  1842. {
  1843. if (isset($options['Event.id'])) {
  1844. $options['eventid'] = $options['Event.id'];
  1845. }
  1846. $possibleOptions = array(
  1847. 'eventid',
  1848. 'idList',
  1849. 'tags',
  1850. 'from',
  1851. 'to',
  1852. 'last',
  1853. 'to_ids',
  1854. 'includeAllTags', // include also non exportable tags, default `false`
  1855. 'includeAttachments',
  1856. 'event_uuid',
  1857. 'distribution',
  1858. 'sharing_group_id',
  1859. 'disableSiteAdmin',
  1860. 'metadata',
  1861. 'enforceWarninglist', // return just attributes that contains no warnings
  1862. 'sgReferenceOnly',
  1863. 'flatten',
  1864. 'blockedAttributeTags',
  1865. 'eventsExtendingUuid',
  1866. 'extended',
  1867. 'excludeGalaxy',
  1868. 'includeRelatedTags',
  1869. 'excludeLocalTags',
  1870. 'includeDecayScore',
  1871. 'includeSightingdb',
  1872. 'includeFeedCorrelations',
  1873. 'includeServerCorrelations',
  1874. 'includeWarninglistHits',
  1875. 'noEventReports', // do not include event report in event data
  1876. 'noShadowAttributes', // do not fetch proposals
  1877. );
  1878. if (!isset($options['excludeLocalTags']) && !empty($user['Role']['perm_sync']) && empty($user['Role']['perm_site_admin'])) {
  1879. $options['excludeLocalTags'] = 1;
  1880. }
  1881. if (!isset($options['includeEventCorrelations'])) {
  1882. $options['includeEventCorrelations'] = true;
  1883. }
  1884. foreach ($possibleOptions as $opt) {
  1885. if (!isset($options[$opt])) {
  1886. $options[$opt] = false;
  1887. }
  1888. }
  1889. $conditions = $this->createEventConditions($user);
  1890. if ($options['eventid']) {
  1891. $conditions['AND'][] = array("Event.id" => $options['eventid']);
  1892. }
  1893. if ($options['eventsExtendingUuid']) {
  1894. if (!is_array($options['eventsExtendingUuid'])) {
  1895. $options['eventsExtendingUuid'] = array($options['eventsExtendingUuid']);
  1896. }
  1897. foreach ($options['eventsExtendingUuid'] as $extendedEvent) {
  1898. $extendedUuids = array();
  1899. if (!Validation::uuid($extendedEvent)) {
  1900. $eventUuid = $this->find('first', array(
  1901. 'recursive' => -1,
  1902. 'conditions' => array('Event.id' => $extendedEvent),
  1903. 'fields' => array('Event.uuid')
  1904. ));
  1905. if (!empty($eventUuid)) {
  1906. $extendedUuids[] = $eventUuid['Event']['uuid'];
  1907. }
  1908. } else {
  1909. $extendedUuids[] = $extendedEvent;
  1910. }
  1911. }
  1912. if (!empty($extendedUuids)) {
  1913. $conditions['AND'][] = array('Event.extends_uuid' => $extendedUuids);
  1914. } else {
  1915. // We've set as a search pattern any event that extends an event and didn't find anything
  1916. // valid, make sure we don't get everything thrown in our face that the user can see.
  1917. $conditions['AND'][] = array('Event.id' => -1);
  1918. }
  1919. }
  1920. if (!isset($user['org_id'])) {
  1921. throw new Exception('There was an error with the user account.');
  1922. }
  1923. $isSiteAdmin = $user['Role']['perm_site_admin'];
  1924. if (isset($options['disableSiteAdmin']) && $options['disableSiteAdmin']) {
  1925. $isSiteAdmin = false;
  1926. }
  1927. $conditionsAttributes = array();
  1928. $conditionsObjects = array();
  1929. $conditionsEventReport = array();
  1930. if ($options['flatten']) {
  1931. $flatten = true;
  1932. } else {
  1933. $flatten = false;
  1934. }
  1935. $sgids = $this->cacheSgids($user, $useCache);
  1936. // restricting to non-private or same org if the user is not a site-admin.
  1937. if (!$isSiteAdmin) {
  1938. // if delegations are enabled, check if there is an event that the current user might see because of the request itself
  1939. if (Configure::read('MISP.delegation')) {
  1940. $delegatedEventIDs = $this->__cachedelegatedEventIDs($user, $useCache);
  1941. $conditions['AND']['OR']['Event.id'] = $delegatedEventIDs;
  1942. }
  1943. $attributeCondSelect = '(SELECT events.org_id FROM events WHERE events.id = Attribute.event_id)';
  1944. $objectCondSelect = '(SELECT events.org_id FROM events WHERE events.id = Object.event_id)';
  1945. $eventReportCondSelect = '(SELECT events.org_id FROM events WHERE events.id = EventReport.event_id)';
  1946. if ($this->getDataSource()->config['datasource'] == 'Database/Postgres') {
  1947. $schemaName = $this->getDataSource()->config['schema'];
  1948. $attributeCondSelect = sprintf('(SELECT "%s"."events"."org_id" FROM "%s"."events" WHERE "%s"."events"."id" = "Attribute"."event_id")', $schemaName, $schemaName, $schemaName);
  1949. $objectCondSelect = sprintf('(SELECT "%s"."events"."org_id" FROM "%s"."events" WHERE "%s"."events"."id" = "Object"."event_id")', $schemaName, $schemaName, $schemaName);
  1950. $eventReportCondSelect = sprintf('(SELECT "%s"."events"."org_id" FROM "%s"."events" WHERE "%s"."events"."id" = "EventReport"."event_id")', $schemaName, $schemaName, $schemaName);
  1951. }
  1952. $conditionsAttributes['AND'][0]['OR'] = array(
  1953. array('AND' => array(
  1954. 'Attribute.distribution >' => 0,
  1955. 'Attribute.distribution !=' => 4,
  1956. )),
  1957. array('AND' => array(
  1958. 'Attribute.distribution' => 4,
  1959. 'Attribute.sharing_group_id' => $sgids,
  1960. )),
  1961. $attributeCondSelect => $user['org_id']
  1962. );
  1963. $conditionsObjects['AND'][0]['OR'] = array(
  1964. array('AND' => array(
  1965. 'Object.distribution >' => 0,
  1966. 'Object.distribution !=' => 4,
  1967. )),
  1968. array('AND' => array(
  1969. 'Object.distribution' => 4,
  1970. 'Object.sharing_group_id' => $sgids,
  1971. )),
  1972. $objectCondSelect => $user['org_id']
  1973. );
  1974. $conditionsEventReport['AND'][0]['OR'] = array(
  1975. array('AND' => array(
  1976. 'EventReport.distribution >' => 0,
  1977. 'EventReport.distribution !=' => 4,
  1978. )),
  1979. array('AND' => array(
  1980. 'EventReport.distribution' => 4,
  1981. 'EventReport.sharing_group_id' => $sgids,
  1982. )),
  1983. $eventReportCondSelect => $user['org_id']
  1984. );
  1985. }
  1986. if ($options['distribution']) {
  1987. $conditions['AND'][] = array('Event.distribution' => $options['distribution']);
  1988. $conditionsAttributes['AND'][] = array('Attribute.distribution' => $options['distribution']);
  1989. $conditionsObjects['AND'][] = array('Object.distribution' => $options['distribution']);
  1990. $conditionsEventReport['AND'][] = array('EventReport.distribution' => $options['distribution']);
  1991. }
  1992. if ($options['sharing_group_id']) {
  1993. $conditions['AND'][] = array('Event.sharing_group_id' => $options['sharing_group_id']);
  1994. $conditionsAttributes['AND'][] = array('Attribute.sharing_group_id' => $options['sharing_group_id']);
  1995. $conditionsObjects['AND'][] = array('Object.sharing_group_id' => $options['sharing_group_id']);
  1996. $conditionsEventReport['AND'][] = array('EventReport.sharing_group_id' => $options['sharing_group_id']);
  1997. }
  1998. if ($options['from']) {
  1999. $conditions['AND'][] = array('Event.date >=' => $options['from']);
  2000. }
  2001. if ($options['to']) {
  2002. $conditions['AND'][] = array('Event.date <=' => $options['to']);
  2003. }
  2004. if ($options['last']) {
  2005. $conditions['AND'][] = array('Event.publish_timestamp >=' => $options['last']);
  2006. }
  2007. if ($options['event_uuid']) {
  2008. $conditions['AND'][] = array('Event.uuid' => $options['event_uuid']);
  2009. }
  2010. if (!empty($options['includeRelatedTags'])) {
  2011. $options['includeGranularCorrelations'] = 1;
  2012. }
  2013. if (isset($options['ignore']) && empty($options['ignore'])) {
  2014. $conditions['AND'][] = array('Event.published' => 1);
  2015. $conditionsAttributes['AND'][] = array('Attribute.to_ids' => 1);
  2016. }
  2017. $softDeletables = array('Attribute', 'Object', 'ObjectReference', 'EventReport');
  2018. if (isset($options['deleted'])) {
  2019. if (!is_array($options['deleted'])) {
  2020. $options['deleted'] = array($options['deleted']);
  2021. }
  2022. foreach ($options['deleted'] as $deleted_key => $deleted_value) {
  2023. if ($deleted_value === 'only') {
  2024. $deleted_value = 1;
  2025. }
  2026. $options['deleted'][$deleted_key] = intval($deleted_value);
  2027. }
  2028. if (!$user['Role']['perm_sync']) {
  2029. foreach ($softDeletables as $softDeletable) {
  2030. if (in_array(0, $options['deleted'])) {
  2031. $deletion_subconditions = array(
  2032. sprintf('%s.deleted', $softDeletable) => 0
  2033. );
  2034. } else {
  2035. $deletion_subconditions = array(
  2036. '1=0'
  2037. );
  2038. }
  2039. ${'conditions' . $softDeletable . 's'}['AND'][] = array(
  2040. 'OR' => array(
  2041. 'AND' => array(
  2042. sprintf('(SELECT events.org_id FROM events WHERE events.id = %s.event_id)', $softDeletable) => $user['org_id'],
  2043. sprintf('%s.deleted', $softDeletable) => $options['deleted']
  2044. ),
  2045. $deletion_subconditions
  2046. )
  2047. );
  2048. }
  2049. } else {
  2050. foreach ($softDeletables as $softDeletable) {
  2051. ${'conditions' . $softDeletable . 's'}['AND'][] = array(
  2052. sprintf('%s.deleted', $softDeletable) => $options['deleted']
  2053. );
  2054. }
  2055. }
  2056. } else {
  2057. foreach ($softDeletables as $softDeletable) {
  2058. ${'conditions' . $softDeletable . 's'}['AND'][$softDeletable . '.deleted'] = 0;
  2059. }
  2060. }
  2061. $proposal_conditions = array('OR' => array('ShadowAttribute.deleted' => 0));
  2062. if (isset($options['deleted_proposals'])) {
  2063. if ($isSiteAdmin) {
  2064. $proposal_conditions = array('OR' => array('ShadowAttribute.deleted' => 1));
  2065. } else {
  2066. $proposal_conditions['OR'][] = array('(SELECT events.org_id FROM events WHERE events.id = ShadowAttribute.event_id)' => $user['org_id']);
  2067. }
  2068. }
  2069. if ($options['idList'] && !$options['tags']) {
  2070. $conditions['AND'][] = array('Event.id' => $options['idList']);
  2071. }
  2072. // If we sent any tags along, load the associated tag names for each attribute
  2073. if ($options['tags']) {
  2074. $temp = $this->__generateCachedTagFilters($options['tags']);
  2075. foreach ($temp as $rules) {
  2076. $conditions['AND'][] = $rules;
  2077. }
  2078. }
  2079. if (!empty($options['to_ids']) || $options['to_ids'] === 0) {
  2080. $conditionsAttributes['AND'][] = array('Attribute.to_ids' => $options['to_ids']);
  2081. }
  2082. // removing this for now, we export the to_ids == 0 attributes too, since there is a to_ids field indicating it in the .xml
  2083. // $conditionsAttributes['AND'] = array('Attribute.to_ids =' => 1);
  2084. // Same idea for the published. Just adjust the tools to check for this
  2085. // $conditions['AND'][] = array('Event.published =' => 1);
  2086. // do not expose all the data ...
  2087. $fields = array('Event.id', 'Event.orgc_id', 'Event.org_id', 'Event.date', 'Event.threat_level_id', 'Event.info', 'Event.published', 'Event.uuid', 'Event.attribute_count', 'Event.analysis', 'Event.timestamp', 'Event.distribution', 'Event.proposal_email_lock', 'Event.user_id', 'Event.locked', 'Event.publish_timestamp', 'Event.sharing_group_id', 'Event.disable_correlation', 'Event.extends_uuid');
  2088. $fieldsAtt = array('Attribute.id', 'Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.to_ids', 'Attribute.uuid', 'Attribute.event_id', 'Attribute.distribution', 'Attribute.timestamp', 'Attribute.comment', 'Attribute.sharing_group_id', 'Attribute.deleted', 'Attribute.disable_correlation', 'Attribute.object_id', 'Attribute.object_relation', 'Attribute.first_seen', 'Attribute.last_seen');
  2089. $fieldsObj = array('*');
  2090. $fieldsShadowAtt = array('ShadowAttribute.id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.value', 'ShadowAttribute.to_ids', 'ShadowAttribute.uuid', 'ShadowAttribute.event_uuid', 'ShadowAttribute.event_id', 'ShadowAttribute.old_id', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.proposal_to_delete', 'ShadowAttribute.timestamp', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen');
  2091. $fieldsOrg = array('id', 'name', 'uuid', 'local');
  2092. $fieldsEventReport = array('*');
  2093. $params = array(
  2094. 'conditions' => $conditions,
  2095. 'recursive' => 0,
  2096. 'fields' => $fields,
  2097. 'contain' => array(
  2098. 'ThreatLevel' => array(
  2099. 'fields' => array('ThreatLevel.name')
  2100. ),
  2101. 'Attribute' => array(
  2102. 'fields' => $fieldsAtt,
  2103. 'conditions' => $conditionsAttributes,
  2104. 'AttributeTag' => array(
  2105. 'order' => false
  2106. ),
  2107. 'order' => false
  2108. ),
  2109. 'Object' => array(
  2110. 'fields' => $fieldsObj,
  2111. 'conditions' => $conditionsObjects,
  2112. 'order' => false,
  2113. 'ObjectReference' => array(
  2114. 'order' => false
  2115. )
  2116. ),
  2117. 'ShadowAttribute' => array(
  2118. 'fields' => $fieldsShadowAtt,
  2119. 'conditions' => $proposal_conditions,
  2120. 'Org' => array('fields' => $fieldsOrg),
  2121. 'order' => false
  2122. ),
  2123. 'EventTag' => array(
  2124. 'order' => false
  2125. ),
  2126. 'EventReport' => array(
  2127. 'fields' => $fieldsEventReport,
  2128. 'conditions' => $conditionsEventReport,
  2129. 'order' => false
  2130. )
  2131. )
  2132. );
  2133. if (!empty($options['excludeLocalTags'])) {
  2134. $params['contain']['EventTag']['conditions'] = array(
  2135. 'EventTag.local' => 0
  2136. );
  2137. $params['contain']['Attribute']['AttributeTag']['conditions'] = array(
  2138. 'AttributeTag.local' => 0
  2139. );
  2140. }
  2141. if ($flatten) {
  2142. unset($params['contain']['Object']);
  2143. }
  2144. if ($options['noEventReports']) {
  2145. unset($params['contain']['EventReport']);
  2146. }
  2147. if ($options['noShadowAttributes']) {
  2148. unset($params['contain']['ShadowAttribute']);
  2149. }
  2150. if ($options['metadata']) {
  2151. unset($params['contain']['Attribute']);
  2152. unset($params['contain']['ShadowAttribute']);
  2153. unset($params['contain']['Object']);
  2154. unset($params['contain']['EventReport']);
  2155. }
  2156. $results = $this->find('all', $params);
  2157. if (empty($results)) {
  2158. return array();
  2159. }
  2160. $sharingGroupData = $options['sgReferenceOnly'] ? [] : $this->__cacheSharingGroupData($user, $useCache);
  2161. // Initialize classes that will be necessary during event fetching
  2162. if ((empty($options['metadata']) && empty($options['noSightings'])) && !isset($this->Sighting)) {
  2163. $this->Sighting = ClassRegistry::init('Sighting');
  2164. }
  2165. if (!empty($options['includeDecayScore']) && !isset($this->DecayingModel)) {
  2166. $this->DecayingModel = ClassRegistry::init('DecayingModel');
  2167. }
  2168. if (($options['includeFeedCorrelations'] || $options['includeServerCorrelations']) && !isset($this->Feed)) {
  2169. $this->Feed = ClassRegistry::init('Feed');
  2170. }
  2171. if (($options['enforceWarninglist'] || $options['includeWarninglistHits']) && !isset($this->Warninglist)) {
  2172. $this->Warninglist = ClassRegistry::init('Warninglist');
  2173. }
  2174. // Precache current user email
  2175. $userEmails = empty($user['id']) ? [] : [$user['id'] => $user['email']];
  2176. // Do some refactoring with the event
  2177. $fields = array(
  2178. 'common' => array('distribution', 'sharing_group_id', 'uuid'),
  2179. 'Attribute' => array('value', 'type', 'category', 'to_ids'),
  2180. 'Object' => array('name', 'meta-category')
  2181. );
  2182. if (!$options['includeAllTags']) {
  2183. $justExportableTags = true;
  2184. } else {
  2185. $justExportableTags = false;
  2186. }
  2187. foreach ($results as $eventKey => &$event) {
  2188. if ($event['Event']['distribution'] == 4 && !in_array($event['Event']['sharing_group_id'], $sgids)) {
  2189. $this->Log = ClassRegistry::init('Log');
  2190. $this->Log->create();
  2191. $this->Log->save(array(
  2192. 'org' => $user['Organisation']['name'],
  2193. 'model' => 'Event',
  2194. 'model_id' => $event['Event']['id'],
  2195. 'email' => $user['email'],
  2196. 'action' => 'fetchEvent',
  2197. 'user_id' => $user['id'],
  2198. 'title' => 'User was able to fetch the event but not the sharing_group it belongs to',
  2199. 'change' => ''
  2200. ));
  2201. unset($results[$eventKey]); // Current user cannot access sharing_group associated to this event
  2202. continue;
  2203. }
  2204. if ($options['includeWarninglistHits'] || $options['enforceWarninglist']) {
  2205. $eventWarnings = $this->Warninglist->attachWarninglistToAttributes($event['Attribute']);
  2206. $this->Warninglist->attachWarninglistToAttributes($event['ShadowAttribute']);
  2207. $event['warnings'] = $eventWarnings;
  2208. }
  2209. $this->__attachReferences($event, $fields);
  2210. $this->__attachTags($event, $justExportableTags);
  2211. $event = $this->Orgc->attachOrgsToEvent($event, $fieldsOrg);
  2212. if (!$options['sgReferenceOnly'] && $event['Event']['sharing_group_id']) {
  2213. $event['SharingGroup'] = $sharingGroupData[$event['Event']['sharing_group_id']]['SharingGroup'];
  2214. }
  2215. // Include information about event creator user email. This information is included for:
  2216. // - users from event creator org
  2217. // - site admins
  2218. // In export, this information will be included in `event_creator_email` field just for auditors of event creator org.
  2219. $sameOrg = $event['Event']['orgc_id'] === $user['org_id'];
  2220. if ($sameOrg || $user['Role']['perm_site_admin']) {
  2221. if (!isset($userEmails[$event['Event']['user_id']])) {
  2222. $userEmails[$event['Event']['user_id']] = $this->User->field('email', ['id' => $event['Event']['user_id']]);
  2223. }
  2224. $userEmail = $userEmails[$event['Event']['user_id']];
  2225. if ($sameOrg && $user['Role']['perm_audit']) {
  2226. $event['Event']['event_creator_email'] = $userEmail;
  2227. }
  2228. $event['User']['email'] = $userEmail;
  2229. }
  2230. $event = $this->massageTags($event, 'Event', $options['excludeGalaxy']);
  2231. // Let's find all the related events and attach it to the event itself
  2232. if ($options['includeEventCorrelations']) {
  2233. $results[$eventKey]['RelatedEvent'] = $this->getRelatedEvents($user, $event['Event']['id'], $sgids);
  2234. }
  2235. // Let's also find all the relations for the attributes - this won't be in the xml export though
  2236. if (!empty($options['includeGranularCorrelations'])) {
  2237. $results[$eventKey]['RelatedAttribute'] = $this->getRelatedAttributes($user, $event['Event']['id'], $sgids);
  2238. if (!empty($options['includeRelatedTags'])) {
  2239. $results[$eventKey] = $this->includeRelatedTags($results[$eventKey], $options);
  2240. }
  2241. $results[$eventKey]['RelatedShadowAttribute'] = $this->getRelatedAttributes($user, $event['Event']['id'], $sgids, true);
  2242. }
  2243. if (isset($event['ShadowAttribute']) && !empty($event['ShadowAttribute']) && isset($options['includeAttachments']) && $options['includeAttachments']) {
  2244. foreach ($event['ShadowAttribute'] as $k => $sa) {
  2245. if ($this->ShadowAttribute->typeIsAttachment($sa['type'])) {
  2246. $encodedFile = $this->ShadowAttribute->base64EncodeAttachment($sa);
  2247. $event['ShadowAttribute'][$k]['data'] = $encodedFile;
  2248. }
  2249. }
  2250. }
  2251. if (isset($event['Attribute'])) {
  2252. if ($options['includeFeedCorrelations']) {
  2253. if (!empty($options['overrideLimit'])) {
  2254. $overrideLimit = true;
  2255. } else {
  2256. $overrideLimit = false;
  2257. }
  2258. $event['Attribute'] = $this->Feed->attachFeedCorrelations($event['Attribute'], $user, $event['Event'], $overrideLimit);
  2259. }
  2260. if (!empty($options['includeServerCorrelations']) && ($user['Role']['perm_site_admin'] || $user['org_id'] == Configure::read('MISP.host_org_id'))) {
  2261. if (!empty($options['overrideLimit'])) {
  2262. $overrideLimit = true;
  2263. } else {
  2264. $overrideLimit = false;
  2265. }
  2266. $event['Attribute'] = $this->Feed->attachFeedCorrelations($event['Attribute'], $user, $event['Event'], $overrideLimit, 'Server');
  2267. }
  2268. $event = $this->__filterBlockedAttributesByTags($event, $options, $user);
  2269. $event['Attribute'] = $this->__attachSharingGroups(!$options['sgReferenceOnly'], $event['Attribute'], $sharingGroupData);
  2270. $proposalBlockAttributes = Configure::read('MISP.proposals_block_attributes');
  2271. // move all object attributes to a temporary container
  2272. $tempObjectAttributeContainer = array();
  2273. foreach ($event['Attribute'] as $key => $attribute) {
  2274. if ($options['enforceWarninglist'] && !empty($attribute['warnings'])) {
  2275. unset($event['Attribute'][$key]);
  2276. continue;
  2277. }
  2278. $event['Attribute'][$key] = $this->massageTags($event['Attribute'][$key], 'Attribute', $options['excludeGalaxy']);
  2279. if ($attribute['category'] === 'Financial fraud') {
  2280. $event['Attribute'][$key] = $this->Attribute->attachValidationWarnings($event['Attribute'][$key]);
  2281. }
  2282. if ($options['includeAttachments'] && $this->Attribute->typeIsAttachment($attribute['type'])) {
  2283. $encodedFile = $this->Attribute->base64EncodeAttachment($attribute);
  2284. $event['Attribute'][$key]['data'] = $encodedFile;
  2285. }
  2286. if (!empty($options['includeDecayScore'])) {
  2287. if (isset($event['EventTag'])) { // include EventTags for score computation
  2288. $event['Attribute'][$key]['EventTag'] = $event['EventTag'];
  2289. }
  2290. $event['Attribute'][$key] = $this->DecayingModel->attachScoresToAttribute($user, $event['Attribute'][$key]);
  2291. if (isset($event['EventTag'])) { // remove included EventTags
  2292. unset($event['Attribute'][$key]['EventTag']);
  2293. }
  2294. }
  2295. $event['Attribute'][$key]['ShadowAttribute'] = array();
  2296. // If a shadowattribute can be linked to an attribute, link it to it then remove it from the event
  2297. // This is to differentiate between proposals that were made to an attribute for modification and between proposals for new attributes
  2298. if (isset($event['ShadowAttribute'])) {
  2299. foreach ($event['ShadowAttribute'] as $k => $sa) {
  2300. if (!empty($sa['old_id'])) {
  2301. if ($event['ShadowAttribute'][$k]['old_id'] == $attribute['id']) {
  2302. $results[$eventKey]['Attribute'][$key]['ShadowAttribute'][] = $sa;
  2303. unset($results[$eventKey]['ShadowAttribute'][$k]);
  2304. }
  2305. }
  2306. }
  2307. }
  2308. if ($proposalBlockAttributes && !empty($options['allow_proposal_blocking'])) {
  2309. foreach ($results[$eventKey]['Attribute'][$key]['ShadowAttribute'] as $sa) {
  2310. if ($sa['proposal_to_delete'] || $sa['to_ids'] == 0) {
  2311. unset($results[$eventKey]['Attribute'][$key]);
  2312. continue;
  2313. }
  2314. }
  2315. }
  2316. if (!$flatten && $attribute['object_id'] != 0) {
  2317. $tempObjectAttributeContainer[$attribute['object_id']][] = $event['Attribute'][$key];
  2318. unset($event['Attribute'][$key]);
  2319. }
  2320. }
  2321. $event['Attribute'] = array_values($event['Attribute']);
  2322. }
  2323. if (!empty($event['Object'])) {
  2324. $event['Object'] = $this->__attachSharingGroups(!$options['sgReferenceOnly'], $event['Object'], $sharingGroupData);
  2325. foreach ($event['Object'] as $objectKey => $objectValue) {
  2326. if (isset($tempObjectAttributeContainer[$objectValue['id']])) {
  2327. $event['Object'][$objectKey]['Attribute'] = $tempObjectAttributeContainer[$objectValue['id']];
  2328. }
  2329. }
  2330. unset($tempObjectAttributeContainer);
  2331. }
  2332. if (!empty($event['EventReport'])) {
  2333. $event['EventReport'] = $this->__attachSharingGroups(!$options['sgReferenceOnly'], $event['EventReport'], $sharingGroupData);
  2334. }
  2335. if (!empty($event['ShadowAttribute'])) {
  2336. if ($isSiteAdmin && $options['includeFeedCorrelations']) {
  2337. if (!empty($options['overrideLimit'])) {
  2338. $overrideLimit = true;
  2339. } else {
  2340. $overrideLimit = false;
  2341. }
  2342. $event['ShadowAttribute'] = $this->Feed->attachFeedCorrelations($event['ShadowAttribute'], $user, $event['Event'], $overrideLimit);
  2343. }
  2344. if (!empty($options['includeServerCorrelations']) && $user['org_id'] == Configure::read('MISP.host_org_id')) {
  2345. if (!empty($options['overrideLimit'])) {
  2346. $overrideLimit = true;
  2347. } else {
  2348. $overrideLimit = false;
  2349. }
  2350. $event['ShadowAttribute'] = $this->Feed->attachFeedCorrelations($event['ShadowAttribute'], $user, $event['Event'], $overrideLimit, 'Server');
  2351. }
  2352. }
  2353. if (empty($options['metadata']) && empty($options['noSightings'])) {
  2354. $event['Sighting'] = $this->Sighting->attachToEvent($event, $user);
  2355. }
  2356. if ($options['includeSightingdb']) {
  2357. $this->Sightingdb = ClassRegistry::init('Sightingdb');
  2358. $event = $this->Sightingdb->attachToEvent($event, $user);
  2359. }
  2360. // remove proposals to attributes that we cannot see
  2361. // if the shadow attribute wasn't moved within an attribute before, this is the case
  2362. if (isset($event['ShadowAttribute'])) {
  2363. foreach ($event['ShadowAttribute'] as $k => $sa) {
  2364. if (!empty($sa['old_id'])) {
  2365. unset($event['ShadowAttribute'][$k]);
  2366. }
  2367. }
  2368. $event['ShadowAttribute'] = array_values($event['ShadowAttribute']);
  2369. }
  2370. }
  2371. if ($options['extended']) {
  2372. foreach ($results as $k => $result) {
  2373. $results[$k] = $this->__mergeExtensions($user, $result, $options);
  2374. }
  2375. }
  2376. return $results;
  2377. }
  2378. private function __cacheRelatedEventTags($eventTagCache, array $relatedAttribute, $excludeLocalTags)
  2379. {
  2380. if (!isset($eventTagCache[$relatedAttribute['id']])) {
  2381. $params = array(
  2382. 'contain' => array(
  2383. 'Tag' => array(
  2384. 'fields' => array(
  2385. 'Tag.id', 'Tag.name', 'Tag.colour', 'Tag.numerical_value'
  2386. )
  2387. )
  2388. ),
  2389. 'recursive' => -1,
  2390. 'conditions' => array(
  2391. 'EventTag.event_id' => $relatedAttribute['id']
  2392. )
  2393. );
  2394. if ($excludeLocalTags) {
  2395. $params['conditions']['EventTag.local'] = 0;
  2396. }
  2397. $eventTags = $this->EventTag->find('all', $params);
  2398. $eventTagCache[$relatedAttribute['id']] = [];
  2399. foreach ($eventTags as $et) {
  2400. if (!isset($eventTagCache[$relatedAttribute['id']][$et['Tag']['id']])) {
  2401. $eventTagCache[$relatedAttribute['id']][$et['Tag']['id']] = $et['Tag'];
  2402. }
  2403. }
  2404. }
  2405. return $eventTagCache;
  2406. }
  2407. private function includeRelatedTags(array $event, array $options)
  2408. {
  2409. $eventTagCache = array();
  2410. $excludeLocalTags = !empty($options['excludeLocalTags']);
  2411. foreach ($event['RelatedAttribute'] as $attributeId => $relatedAttributes) {
  2412. $tags = [];
  2413. foreach ($relatedAttributes as $relatedAttribute) {
  2414. $eventTagCache = $this->__cacheRelatedEventTags($eventTagCache, $relatedAttribute, $excludeLocalTags);
  2415. foreach ($eventTagCache[$relatedAttribute['id']] as $tagId => $tag) {
  2416. $tags[$tagId]= $tag;
  2417. }
  2418. $params = array(
  2419. 'contain' => array(
  2420. 'Tag' => array(
  2421. 'fields' => array(
  2422. 'Tag.id', 'Tag.name', 'Tag.colour', 'Tag.numerical_value'
  2423. )
  2424. )
  2425. ),
  2426. 'recursive' => -1,
  2427. 'conditions' => array(
  2428. 'AttributeTag.attribute_id' => $relatedAttribute['attribute_id']
  2429. )
  2430. );
  2431. if ($excludeLocalTags) {
  2432. $params['conditions']['AttributeTag.local'] = 0;
  2433. }
  2434. $attributeTags = $this->Attribute->AttributeTag->find('all', $params);
  2435. foreach ($attributeTags as $at) {
  2436. $tags[$at['Tag']['id']] = $at['Tag'];
  2437. }
  2438. }
  2439. if (!empty($tags)) {
  2440. $attributePos = false;
  2441. foreach ($event['Attribute'] as $k => $attribute) {
  2442. if ($attribute['id'] == $attributeId) {
  2443. $attributePos = $k;
  2444. break;
  2445. }
  2446. }
  2447. $event['Attribute'][$attributePos]['RelatedTags'] = array_values($tags);
  2448. }
  2449. }
  2450. return $event;
  2451. }
  2452. /**
  2453. * @param array $user
  2454. * @param array $event
  2455. * @param array $options
  2456. * @return array
  2457. * @throws Exception
  2458. */
  2459. private function __mergeExtensions(array $user, array $event, array $options)
  2460. {
  2461. $extensions = $this->fetchEvent($user, [
  2462. 'eventsExtendingUuid' => $event['Event']['uuid'],
  2463. 'includeEventCorrelations' => $options['includeEventCorrelations'],
  2464. 'includeWarninglistHits' => $options['includeWarninglistHits'],
  2465. 'noShadowAttributes' => $options['noShadowAttributes'],
  2466. 'noEventReports' => $options['noEventReports'],
  2467. 'noSightings' => isset($options['noSightings']) ? $options['noSightings'] : null,
  2468. 'sgReferenceOnly' => $options['sgReferenceOnly'],
  2469. ]);
  2470. foreach ($extensions as $extensionEvent) {
  2471. $eventMeta = array(
  2472. 'id' => $extensionEvent['Event']['id'],
  2473. 'info' => $extensionEvent['Event']['info'],
  2474. 'Orgc' => array(
  2475. 'id' => $extensionEvent['Orgc']['id'],
  2476. 'name' => $extensionEvent['Orgc']['name'],
  2477. 'uuid' => $extensionEvent['Orgc']['uuid'],
  2478. ),
  2479. );
  2480. $event['Event']['extensionEvents'][$eventMeta['id']] = $eventMeta;
  2481. $thingsToMerge = array('Attribute', 'Object', 'ShadowAttribute', 'Galaxy');
  2482. foreach ($thingsToMerge as $thingToMerge) {
  2483. $event[$thingToMerge] = array_merge($event[$thingToMerge], $extensionEvent[$thingToMerge]);
  2484. }
  2485. // Merge event reports if requested
  2486. if (!$options['noEventReports']) {
  2487. $event['EventReport'] = array_merge($event['EventReport'], $extensionEvent['EventReport']);
  2488. }
  2489. // Merge just tags that are not already in main event
  2490. foreach ($extensionEvent['EventTag'] as $eventTag) {
  2491. foreach ($event['EventTag'] as $eT) {
  2492. if ($eT['Tag']['id'] == $eventTag['Tag']['id']) {
  2493. continue 2; // tag already exists, skip
  2494. }
  2495. }
  2496. $event['EventTag'][] = $eventTag;
  2497. }
  2498. if ($options['includeEventCorrelations']) {
  2499. // Merge just related events that are not already in main event
  2500. foreach ($extensionEvent['RelatedEvent'] as $relatedEvent) {
  2501. foreach ($event['RelatedEvent'] as $rE) {
  2502. if ($rE['Event']['id'] == $relatedEvent['Event']['id']) {
  2503. continue 2; // event already exists, skip
  2504. }
  2505. }
  2506. $event['RelatedEvent'][] = $relatedEvent;
  2507. }
  2508. }
  2509. if ($options['includeWarninglistHits']) {
  2510. // Merge just event warninglist that are not already in main event
  2511. foreach ($extensionEvent['warnings'] as $warninglistId => $warning) {
  2512. if (!isset($event['warnings'][$warninglistId])) {
  2513. $event['warnings'][$warninglistId] = $warning;
  2514. }
  2515. }
  2516. }
  2517. }
  2518. return $event;
  2519. }
  2520. private function __attachSharingGroups($doAttach, $data, $sharingGroupData)
  2521. {
  2522. if (!$doAttach) {
  2523. return $data;
  2524. }
  2525. foreach ($data as $k => $v) {
  2526. if ($v['distribution'] == 4) {
  2527. if (isset($sharingGroupData[$v['sharing_group_id']])) {
  2528. $data[$k]['SharingGroup'] = $sharingGroupData[$v['sharing_group_id']]['SharingGroup'];
  2529. } else {
  2530. unset($data[$k]); // current user could not fetch the sharing_group
  2531. }
  2532. }
  2533. }
  2534. return $data;
  2535. }
  2536. // Filter the attributes within an event based on the tag filter block rules
  2537. private function __filterBlockedAttributesByTags($event, $options, $user)
  2538. {
  2539. if (!empty($options['blockedAttributeTags'])) {
  2540. foreach ($options['blockedAttributeTags'] as $key => $blockedTag) {
  2541. if (!is_numeric($blockedTag)) {
  2542. $options['blockedAttributeTags'][$key] = $this->EventTag->Tag->lookupTagIdFromName($blockedTag);
  2543. } else {
  2544. $options['blockedAttributeTags'][$key] = $blockedTag;
  2545. }
  2546. }
  2547. }
  2548. if (!empty($user['Server']['push_rules'])) {
  2549. $push_rules = json_decode($user['Server']['push_rules'], true);
  2550. if (!empty($push_rules['tags']['NOT'])) {
  2551. if (empty($options['blockedAttributeTags'])) {
  2552. $options['blockedAttributeTags'] = array();
  2553. }
  2554. $options['blockedAttributeTags'] = array_merge($options['blockedAttributeTags'], $push_rules['tags']['NOT']);
  2555. }
  2556. }
  2557. if (!empty($options['blockedAttributeTags'])) {
  2558. if (!empty($event['Attribute'])) {
  2559. $event['Attribute'] = $this->__filterBlockedAttributesFromContainer($event['Attribute'], $options['blockedAttributeTags']);
  2560. }
  2561. }
  2562. return $event;
  2563. }
  2564. // accepts an attribute array and a list of blocked tags. Returns the attribute array with the blocked attributes cleaned out.
  2565. private function __filterBlockedAttributesFromContainer($container, $blockedTags)
  2566. {
  2567. foreach ($container as $key => $attribute) {
  2568. if (!empty($attribute['AttributeTag'])) {
  2569. foreach ($attribute['AttributeTag'] as $at) {
  2570. if (in_array($at['tag_id'], $blockedTags)) {
  2571. unset($container[$key]);
  2572. }
  2573. }
  2574. }
  2575. }
  2576. $container = array_values($container);
  2577. return $container;
  2578. }
  2579. public function set_filter_org(&$params, $conditions, $options)
  2580. {
  2581. if (!empty($params['org'])) {
  2582. $params['org'] = $this->convert_filters($params['org']);
  2583. if (!empty($params['org']['OR'])) {
  2584. foreach ($params['org']['OR'] as $k => $org) {
  2585. if (!is_numeric($org)) {
  2586. $existingOrg = $this->Orgc->find('first', array(
  2587. 'recursive' => -1,
  2588. 'conditions' => array('Orgc.name' => $org),
  2589. 'fields' => array('Orgc.name', 'Orgc.id')
  2590. ));
  2591. if (empty($existingOrg)) {
  2592. $params['org']['OR'][$k] = -1;
  2593. } else {
  2594. $params['org']['OR'][$k] = $existingOrg['Orgc']['id'];
  2595. }
  2596. }
  2597. }
  2598. }
  2599. if (!empty($params['org']['NOT'])) {
  2600. $temp = array();
  2601. foreach ($params['org']['NOT'] as $org) {
  2602. if (!is_numeric($org)) {
  2603. $existingOrg = $this->Orgc->find('first', array(
  2604. 'recursive' => -1,
  2605. 'conditions' => array('Orgc.name' => $org),
  2606. 'fields' => array('Orgc.name', 'Orgc.id')
  2607. ));
  2608. if (!empty($existingOrg)) {
  2609. $temp[] = $existingOrg['Orgc']['id'];
  2610. }
  2611. } else {
  2612. $temp[] = $org;
  2613. }
  2614. }
  2615. if (!empty($temp)) {
  2616. $params['org']['NOT'] = $temp;
  2617. } else {
  2618. unset($params['org']['NOT']);
  2619. }
  2620. }
  2621. $conditions = $this->generic_add_filter($conditions, $params['org'], 'Event.orgc_id');
  2622. }
  2623. return $conditions;
  2624. }
  2625. public function set_filter_eventid(&$params, $conditions, $options)
  2626. {
  2627. if (!empty($params['eventid']) && $params['eventid'] !== 'all') {
  2628. $params['eventid'] = $this->convert_filters($params['eventid']);
  2629. $keys = array(
  2630. 'uuid' => 'Event.uuid',
  2631. 'id' => 'Event.id'
  2632. );
  2633. $id_params = array();
  2634. foreach ($params['eventid'] as $operand => $list) {
  2635. foreach ($list as $id) {
  2636. if ($operand === 'OR') {
  2637. $id_params['AND']['OR'][$keys[Validation::uuid($id) ? 'uuid' : 'id']][] = $id;
  2638. } else if ($operand === 'AND') {
  2639. $id_params['AND']['AND'][$keys[Validation::uuid($id) ? 'uuid' : 'id']][] = $id;
  2640. } else {
  2641. $id_params['AND']['NOT'][$keys[Validation::uuid($id) ? 'uuid' : 'id']][] = $id;
  2642. }
  2643. }
  2644. }
  2645. $conditions['AND'][] = $id_params;
  2646. }
  2647. return $conditions;
  2648. }
  2649. public function set_filter_eventinfo(&$params, $conditions, $options)
  2650. {
  2651. if (!empty($params['eventinfo'])) {
  2652. $params['eventinfo'] = $this->convert_filters($params['eventinfo']);
  2653. $conditions = $this->generic_add_filter($conditions, $params['eventinfo'], 'Event.info');
  2654. }
  2655. return $conditions;
  2656. }
  2657. public function set_filter_uuid(&$params, $conditions, $options)
  2658. {
  2659. if ($options['scope'] === 'Event') {
  2660. if (!empty($params['uuid'])) {
  2661. $params['uuid'] = $this->convert_filters($params['uuid']);
  2662. if (!empty($params['uuid']['OR'])) {
  2663. $subQueryOptions = array(
  2664. 'conditions' => array('Attribute.uuid' => $params['uuid']['OR']),
  2665. 'fields' => array('event_id')
  2666. );
  2667. $attributeSubquery = $this->subQueryGenerator($this->Attribute, $subQueryOptions, 'Event.id');
  2668. $conditions['AND'][] = array(
  2669. 'OR' => array(
  2670. 'Event.uuid' => $params['uuid']['OR'],
  2671. $attributeSubquery
  2672. )
  2673. );
  2674. }
  2675. if (!empty($params['uuid']['NOT'])) {
  2676. $subQueryOptions = array(
  2677. 'conditions' => array('Attribute.uuid' => $params['uuid']['NOT']),
  2678. 'fields' => array('event_id')
  2679. );
  2680. $attributeSubquery = $this->subQueryGenerator($this->Attribute, $subQueryOptions, 'Event.id');
  2681. $conditions['AND'][] = array(
  2682. 'NOT' => array(
  2683. 'Event.uuid' => $params['uuid']['NOT'],
  2684. $attributeSubquery
  2685. )
  2686. );
  2687. }
  2688. }
  2689. } else {
  2690. $conditions = $this->{$options['scope']}->set_filter_uuid($params, $conditions, $options);
  2691. }
  2692. return $conditions;
  2693. }
  2694. public function set_filter_mixed_id(&$params, $conditions, $options)
  2695. {
  2696. if (!empty($params['mixed_id'])) {
  2697. $params['mixed_id'] = $this->convert_filters($params['mixed_id']);
  2698. if (!empty($options['scope']) && $options['scope'] === 'Event') {
  2699. $conditions = $this->generic_add_filter($conditions, $params['uuid'], 'Event.uuid');
  2700. }
  2701. if (!empty($options['scope']) && $options['scope'] === 'Attribute') {
  2702. $conditions = $this->generic_add_filter($conditions, $params['uuid'], 'Attribute.uuid');
  2703. }
  2704. }
  2705. return $conditions;
  2706. }
  2707. public function set_filter_deleted(&$params, $conditions, $options)
  2708. {
  2709. if (!empty($params['deleted'])) {
  2710. if (empty($options['scope'])) {
  2711. $scope = 'Attribute';
  2712. } else {
  2713. $scope = $options['scope'];
  2714. }
  2715. if ($params['deleted']) {
  2716. $conditions = $this->generic_add_filter($conditions, $params['deleted'], $scope . '.deleted');
  2717. }
  2718. }
  2719. return $conditions;
  2720. }
  2721. public function set_filter_to_ids(&$params, $conditions, $options)
  2722. {
  2723. if (isset($params['to_ids'])) {
  2724. if ($params['to_ids'] === 'exclude') {
  2725. $params['to_ids'] = 0;
  2726. }
  2727. $conditions['AND']['Attribute.to_ids'] = $params['to_ids'];
  2728. }
  2729. return $conditions;
  2730. }
  2731. public function set_filter_ignore(&$params, $conditions, $options)
  2732. {
  2733. if (empty($params['ignore'])) {
  2734. if (empty($options['scope'])) {
  2735. $scope = 'Attribute';
  2736. } else {
  2737. $scope = $options['scope'];
  2738. }
  2739. if ($scope === 'Attribute') {
  2740. $conditions['AND']['Attribute.to_ids'] = 1;
  2741. } else {
  2742. $conditions['AND']['Event.published'] = 1;
  2743. }
  2744. }
  2745. return $conditions;
  2746. }
  2747. public function set_filter_published(&$params, $conditions, $options)
  2748. {
  2749. if (isset($params['published'])) {
  2750. $conditions['AND']['Event.published'] = $params['published'];
  2751. }
  2752. return $conditions;
  2753. }
  2754. public function set_filter_threat_level_id(&$params, $conditions, $options)
  2755. {
  2756. if (isset($params['threat_level_id'])) {
  2757. $conditions['AND']['Event.threat_level_id'] = $params['threat_level_id'];
  2758. }
  2759. return $conditions;
  2760. }
  2761. public function set_filter_tags(&$params, $conditions, $options)
  2762. {
  2763. if (!empty($params['tags'])) {
  2764. $conditions = $this->Attribute->set_filter_tags($params, $conditions, $options);
  2765. }
  2766. return $conditions;
  2767. }
  2768. public function set_filter_type(&$params, $conditions, $options)
  2769. {
  2770. if (!empty($params[$options['filter']])) {
  2771. $params[$options['filter']] = $this->convert_filters($params[$options['filter']]);
  2772. if (!empty(Configure::read('MISP.attribute_filters_block_only'))) {
  2773. if ($options['context'] === 'Event' && !empty($params[$options['filter']]['OR'])) {
  2774. unset($params[$options['filter']]['OR']);
  2775. }
  2776. }
  2777. if (!empty($params[$options['filter']])) {
  2778. foreach (['OR', 'NOT'] as $operator) {
  2779. if (
  2780. !empty($params[$options['filter']][$operator]) &&
  2781. (
  2782. in_array('email-src', $params[$options['filter']][$operator]) ||
  2783. in_array('email-dst', $params[$options['filter']][$operator])
  2784. ) && (
  2785. !in_array('email', $params[$options['filter']][$operator])
  2786. )
  2787. ) {
  2788. $params[$options['filter']][$operator][] = 'email';
  2789. }
  2790. }
  2791. }
  2792. $conditions = $this->generic_add_filter($conditions, $params[$options['filter']], 'Attribute.' . $options['filter']);
  2793. }
  2794. return $conditions;
  2795. }
  2796. public function set_filter_simple_attribute(&$params, $conditions, $options)
  2797. {
  2798. if (!empty($params[$options['filter']])) {
  2799. $params[$options['filter']] = $this->convert_filters($params[$options['filter']]);
  2800. if (!empty(Configure::read('MISP.attribute_filters_block_only'))) {
  2801. if ($options['context'] === 'Event' && !empty($params[$options['filter']]['OR'])) {
  2802. unset($params[$options['filter']]['OR']);
  2803. }
  2804. }
  2805. $conditions = $this->generic_add_filter($conditions, $params[$options['filter']], 'Attribute.' . $options['filter']);
  2806. }
  2807. return $conditions;
  2808. }
  2809. public function set_filter_attribute_id(&$params, $conditions, $options)
  2810. {
  2811. if (!empty($params[$options['filter']])) {
  2812. $params[$options['filter']] = $this->convert_filters($params[$options['filter']]);
  2813. $conditions = $this->generic_add_filter($conditions, $params[$options['filter']], 'Attribute.' . $options['filter']);
  2814. }
  2815. return $conditions;
  2816. }
  2817. public function set_filter_value(&$params, $conditions, $options, $keys = array('Attribute.value1', 'Attribute.value2'))
  2818. {
  2819. if (!empty($params['value'])) {
  2820. $params[$options['filter']] = $this->convert_filters($params[$options['filter']]);
  2821. $conditions = $this->generic_add_filter($conditions, $params[$options['filter']], $keys);
  2822. }
  2823. return $conditions;
  2824. }
  2825. public function set_filter_object_name(&$params, $conditions, $options)
  2826. {
  2827. if (!empty($params['object_name'])) {
  2828. $params['object_name'] = $this->convert_filters($params['object_name']);
  2829. $conditions = $this->generic_add_filter($conditions, $params['object_name'], 'Object.name');
  2830. }
  2831. return $conditions;
  2832. }
  2833. public function set_filter_object_template_uuid(&$params, $conditions, $options)
  2834. {
  2835. if (!empty($params['object_template_uuid'])) {
  2836. $params['object_template_uuid'] = $this->convert_filters($params['object_template_uuid']);
  2837. $conditions = $this->generic_add_filter($conditions, $params['object_template_uuid'], 'Object.template_uuid');
  2838. }
  2839. return $conditions;
  2840. }
  2841. public function set_filter_object_template_version(&$params, $conditions, $options)
  2842. {
  2843. if (!empty($params['object_template_version'])) {
  2844. $params['object_template_version'] = $this->convert_filters($params['object_template_version']);
  2845. $conditions = $this->generic_add_filter($conditions, $params['object_template_version'], 'Object.template_version');
  2846. }
  2847. return $conditions;
  2848. }
  2849. public function set_filter_comment(&$params, $conditions, $options)
  2850. {
  2851. if (!empty($params['comment'])) {
  2852. $params['comment'] = $this->convert_filters($params['comment']);
  2853. $conditions = $this->generic_add_filter($conditions, $params['comment'], 'Attribute.comment');
  2854. }
  2855. return $conditions;
  2856. }
  2857. public function set_filter_seen(&$params, $conditions, $options)
  2858. {
  2859. $f = $options['scope'] . '.' . $options['filter'];
  2860. $conditions = $this->Attribute->setTimestampSeenConditions($params[$options['filter']], $conditions, $f);
  2861. return $conditions;
  2862. }
  2863. public function set_filter_timestamp(&$params, $conditions, $options)
  2864. {
  2865. if ($options['filter'] == 'from') {
  2866. if (is_numeric($params['from'])) {
  2867. $conditions['AND']['Event.date >='] = date('Y-m-d', $params['from']);
  2868. } else {
  2869. $conditions['AND']['Event.date >='] = $params['from'];
  2870. }
  2871. } elseif ($options['filter'] == 'to') {
  2872. if (is_numeric($params['to'])) {
  2873. $conditions['AND']['Event.date <='] = date('Y-m-d', $params['to']);
  2874. } else {
  2875. $conditions['AND']['Event.date <='] = $params['to'];
  2876. }
  2877. } else {
  2878. if (empty($options['scope'])) {
  2879. $scope = 'Attribute';
  2880. } else {
  2881. $scope = $options['scope'];
  2882. }
  2883. $filters = array(
  2884. 'timestamp' => array(
  2885. $scope . '.timestamp'
  2886. ),
  2887. 'publish_timestamp' => array(
  2888. 'Event.publish_timestamp'
  2889. ),
  2890. 'last' => array(
  2891. 'Event.publish_timestamp'
  2892. ),
  2893. 'event_timestamp' => array(
  2894. 'Event.timestamp'
  2895. ),
  2896. 'attribute_timestamp' => array(
  2897. 'Attribute.timestamp'
  2898. ),
  2899. );
  2900. foreach ($filters[$options['filter']] as $f) {
  2901. $conditions = $this->Attribute->setTimestampConditions($params[$options['filter']], $conditions, $f);
  2902. }
  2903. }
  2904. return $conditions;
  2905. }
  2906. public function set_filter_date(&$params, $conditions, $options)
  2907. {
  2908. $timestamp = $this->Attribute->setTimestampConditions($params[$options['filter']], $conditions, 'Event.date', true);
  2909. if (!is_array($timestamp)) {
  2910. $conditions['AND']['Event.date >='] = date('Y-m-d', $timestamp);
  2911. } else {
  2912. $conditions['AND']['Event.date >='] = date('Y-m-d', $timestamp[0]);
  2913. $conditions['AND']['Event.date <='] = date('Y-m-d', $timestamp[1]);
  2914. }
  2915. return $conditions;
  2916. }
  2917. public function sendAlertEmailRouter($id, $user, $oldpublish = null)
  2918. {
  2919. if (Configure::read('MISP.block_old_event_alert')) {
  2920. $oldest = time() - (Configure::read('MISP.block_old_event_alert_age') * 86400);
  2921. $oldest_date = time() - (Configure::read('MISP.block_old_event_alert_by_date') * 86400);
  2922. $event = $this->find('first', array(
  2923. 'conditions' => array('Event.id' => $id),
  2924. 'recursive' => -1,
  2925. 'fields' => array('Event.timestamp', 'Event.date')
  2926. ));
  2927. if (empty($event)) {
  2928. return false;
  2929. }
  2930. if (!empty(Configure::read('MISP.block_old_event_alert_age')) && is_numeric(Configure::read('MISP.block_old_event_alert_age'))) {
  2931. if (intval($event['Event']['timestamp']) < $oldest) {
  2932. return true;
  2933. }
  2934. }
  2935. if (!empty(Configure::read('MISP.block_old_event_alert_by_date')) && is_numeric(Configure::read('MISP.block_old_event_alert_by_date'))) {
  2936. if (strtotime($event['Event']['date']) < $oldest_date) {
  2937. return true;
  2938. }
  2939. }
  2940. }
  2941. if (Configure::read('MISP.block_event_alert') && Configure::read('MISP.block_event_alert_tag') && !empty(Configure::read('MISP.block_event_alert_tag'))) {
  2942. $noAlertTag = Configure::read('MISP.block_event_alert_tag');
  2943. $tagLen = strlen($noAlertTag);
  2944. $event = $this->fetchEvent($user, array('eventid' => $id, 'includeAllTags' => true));
  2945. if (empty($event)) {
  2946. return false;
  2947. }
  2948. foreach ($event[0]['EventTag'] as $k => $tag) {
  2949. if (strcasecmp($noAlertTag, $tag['Tag']['name']) == 0) {
  2950. return true;
  2951. }
  2952. }
  2953. }
  2954. if (Configure::read('MISP.disable_emailing')) {
  2955. $this->Log = ClassRegistry::init('Log');
  2956. $this->Log->create();
  2957. $this->Log->save(array(
  2958. 'org' => 'SYSTEM',
  2959. 'model' => 'Event',
  2960. 'model_id' => $id,
  2961. 'email' => $user['email'],
  2962. 'action' => 'publish',
  2963. 'title' => 'E-mail alerts not sent out during publishing. Reason: Emailing is currently disabled on this instance.',
  2964. 'change' => null,
  2965. ));
  2966. return true;
  2967. }
  2968. if (Configure::read('MISP.background_jobs')) {
  2969. $job = ClassRegistry::init('Job');
  2970. $job->create();
  2971. $data = array(
  2972. 'worker' => 'email',
  2973. 'job_type' => 'publish_alert_email',
  2974. 'job_input' => 'Event: ' . $id,
  2975. 'status' => 0,
  2976. 'retries' => 0,
  2977. 'org_id' => $user['org_id'],
  2978. 'org' => $user['Organisation']['name'],
  2979. 'message' => 'Sending...',
  2980. );
  2981. $job->save($data);
  2982. $jobId = $job->id;
  2983. $process_id = CakeResque::enqueue(
  2984. 'email',
  2985. 'EventShell',
  2986. array('alertemail', $user['id'], $jobId, $id, $oldpublish),
  2987. true
  2988. );
  2989. $job->saveField('process_id', $process_id);
  2990. return true;
  2991. } else {
  2992. return $this->sendAlertEmail($id, $user, $oldpublish);
  2993. }
  2994. }
  2995. /**
  2996. * @param int $id
  2997. * @param array $senderUser Not used anymore.
  2998. * @param int|null $oldpublish Timestamp of old publishing.
  2999. * @param int|null $jobId
  3000. * @return bool
  3001. * @throws Exception
  3002. */
  3003. public function sendAlertEmail($id, array $senderUser, $oldpublish = null, $jobId = null)
  3004. {
  3005. $event = $this->find('first', [
  3006. 'conditions' => ['Event.id' => $id],
  3007. 'contain' => ['EventTag' => ['Tag'], 'ThreatLevel'],
  3008. ]);
  3009. if (empty($event)) {
  3010. throw new NotFoundException('Invalid Event.');
  3011. }
  3012. // Initialise the Job class if we have a background process ID
  3013. // This will keep updating the process's progress bar
  3014. if ($jobId) {
  3015. $this->Job = ClassRegistry::init('Job');
  3016. }
  3017. $this->NotificationLog = ClassRegistry::init('NotificationLog');
  3018. if (!$this->NotificationLog->check($event['Event']['orgc_id'], 'publish')) {
  3019. if ($jobId) {
  3020. $this->Job->saveStatus($jobId, true, __('Mails blocked by org alert threshold.'));
  3021. }
  3022. return true;
  3023. }
  3024. $userConditions = array('autoalert' => 1);
  3025. $usersWithAccess = $this->User->getUsersWithAccess(
  3026. $owners = array(
  3027. $event['Event']['orgc_id'],
  3028. $event['Event']['org_id']
  3029. ),
  3030. $event['Event']['distribution'],
  3031. $event['Event']['sharing_group_id'],
  3032. $userConditions
  3033. );
  3034. if (Configure::read('MISP.extended_alert_subject')) {
  3035. $subject = preg_replace("/\r|\n/", "", $event['Event']['info']);
  3036. if (strlen($subject) > 58) {
  3037. $subject = substr($subject, 0, 55) . '... - ';
  3038. } else {
  3039. $subject .= " - ";
  3040. }
  3041. } else {
  3042. $subject = '';
  3043. }
  3044. $subjMarkingString = $this->getEmailSubjectMarkForEvent($event);
  3045. if (Configure::read('MISP.threatlevel_in_email_subject') === false) {
  3046. $threatLevel = '';
  3047. } else {
  3048. $threatLevel = $event['ThreatLevel']['name'] . " - ";
  3049. }
  3050. $subject = "[" . Configure::read('MISP.org') . " MISP] Event $id - $subject$threatLevel" . strtoupper($subjMarkingString);
  3051. $eventUrl = $this->__getAnnounceBaseurl() . "/events/view/" . $id;
  3052. $bodyNoEnc = __("A new or modified event was just published on %s", $eventUrl) . "\n\n";
  3053. $bodyNoEnc .= __("If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via %s", $this->__getAnnounceBaseurl() . '/users/edit');
  3054. $userCount = count($usersWithAccess);
  3055. $this->UserSetting = ClassRegistry::init('UserSetting');
  3056. foreach ($usersWithAccess as $k => $user) {
  3057. if ($this->UserSetting->checkPublishFilter($user, $event)) {
  3058. // Fetch event for user that will receive alert e-mail to respect all ACLs
  3059. $eventForUser = $this->fetchEvent($user, [
  3060. 'eventid' => $id,
  3061. 'includeAllTags' => true,
  3062. 'includeEventCorrelations' => true,
  3063. ])[0];
  3064. $body = $this->__buildAlertEmailBody($eventForUser, $user, $oldpublish);
  3065. $this->User->sendEmail(array('User' => $user), $body, $bodyNoEnc, $subject);
  3066. }
  3067. if ($jobId) {
  3068. $this->Job->saveProgress($jobId, null, $k / $userCount * 100);
  3069. }
  3070. }
  3071. if ($jobId) {
  3072. $this->Job->saveStatus($jobId, true, __('Mails sent.'));
  3073. }
  3074. return true;
  3075. }
  3076. /**
  3077. * @param string $body
  3078. * @param string $bodyTempOther
  3079. * @param array $objects
  3080. * @param int|null $oldpublish Timestamp of latest publish
  3081. */
  3082. private function __buildAlertEmailObject(&$body, &$bodyTempOther, array $objects, $oldpublish)
  3083. {
  3084. foreach ($objects as $object) {
  3085. if (isset($oldpublish) && isset($object['timestamp']) && $object['timestamp'] > $oldpublish) {
  3086. $body .= '* ';
  3087. } else {
  3088. $body .= ' ';
  3089. }
  3090. $body .= $object['name'] . '/' . $object['meta-category'] . "\n";
  3091. if (!empty($object['Attribute'])) {
  3092. $body .= $this->__buildAlertEmailAttribute($body, $bodyTempOther, $object['Attribute'], $oldpublish, ' ');
  3093. }
  3094. }
  3095. }
  3096. /**
  3097. * @param string $body
  3098. * @param string $bodyTempOther
  3099. * @param array $attributes
  3100. * @param int|null $oldpublish Timestamp of latest publish
  3101. * @param string $indent
  3102. */
  3103. private function __buildAlertEmailAttribute(&$body, &$bodyTempOther, array $attributes, $oldpublish, $indent = ' ')
  3104. {
  3105. $appendlen = 20;
  3106. foreach ($attributes as $attribute) {
  3107. $ids = $attribute['to_ids'] ? ' (IDS)' : '';
  3108. // Defanging URLs (Not "links") emails domains/ips in notification emails
  3109. $value = $attribute['value'];
  3110. if ('url' == $attribute['type'] || 'uri' == $attribute['type']) {
  3111. $value = str_ireplace("http", "hxxp", $value);
  3112. $value = str_ireplace(".", "[.]", $value);
  3113. } elseif (in_array($attribute['type'], array('email-src', 'email-dst', 'whois-registrant-email', 'dns-soa-email', 'email-reply-to'))) {
  3114. $value = str_replace("@", "[at]", $value);
  3115. } elseif (in_array($attribute['type'], array('hostname', 'domain', 'ip-src', 'ip-dst', 'domain|ip'))) {
  3116. $value = str_replace(".", "[.]", $value);
  3117. }
  3118. $strRepeatCount = $appendlen - 2 - strlen($attribute['type']);
  3119. $strRepeat = ($strRepeatCount > 0) ? str_repeat(' ', $strRepeatCount) : '';
  3120. if (isset($oldpublish) && isset($attribute['timestamp']) && $attribute['timestamp'] > $oldpublish) {
  3121. $line = '* ' . $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . " *\n";
  3122. } else {
  3123. $line = $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . "\n";
  3124. }
  3125. if (!empty($attribute['AttributeTag'])) {
  3126. $tags = [];
  3127. foreach ($attribute['AttributeTag'] as $aT) {
  3128. $tags[] = $aT['Tag']['name'];
  3129. }
  3130. $line .= ' - Tags: ' . implode(', ', $tags) . "\n";
  3131. }
  3132. if ('other' == $attribute['type']) { // append the 'other' attribute types to the bottom.
  3133. $bodyTempOther .= $line;
  3134. } else {
  3135. $body .= $line;
  3136. }
  3137. }
  3138. }
  3139. /**
  3140. * @param array $event
  3141. * @param array $user
  3142. * @param int|null $oldpublish Timestamp of latest publish
  3143. * @return string
  3144. */
  3145. private function __buildAlertEmailBody(array $event, array $user, $oldpublish)
  3146. {
  3147. // The mail body, h() is NOT needed as we are sending plain-text mails.
  3148. $body = "";
  3149. $body .= '==============================================' . "\n";
  3150. $body .= 'URL : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $event['Event']['id'] . "\n";
  3151. $body .= 'Event ID : ' . $event['Event']['id'] . "\n";
  3152. $body .= 'Date : ' . $event['Event']['date'] . "\n";
  3153. if (Configure::read('MISP.showorg')) {
  3154. $body .= 'Reported by : ' . $event['Orgc']['name'] . "\n";
  3155. $body .= 'Local owner of the event : ' . $event['Org']['name'] . "\n";
  3156. }
  3157. $body .= 'Distribution: ' . $this->distributionLevels[$event['Event']['distribution']] . "\n";
  3158. if ($event['Event']['distribution'] == 4) {
  3159. $body .= 'Sharing Group: ' . $event['SharingGroup']['name'] . "\n";
  3160. }
  3161. $tags = [];
  3162. foreach ($event['EventTag'] as $tag) {
  3163. $tags[] = $tag['Tag']['name'];
  3164. }
  3165. $body .= 'Tags: ' . implode(', ', $tags) . "\n";
  3166. $body .= 'Threat Level: ' . $event['ThreatLevel']['name'] . "\n";
  3167. $body .= 'Analysis : ' . $this->analysisLevels[$event['Event']['analysis']] . "\n";
  3168. $body .= 'Description : ' . $event['Event']['info'] . "\n";
  3169. if (!empty($event['RelatedEvent'])) {
  3170. $body .= '==============================================' . "\n";
  3171. $body .= 'Related to: '. "\n";
  3172. foreach ($event['RelatedEvent'] as $relatedEvent) {
  3173. $body .= $this->__getAnnounceBaseurl() . '/events/view/' . $relatedEvent['Event']['id'] . ' (' . $relatedEvent['Event']['date'] . ') ' ."\n";
  3174. }
  3175. $body .= '==============================================' . "\n";
  3176. }
  3177. $bodyTempOther = "";
  3178. if (!empty($event['Attribute'])) {
  3179. $body .= 'Attributes (* indicates a new or modified attribute):' . "\n";
  3180. $this->__buildAlertEmailAttribute($body, $bodyTempOther, $event['Attribute'], $oldpublish);
  3181. }
  3182. if (!empty($event['Object'])) {
  3183. $body .= 'Objects (* indicates a new or modified object):' . "\n";
  3184. $this->__buildAlertEmailObject($body, $bodyTempOther, $event['Object'], $oldpublish);
  3185. }
  3186. if (!empty($bodyTempOther)) {
  3187. $body .= "\n";
  3188. }
  3189. $body .= $bodyTempOther; // append the 'other' attribute types to the bottom.
  3190. $body .= '==============================================' . "\n";
  3191. $body .= sprintf(
  3192. "You receive this e-mail because the e-mail address %s is set to receive publish alerts on the MISP instance at %s.%s%s",
  3193. $user['email'],
  3194. $this->__getAnnounceBaseurl(),
  3195. PHP_EOL,
  3196. PHP_EOL
  3197. );
  3198. $body .= "If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via " . $this->__getAnnounceBaseurl() . '/users/edit' . PHP_EOL;
  3199. $body .= '==============================================' . "\n";
  3200. return $body;
  3201. }
  3202. /**
  3203. * @param int $id Event ID
  3204. * @param string $message Message that user want to send to event creator
  3205. * @param bool $creator_only Should be contacted just event creator or all user from org owning given event?
  3206. * @param array $user User that wanna know more
  3207. * @return bool True if all e-mails was send correctly.
  3208. * @throws Exception
  3209. */
  3210. public function sendContactEmail($id, $message, $creator_only, array $user)
  3211. {
  3212. // fetch the event as user that requested more information. So if creators will reply to that email, no data
  3213. // that requestor could not access would be leaked.
  3214. $event = $this->fetchEvent($this->User->rearrangeToAuthForm($user), [
  3215. 'eventid' => $id,
  3216. 'includeAllTags' => true,
  3217. 'includeEventCorrelations' => true,
  3218. ]);
  3219. if (empty($event)) {
  3220. throw new NotFoundException('Invalid Event.');
  3221. }
  3222. $event = $event[0];
  3223. if (!$creator_only) {
  3224. // Insert extra field here: alertOrg or something, then foreach all the org members
  3225. // limit this array to users with contactalerts turned on!
  3226. $orgMembers = array();
  3227. $this->User->recursive = 0;
  3228. $temp = $this->User->find('all', array(
  3229. 'fields' => array('email', 'gpgkey', 'certif_public', 'contactalert', 'id', 'org_id', 'disabled'),
  3230. 'conditions' => array('disabled' => 0, 'User.org_id' => $event['Event']['orgc_id']),
  3231. 'recursive' => -1
  3232. ));
  3233. foreach ($temp as $tempElement) {
  3234. if ($tempElement['User']['contactalert'] || $tempElement['User']['id'] == $event['Event']['user_id']) {
  3235. array_push($orgMembers, $tempElement);
  3236. }
  3237. }
  3238. } else {
  3239. $temp = $this->User->find('first', array(
  3240. 'conditions' => array(
  3241. 'User.id' => $event['Event']['user_id'],
  3242. 'User.disabled' => 0,
  3243. 'User.org_id' => $event['Event']['orgc_id'],
  3244. ),
  3245. 'fields' => array('User.email', 'User.gpgkey', 'User.certif_public', 'User.id', 'User.disabled'),
  3246. 'recursive' => -1
  3247. ));
  3248. if (!empty($temp)) {
  3249. $orgMembers = array($temp);
  3250. }
  3251. }
  3252. if (empty($orgMembers)) {
  3253. return false;
  3254. }
  3255. $tplColorString = $this->getEmailSubjectMarkForEvent($event);
  3256. $subject = "[" . Configure::read('MISP.org') . " MISP] Need info about event $id - " . strtoupper($tplColorString);
  3257. $result = true;
  3258. foreach ($orgMembers as $reporter) {
  3259. list($bodyevent, $body) = $this->__buildContactEventEmailBody($user, $message, $event);
  3260. $result = $this->User->sendEmail($reporter, $bodyevent, $body, $subject, $user) && $result;
  3261. }
  3262. return $result;
  3263. }
  3264. private function __buildContactEventEmailBody(array $user, $message, array $event)
  3265. {
  3266. // The mail body, h() is NOT needed as we are sending plain-text mails.
  3267. $body = "";
  3268. $body .= "Hello, \n";
  3269. $body .= "\n";
  3270. $body .= "Someone wants to get in touch with you concerning a MISP event. \n";
  3271. $body .= "\n";
  3272. $body .= "You can reach them at " . $user['User']['email'] . "\n";
  3273. if (!empty($user['User']['gpgkey'])) {
  3274. $body .= "Their GnuPG key is added as attachment to this email. \n";
  3275. }
  3276. if (!empty($user['User']['certif_public'])) {
  3277. $body .= "Their Public certificate is added as attachment to this email. \n";
  3278. }
  3279. $body .= "\n";
  3280. $body .= "They wrote the following message: \n";
  3281. $body .= $message . "\n";
  3282. $body .= "\n";
  3283. $body .= "\n";
  3284. $body .= "The event is the following: \n";
  3285. // print the event in mail-format
  3286. // LATER place event-to-email-layout in a function
  3287. $body .= 'URL : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $event['Event']['id'] . "\n";
  3288. $bodyevent = $body;
  3289. $bodyevent .= 'Event ID : ' . $event['Event']['id'] . "\n";
  3290. $bodyevent .= 'Date : ' . $event['Event']['date'] . "\n";
  3291. if (Configure::read('MISP.showorg')) {
  3292. $bodyevent .= 'Reported by : ' . $event['Orgc']['name'] . "\n";
  3293. }
  3294. $bodyevent .= 'Risk : ' . $event['ThreatLevel']['name'] . "\n";
  3295. $bodyevent .= 'Analysis : ' . $this->analysisLevels[$event['Event']['analysis']] . "\n";
  3296. foreach ($event['RelatedEvent'] as $relatedEvent) {
  3297. $bodyevent .= 'Related to : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $relatedEvent['Event']['id'] . ' (' . $relatedEvent['Event']['date'] . ')' . "\n";
  3298. }
  3299. $bodyevent .= 'Info : ' . "\n";
  3300. $bodyevent .= $event['Event']['info'] . "\n";
  3301. $bodyTempOther = "";
  3302. if (!empty($event['Attribute'])) {
  3303. $bodyevent .= 'Attributes:' . "\n";
  3304. $this->__buildAlertEmailAttribute($bodyevent, $bodyTempOther, $event['Attribute'], null);
  3305. }
  3306. if (!empty($event['Object'])) {
  3307. $bodyevent .= 'Objects:' . "\n";
  3308. $this->__buildAlertEmailObject($bodyevent, $bodyTempOther, $event['Object'], null);
  3309. }
  3310. if (!empty($bodyTempOther)) {
  3311. $bodyevent .= "\n";
  3312. }
  3313. $bodyevent .= $bodyTempOther; // append the 'other' attribute types to the bottom.
  3314. return array($bodyevent, $body);
  3315. }
  3316. public function __captureSGForElement($element, $user, $syncLocal=false)
  3317. {
  3318. if (isset($element['SharingGroup'])) {
  3319. $sg = $this->SharingGroup->captureSG($element['SharingGroup'], $user, $syncLocal);
  3320. unset($element['SharingGroup']);
  3321. } elseif (isset($element['sharing_group_id'])) {
  3322. $sg = $this->SharingGroup->checkIfAuthorised($user, $element['sharing_group_id']) ? $element['sharing_group_id'] : false;
  3323. } else {
  3324. $sg = false;
  3325. }
  3326. if ($sg===false) {
  3327. $sg = 0;
  3328. $element['distribution'] = 0;
  3329. }
  3330. $element['sharing_group_id'] = $sg;
  3331. return $element;
  3332. }
  3333. // When we receive an event via REST, we might end up with organisations, sharing groups, tags that we do not know
  3334. // or which we need to update. All of that is controlled in this method.
  3335. private function __captureObjects($data, $user, $syncLocal=false)
  3336. {
  3337. // First we need to check whether the event or any attributes are tied to a sharing group and whether the user is even allowed to create the sharing group / is part of it
  3338. if (isset($data['Event']['distribution']) && $data['Event']['distribution'] == 4) {
  3339. $data['Event'] = $this->__captureSGForElement($data['Event'], $user, $syncLocal);
  3340. }
  3341. if (!empty($data['Event']['Attribute'])) {
  3342. foreach ($data['Event']['Attribute'] as $k => $a) {
  3343. unset($data['Event']['Attribute']['id']);
  3344. if (isset($a['distribution']) && $a['distribution'] == 4) {
  3345. $data['Event']['Attribute'][$k] = $this->__captureSGForElement($a, $user, $syncLocal);
  3346. if ($data['Event']['Attribute'][$k] === false) {
  3347. unset($data['Event']['Attribute']);
  3348. }
  3349. }
  3350. }
  3351. }
  3352. if (!empty($data['Event']['Object'])) {
  3353. foreach ($data['Event']['Object'] as $k => $o) {
  3354. if (isset($o['distribution']) && $o['distribution'] == 4) {
  3355. $data['Event']['Object'][$k] = $this->__captureSGForElement($o, $user, $syncLocal);
  3356. if ($data['Event']['Object'][$k] === false) {
  3357. unset($data['Event']['Object'][$k]);
  3358. continue;
  3359. }
  3360. }
  3361. foreach ($o['Attribute'] as $k2 => $a) {
  3362. if (isset($a['distribution']) && $a['distribution'] == 4) {
  3363. $data['Event']['Object'][$k]['Attribute'][$k2] = $this->__captureSGForElement($a, $user, $syncLocal);
  3364. if ($data['Event']['Object'][$k]['Attribute'][$k2] === false) {
  3365. unset($data['Event']['Object'][$k]['Attribute'][$k2]);
  3366. }
  3367. }
  3368. }
  3369. }
  3370. }
  3371. // first we want to see how the creator organisation is encoded
  3372. // The options here are either by passing an organisation object along or simply passing a string along
  3373. if (isset($data['Event']['Orgc'])) {
  3374. $data['Event']['orgc_id'] = $this->Orgc->captureOrg($data['Event']['Orgc'], $user);
  3375. unset($data['Event']['Orgc']);
  3376. } elseif (isset($data['Event']['orgc'])) {
  3377. $data['Event']['orgc_id'] = $this->Orgc->captureOrg($data['Event']['orgc'], $user);
  3378. unset($data['Event']['orgc']);
  3379. } else {
  3380. $data['Event']['orgc_id'] = $user['org_id'];
  3381. }
  3382. $event_tag_ids = array();
  3383. if (isset($data['Event']['EventTag'])) {
  3384. if (isset($data['Event']['EventTag']['id'])) {
  3385. $data['Event']['EventTag'] = array($data['Event']['EventTag']);
  3386. }
  3387. $eventTags = array();
  3388. foreach ($data['Event']['EventTag'] as $k => $tag) {
  3389. $temp = $this->EventTag->Tag->captureTag($data['Event']['EventTag'][$k]['Tag'], $user);
  3390. if ($temp && !in_array($temp, $event_tag_ids)) {
  3391. $eventTags[] = array('tag_id' => $temp);
  3392. $event_tag_ids[] = $temp;
  3393. }
  3394. unset($data['Event']['EventTag'][$k]);
  3395. }
  3396. $data['Event']['EventTag'] = $eventTags;
  3397. } else {
  3398. $data['Event']['EventTag'] = array();
  3399. }
  3400. if (isset($data['Event']['Tag'])) {
  3401. if (isset($data['Event']['Tag']['name'])) {
  3402. $data['Event']['Tag'] = array($data['Event']['Tag']);
  3403. }
  3404. foreach ($data['Event']['Tag'] as $tag) {
  3405. $tag_id = $this->EventTag->Tag->captureTag($tag, $user);
  3406. if ($tag_id && !in_array($tag_id, $event_tag_ids)) {
  3407. $data['Event']['EventTag'][] = array('tag_id' => $tag_id);
  3408. $event_tag_ids[] = $tag_id;
  3409. }
  3410. }
  3411. unset($data['Event']['Tag']);
  3412. }
  3413. if (!empty($data['Event']['Attribute'])) {
  3414. $data['Event']['Attribute'] = $this->__captureAttributeTags($data['Event']['Attribute'], $user);
  3415. }
  3416. if (!empty($data['Event']['Object'])) {
  3417. foreach ($data['Event']['Object'] as $k => $object) {
  3418. if (!empty($data['Event']['Object'][$k]['Attribute'])) {
  3419. $data['Event']['Object'][$k]['Attribute'] = $this->__captureAttributeTags($data['Event']['Object'][$k]['Attribute'], $user);
  3420. }
  3421. }
  3422. }
  3423. return $data;
  3424. }
  3425. private function __captureAttributeTags($attributes, $user)
  3426. {
  3427. foreach ($attributes as $k => $a) {
  3428. if (isset($attributes[$k]['AttributeTag'])) {
  3429. if (isset($attributes[$k]['AttributeTag']['id'])) {
  3430. $attributes[$k]['AttributeTag'] = array($attributes[$k]['AttributeTag']);
  3431. }
  3432. $attributeTags = array();
  3433. foreach ($attributes[$k]['AttributeTag'] as $tk => $tag) {
  3434. $attributeTags[] = array('tag_id' => $this->Attribute->AttributeTag->Tag->captureTag($attributes[$k]['AttributeTag'][$tk]['Tag'], $user));
  3435. unset($attributes[$k]['AttributeTag'][$tk]);
  3436. }
  3437. $attributes[$k]['AttributeTag'] = $attributeTags;
  3438. } else {
  3439. $attributes[$k]['AttributeTag'] = array();
  3440. }
  3441. if (isset($attributes[$k]['Tag'])) {
  3442. if (isset($attributes[$k]['Tag']['name'])) {
  3443. $attributes[$k]['Tag'] = array($attributes[$k]['Tag']);
  3444. }
  3445. foreach ($attributes[$k]['Tag'] as $tag) {
  3446. $tag_id = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user);
  3447. if ($tag_id) {
  3448. $attributes[$k]['AttributeTag'][] = array('tag_id' => $tag_id);
  3449. }
  3450. }
  3451. unset($attributes[$k]['Tag']);
  3452. }
  3453. }
  3454. return $attributes;
  3455. }
  3456. public function checkEventBlockRules($event)
  3457. {
  3458. $this->AdminSetting = ClassRegistry::init('AdminSetting');
  3459. $setting = $this->AdminSetting->find('first', [
  3460. 'conditions' => ['setting' => 'eventBlockRule'],
  3461. 'recursive' => -1
  3462. ]);
  3463. if (empty($setting) || empty($setting['AdminSetting']['value'])) {
  3464. return true;
  3465. }
  3466. $rules = json_decode($setting['AdminSetting']['value'], true);
  3467. if (empty($rules)) {
  3468. return true;
  3469. }
  3470. if (!empty($rules['tags'])) {
  3471. if (!is_array($rules['tags'])) {
  3472. $rules['tags'] = [$rules['tags']];
  3473. }
  3474. $eventTags = Hash::extract($event, 'Event.Tag.{n}.name');
  3475. if (empty($eventTags)) {
  3476. $eventTags = Hash::extract($event, 'Event.EventTag.{n}.Tag.name');
  3477. }
  3478. if (!empty($eventTags)) {
  3479. foreach ($rules['tags'] as $blockTag) {
  3480. if (in_array($blockTag, $eventTags)) {
  3481. return false;
  3482. }
  3483. }
  3484. }
  3485. }
  3486. return true;
  3487. }
  3488. /**
  3489. * @param array $user
  3490. * @param string $data
  3491. * @param bool $isXml
  3492. * @param bool $takeOwnership
  3493. * @param bool $publish
  3494. * @return array[]
  3495. * @throws Exception
  3496. */
  3497. public function addMISPExportFile(array $user, $data, $isXml = false, $takeOwnership = false, $publish = false)
  3498. {
  3499. if (empty($data)) {
  3500. throw new Exception("File is empty");
  3501. }
  3502. if ($isXml) {
  3503. App::uses('Xml', 'Utility');
  3504. $dataArray = Xml::toArray(Xml::build($data));
  3505. } else {
  3506. $dataArray = $this->jsonDecode($data);
  3507. if (isset($dataArray['response'][0])) {
  3508. foreach ($dataArray['response'] as $k => $temp) {
  3509. $dataArray['Event'][] = $temp['Event'];
  3510. unset($dataArray['response'][$k]);
  3511. }
  3512. }
  3513. }
  3514. // In case we receive an event that is not encapsulated in a response. This should never happen (unless it's a copy+paste fail),
  3515. // but just in case, let's clean it up anyway.
  3516. if (isset($dataArray['Event'])) {
  3517. $dataArray['response']['Event'] = $dataArray['Event'];
  3518. unset($dataArray['Event']);
  3519. }
  3520. if (!isset($dataArray['response']) || !isset($dataArray['response']['Event'])) {
  3521. $exception = $isXml ? __('This is not a valid MISP XML file.') : __('This is not a valid MISP JSON file.');
  3522. throw new Exception($exception);
  3523. }
  3524. $dataArray = $this->updateXMLArray($dataArray);
  3525. $results = array();
  3526. $validationIssues = array();
  3527. if (isset($dataArray['response']['Event'][0])) {
  3528. foreach ($dataArray['response']['Event'] as $event) {
  3529. $result = array('info' => $event['info']);
  3530. if ($takeOwnership) {
  3531. $event['orgc_id'] = $user['org_id'];
  3532. unset($event['Orgc']);
  3533. }
  3534. $event = array('Event' => $event);
  3535. $created_id = 0;
  3536. $event['Event']['locked'] = 1;
  3537. $event['Event']['published'] = $publish;
  3538. $result['result'] = $this->_add($event, true, $user, '', null, false, null, $created_id, $validationIssues);
  3539. $result['id'] = $created_id;
  3540. $result['validationIssues'] = $validationIssues;
  3541. $results[] = $result;
  3542. }
  3543. } else {
  3544. $temp['Event'] = $dataArray['response']['Event'];
  3545. if ($takeOwnership) {
  3546. $temp['Event']['orgc_id'] = $user['org_id'];
  3547. unset($temp['Event']['Orgc']);
  3548. }
  3549. $created_id = 0;
  3550. $temp['Event']['locked'] = 1;
  3551. $temp['Event']['published'] = $publish;
  3552. $result = $this->_add($temp, true, $user, '', null, false, null, $created_id, $validationIssues);
  3553. $results = array(array(
  3554. 'info' => $temp['Event']['info'],
  3555. 'result' => $result,
  3556. 'id' => $created_id,
  3557. 'validationIssues' => $validationIssues,
  3558. ));
  3559. }
  3560. return $results;
  3561. }
  3562. // Low level function to add an Event based on an Event $data array
  3563. public function _add(array &$data, $fromXml, array $user, $org_id = 0, $passAlong = null, $fromPull = false, $jobId = null, &$created_id = 0, &$validationErrors = array())
  3564. {
  3565. if ($jobId) {
  3566. App::uses('AuthComponent', 'Controller/Component');
  3567. }
  3568. if (Configure::read('MISP.enableEventBlocklisting') !== false && isset($data['Event']['uuid'])) {
  3569. $this->EventBlocklist = ClassRegistry::init('EventBlocklist');
  3570. if ($this->EventBlocklist->isBlocked($data['Event']['uuid'])) {
  3571. return 'Blocked by blocklist';
  3572. }
  3573. }
  3574. if (!$this->checkEventBlockRules($data)) {
  3575. return 'Blocked by event block rules';
  3576. }
  3577. $this->Log = ClassRegistry::init('Log');
  3578. if (empty($data['Event']['Attribute']) && empty($data['Event']['Object']) && !empty($data['Event']['published']) && empty($data['Event']['EventReport'])) {
  3579. $this->Log->create();
  3580. $validationErrors['Event'] = 'Received a published event that was empty. Event add process blocked.';
  3581. $this->Log->save(array(
  3582. 'org' => $user['Organisation']['name'],
  3583. 'model' => 'Event',
  3584. 'model_id' => 0,
  3585. 'email' => $user['email'],
  3586. 'action' => 'add',
  3587. 'user_id' => $user['id'],
  3588. 'title' => $validationErrors['Event'],
  3589. 'change' => ''
  3590. ));
  3591. return json_encode($validationErrors);
  3592. }
  3593. $this->create();
  3594. // force check userid and orgname to be from yourself
  3595. $data['Event']['user_id'] = $user['id'];
  3596. if ($fromPull) {
  3597. $data['Event']['org_id'] = $org_id;
  3598. } else {
  3599. $data['Event']['org_id'] = $user['Organisation']['id'];
  3600. }
  3601. // set these fields if the event is freshly created and not pushed from another instance.
  3602. // Moved out of if (!$fromXML), since we might get a restful event without the orgc/timestamp set
  3603. if (!isset($data['Event']['orgc_id']) && !isset($data['Event']['orgc'])) {
  3604. $data['Event']['orgc_id'] = $data['Event']['org_id'];
  3605. } else {
  3606. if (!isset($data['Event']['Orgc'])) {
  3607. if (isset($data['Event']['orgc_id']) && $data['Event']['orgc_id'] != $user['org_id'] && !$user['Role']['perm_sync'] && !$user['Role']['perm_site_admin']) {
  3608. throw new MethodNotAllowedException('Event cannot be created as you are not a member of the creator organisation.');
  3609. }
  3610. } else {
  3611. if ($data['Event']['Orgc']['uuid'] != $user['Organisation']['uuid'] && !$user['Role']['perm_sync'] && !$user['Role']['perm_site_admin']) {
  3612. throw new MethodNotAllowedException('Event cannot be created as you are not a member of the creator organisation.');
  3613. }
  3614. if (isset($data['Event']['orgc']) && $data['Event']['orgc'] != $user['Organisation']['name'] && !$user['Role']['perm_sync'] && !$user['Role']['perm_site_admin']) {
  3615. throw new MethodNotAllowedException('Event cannot be created as you are not a member of the creator organisation.');
  3616. }
  3617. }
  3618. if (isset($data['Event']['orgc_id']) && $data['Event']['orgc_id'] != $user['org_id'] && !$user['Role']['perm_sync'] && !$user['Role']['perm_site_admin']) {
  3619. throw new MethodNotAllowedException('Event cannot be created as you are not a member of the creator organisation.');
  3620. }
  3621. }
  3622. if (!Configure::check('MISP.enableOrgBlocklisting') || Configure::read('MISP.enableOrgBlocklisting') !== false) {
  3623. $this->OrgBlocklist = ClassRegistry::init('OrgBlocklist');
  3624. if (!isset($data['Event']['Orgc']['uuid'])) {
  3625. $orgc = $this->Orgc->find('first', array('conditions' => array('Orgc.id' => $data['Event']['orgc_id']), 'fields' => array('Orgc.uuid'), 'recursive' => -1));
  3626. } else {
  3627. $orgc = array('Orgc' => array('uuid' => $data['Event']['Orgc']['uuid']));
  3628. }
  3629. if ($this->OrgBlocklist->hasAny(array('OrgBlocklist.org_uuid' => $orgc['Orgc']['uuid']))) {
  3630. return 'blocked';
  3631. }
  3632. }
  3633. if ($passAlong) {
  3634. $this->Server = ClassRegistry::init('Server');
  3635. $server = $this->Server->find('first', array(
  3636. 'conditions' => array(
  3637. 'Server.id' => $passAlong
  3638. ),
  3639. 'recursive' => -1,
  3640. 'fields' => array(
  3641. 'Server.name',
  3642. 'Server.id',
  3643. 'Server.unpublish_event',
  3644. 'Server.publish_without_email',
  3645. 'Server.internal'
  3646. )
  3647. ));
  3648. } else {
  3649. $server['Server']['internal'] = false;
  3650. }
  3651. if ($fromXml) {
  3652. // Workaround for different structure in XML/array than what CakePHP expects
  3653. $data = $this->cleanupEventArrayFromXML($data);
  3654. // the event_id field is not set (normal) so make sure no validation errors are thrown
  3655. // LATER do this with $this->validator()->remove('event_id');
  3656. unset($this->Attribute->validate['event_id']); // otherwise gives bugs because event_id is not set
  3657. unset($this->Attribute->validate['value']['uniqueValue']); // unset this - we are saving a new event, there are no values to compare against and event_id is not set in the attributes
  3658. }
  3659. unset($data['Event']['id']);
  3660. if (isset($data['Event']['published']) && $data['Event']['published'] && $user['Role']['perm_publish'] == 0) {
  3661. $data['Event']['published'] = 0;
  3662. }
  3663. if (isset($data['Event']['uuid'])) {
  3664. // check if the uuid already exists
  3665. $existingEvent = $this->find('first', array('conditions' => array('Event.uuid' => $data['Event']['uuid'])));
  3666. if ($existingEvent) {
  3667. // RESTful, set response location header so client can find right URL to edit
  3668. if ($fromPull) {
  3669. return false;
  3670. }
  3671. if ($fromXml) {
  3672. $created_id = $existingEvent['Event']['id'];
  3673. }
  3674. return $existingEvent['Event']['id'];
  3675. } else {
  3676. if ($fromXml) {
  3677. $data = $this->__captureObjects($data, $user, $server['Server']['internal']);
  3678. }
  3679. if ($data === false) {
  3680. $failedCapture = true;
  3681. }
  3682. }
  3683. } else {
  3684. if ($fromXml) {
  3685. $data = $this->__captureObjects($data, $user, $server['Server']['internal']);
  3686. }
  3687. if ($data === false) {
  3688. $failedCapture = true;
  3689. }
  3690. }
  3691. if (!empty($failedCapture)) {
  3692. $this->Log->create();
  3693. $this->Log->save(array(
  3694. 'org' => $user['Organisation']['name'],
  3695. 'model' => 'Event',
  3696. 'model_id' => 0,
  3697. 'email' => $user['email'],
  3698. 'action' => 'add',
  3699. 'user_id' => $user['id'],
  3700. 'title' => 'Event could not be saved due to a failed sharing group capture.',
  3701. 'change' => ''
  3702. ));
  3703. $validationErrors['Event'] = 'Issues saving a Sharing Group.';
  3704. return json_encode($validationErrors);
  3705. }
  3706. $fieldList = array(
  3707. 'Event' => array(
  3708. 'org_id',
  3709. 'orgc_id',
  3710. 'date',
  3711. 'threat_level_id',
  3712. 'analysis',
  3713. 'info',
  3714. 'user_id',
  3715. 'published',
  3716. 'uuid',
  3717. 'timestamp',
  3718. 'distribution',
  3719. 'sharing_group_id',
  3720. 'locked',
  3721. 'disable_correlation',
  3722. 'extends_uuid'
  3723. ),
  3724. 'Attribute' => $this->Attribute->captureFields,
  3725. 'Object' => array(
  3726. 'name',
  3727. 'meta-category',
  3728. 'description',
  3729. 'template_uuid',
  3730. 'template_version',
  3731. 'event_id',
  3732. 'uuid',
  3733. 'timestamp',
  3734. 'distribution',
  3735. 'sharing_group_id',
  3736. 'comment',
  3737. 'deleted'
  3738. ),
  3739. 'ObjectRelation' => array(),
  3740. 'EventReport' => $this->EventReport->captureFields,
  3741. );
  3742. $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList['Event']));
  3743. if ($saveResult) {
  3744. if ($passAlong) {
  3745. if ($server['Server']['publish_without_email'] == 0) {
  3746. $st = "enabled";
  3747. } else {
  3748. $st = "disabled";
  3749. }
  3750. $this->Log->create();
  3751. $this->Log->save(array(
  3752. 'org' => $user['Organisation']['name'],
  3753. 'model' => 'Event',
  3754. 'model_id' => $saveResult['Event']['id'],
  3755. 'email' => $user['email'],
  3756. 'action' => 'add',
  3757. 'user_id' => $user['id'],
  3758. 'title' => 'Event pulled from Server(' . $server['Server']['id'] . ') - "' . $server['Server']['name'] . '" - Notification by mail ' . $st,
  3759. 'change' => ''
  3760. ));
  3761. }
  3762. if (isset($data['Event']['EventTag'])) {
  3763. foreach ($data['Event']['EventTag'] as $et) {
  3764. $this->EventTag->create();
  3765. $et['event_id'] = $this->id;
  3766. $this->EventTag->save($et);
  3767. }
  3768. }
  3769. $parentEvent = $this->find('first', array(
  3770. 'conditions' => array('Event.id' => $this->id),
  3771. 'recursive' => -1
  3772. ));
  3773. if (!empty($data['Event']['Attribute'])) {
  3774. foreach ($data['Event']['Attribute'] as $k => $attribute) {
  3775. $block = false;
  3776. for ($i = 0; $i < $k; $i++) {
  3777. if (empty($data['Event']['Attribute'][$i])) {
  3778. continue;
  3779. }
  3780. if (
  3781. $data['Event']['Attribute'][$i]['value'] == $attribute['value'] &&
  3782. $data['Event']['Attribute'][$i]['type'] == $attribute['type'] &&
  3783. $data['Event']['Attribute'][$i]['category'] == $attribute['category']
  3784. ) {
  3785. $block = true;
  3786. unset($data['Event']['Attribute'][$i]);
  3787. break;
  3788. }
  3789. }
  3790. if (!$block) {
  3791. $data['Event']['Attribute'][$k] = $this->Attribute->captureAttribute($attribute, $this->id, $user, 0, $this->Log, $parentEvent);
  3792. }
  3793. }
  3794. $data['Event']['Attribute'] = array_values($data['Event']['Attribute']);
  3795. }
  3796. $referencesToCapture = array();
  3797. if (!empty($data['Event']['Object'])) {
  3798. foreach ($data['Event']['Object'] as $object) {
  3799. $result = $this->Object->captureObject($object, $this->id, $user, $this->Log, false);
  3800. }
  3801. foreach ($data['Event']['Object'] as $object) {
  3802. if (isset($object['ObjectReference'])) {
  3803. foreach ($object['ObjectReference'] as $objectRef) {
  3804. $objectRef['source_uuid'] = $object['uuid'];
  3805. $referencesToCapture[] = $objectRef;
  3806. }
  3807. }
  3808. }
  3809. }
  3810. foreach ($referencesToCapture as $referenceToCapture) {
  3811. $result = $this->Object->ObjectReference->captureReference(
  3812. $referenceToCapture,
  3813. $this->id,
  3814. $user,
  3815. $this->Log
  3816. );
  3817. }
  3818. if (!empty($data['Event']['EventReport'])) {
  3819. foreach ($data['Event']['EventReport'] as $report) {
  3820. $result = $this->EventReport->captureReport($user, $report, $this->id);
  3821. }
  3822. }
  3823. // zeroq: check if sightings are attached and add to event
  3824. if (isset($data['Sighting']) && !empty($data['Sighting'])) {
  3825. $this->Sighting = ClassRegistry::init('Sighting');
  3826. foreach ($data['Sighting'] as $s) {
  3827. $result = $this->Sighting->saveSightings($s['attribute_uuid'], false, $s['date_sighting'], $user, $s['type'], $s['source'], $s['uuid']);
  3828. }
  3829. }
  3830. if ($fromXml) {
  3831. $created_id = $this->id;
  3832. }
  3833. if (!empty($data['Event']['published']) && 1 == $data['Event']['published']) {
  3834. // do the necessary actions to publish the event (email, upload,...)
  3835. if (('true' != Configure::read('MISP.disablerestalert')) && (empty($server) || empty($server['Server']['publish_without_email']))) {
  3836. $this->sendAlertEmailRouter($this->getID(), $user);
  3837. }
  3838. $this->publish($this->getID(), $passAlong);
  3839. }
  3840. if (empty($data['Event']['locked']) && !empty(Configure::read('MISP.default_event_tag_collection'))) {
  3841. $this->TagCollection = ClassRegistry::init('TagCollection');
  3842. $tagCollection = $this->TagCollection->fetchTagCollection($user, array('conditions' => array('TagCollection.id' => Configure::read('MISP.default_event_tag_collection'))));
  3843. if (!empty($tagCollection)) {
  3844. $tag_id_list = array();
  3845. foreach ($tagCollection[0]['TagCollectionTag'] as $tagCollectionTag) {
  3846. $tag_id_list[] = $tagCollectionTag['tag_id'];
  3847. }
  3848. foreach ($tag_id_list as $tag_id) {
  3849. $tag = $this->EventTag->Tag->find('first', array(
  3850. 'conditions' => array('Tag.id' => $tag_id),
  3851. 'recursive' => -1,
  3852. 'fields' => array('Tag.name')
  3853. ));
  3854. if (!empty($tag)) {
  3855. $found = $this->EventTag->find('first', array(
  3856. 'conditions' => array(
  3857. 'event_id' => $this->id,
  3858. 'tag_id' => $tag_id
  3859. ),
  3860. 'recursive' => -1,
  3861. ));
  3862. if (empty($found)) {
  3863. $this->EventTag->create();
  3864. if ($this->EventTag->save(array('event_id' => $this->id, 'tag_id' => $tag_id))) {
  3865. $this->Log->createLogEntry($user, 'tag', 'Event', $this->id, 'Attached tag (' . $tag_id . ') "' . $tag['Tag']['name'] . '" to event (' . $this->id . ')', 'Event (' . $this->id . ') tagged as Tag (' . $tag_id . ')');
  3866. }
  3867. }
  3868. }
  3869. }
  3870. }
  3871. }
  3872. return true;
  3873. } else {
  3874. $validationErrors['Event'] = $this->validationErrors;
  3875. return json_encode($validationErrors);
  3876. }
  3877. }
  3878. public function _edit(array &$data, array $user, $id = null, $jobId = null, $passAlong = null, $force = false)
  3879. {
  3880. $data = $this->cleanupEventArrayFromXML($data);
  3881. unset($this->Attribute->validate['event_id']);
  3882. unset($this->Attribute->validate['value']['unique']); // otherwise gives bugs because event_id is not set
  3883. // reposition to get the event.id with given uuid
  3884. if (isset($data['Event']['uuid'])) {
  3885. $conditions = ['Event.uuid' => $data['Event']['uuid']];
  3886. } elseif ($id) {
  3887. $conditions = ['Event.id' => $id];
  3888. } else {
  3889. throw new InvalidArgumentException("No event UUID or ID provided.");
  3890. }
  3891. $existingEvent = $this->find('first', ['conditions' => $conditions, 'recursive' => -1]);
  3892. if ($passAlong) {
  3893. $this->Server = ClassRegistry::init('Server');
  3894. $server = $this->Server->find('first', array(
  3895. 'conditions' => array(
  3896. 'Server.id' => $passAlong
  3897. ),
  3898. 'recursive' => -1,
  3899. 'fields' => array(
  3900. 'Server.name',
  3901. 'Server.id',
  3902. 'Server.unpublish_event',
  3903. 'Server.publish_without_email',
  3904. 'Server.internal',
  3905. )
  3906. ));
  3907. } else {
  3908. $server['Server']['internal'] = false;
  3909. }
  3910. // If the event exists...
  3911. $dateObj = new DateTime();
  3912. $date = $dateObj->getTimestamp();
  3913. if (count($existingEvent)) {
  3914. $data['Event']['id'] = $existingEvent['Event']['id'];
  3915. $id = $existingEvent['Event']['id'];
  3916. // Conditions affecting all:
  3917. // user.org == event.org
  3918. // edit timestamp newer than existing event timestamp
  3919. if ($force || !isset($data['Event']['timestamp']) || $data['Event']['timestamp'] > $existingEvent['Event']['timestamp']) {
  3920. if (!isset($data['Event']['timestamp'])) {
  3921. $data['Event']['timestamp'] = $date;
  3922. }
  3923. if (isset($data['Event']['distribution']) && $data['Event']['distribution'] == 4) {
  3924. if (!isset($data['Event']['SharingGroup'])) {
  3925. if (!isset($data['Event']['sharing_group_id'])) {
  3926. return(array('error' => 'Event could not be saved: Sharing group chosen as the distribution level, but no sharing group specified. Make sure that the event includes a valid sharing_group_id or change to a different distribution level.'));
  3927. }
  3928. if (!$this->SharingGroup->checkIfAuthorised($user, $data['Event']['sharing_group_id'])) {
  3929. return(array('error' => 'Event could not be saved: Invalid sharing group or you don\'t have access to that sharing group.'));
  3930. }
  3931. } else {
  3932. $data['Event']['sharing_group_id'] = $this->SharingGroup->captureSG($data['Event']['SharingGroup'], $user, $server['Server']['internal']);
  3933. unset($data['Event']['SharingGroup']);
  3934. if ($data['Event']['sharing_group_id'] === false) {
  3935. return (array('error' => 'Event could not be saved: User not authorised to create the associated sharing group.'));
  3936. }
  3937. }
  3938. }
  3939. // If the above is true, we have two more options:
  3940. // For users that are of the creating org of the event, always allow the edit
  3941. // For users that are sync users, only allow the edit if the event is locked
  3942. if ($existingEvent['Event']['orgc_id'] === $user['org_id']
  3943. || ($user['Role']['perm_sync'] && $existingEvent['Event']['locked']) || $user['Role']['perm_site_admin']) {
  3944. if ($user['Role']['perm_sync']) {
  3945. if (isset($data['Event']['distribution']) && $data['Event']['distribution'] == 4 && !$this->SharingGroup->checkIfAuthorised($user, $data['Event']['sharing_group_id'])) {
  3946. return (array('error' => 'Event could not be saved: The sync user has to have access to the sharing group in order to be able to edit it.'));
  3947. }
  3948. }
  3949. } else {
  3950. return (array('error' => 'Event could not be saved: The user used to edit the event is not authorised to do so. This can be caused by the user not being of the same organisation as the original creator of the event whilst also not being a site administrator.'));
  3951. }
  3952. } else {
  3953. return (array('error' => 'Event could not be saved: Event in the request not newer than the local copy.'));
  3954. }
  3955. $changed = false;
  3956. // If a field is not set in the request, just reuse the old value
  3957. // Also, compare the event to the existing event and see whether this is a meaningful change
  3958. $recoverFields = array('analysis', 'threat_level_id', 'info', 'distribution', 'date');
  3959. foreach ($recoverFields as $rF) {
  3960. if (!isset($data['Event'][$rF])) {
  3961. $data['Event'][$rF] = $existingEvent['Event'][$rF];
  3962. } else {
  3963. if ($data['Event'][$rF] != $existingEvent['Event'][$rF]) {
  3964. $changed = true;
  3965. }
  3966. }
  3967. }
  3968. } else {
  3969. return (array('error' => 'Event could not be saved: Could not find the local event.'));
  3970. }
  3971. if (!empty($data['Event']['published']) && !$user['Role']['perm_publish']) {
  3972. $data['Event']['published'] = 0;
  3973. }
  3974. if (!isset($data['Event']['published'])) {
  3975. $data['Event']['published'] = 0;
  3976. }
  3977. $fieldList = array(
  3978. 'date',
  3979. 'threat_level_id',
  3980. 'analysis',
  3981. 'info',
  3982. 'published',
  3983. 'uuid',
  3984. 'distribution',
  3985. 'timestamp',
  3986. 'sharing_group_id',
  3987. 'disable_correlation',
  3988. 'extends_uuid'
  3989. );
  3990. $saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList));
  3991. $this->Log = ClassRegistry::init('Log');
  3992. if ($saveResult) {
  3993. $validationErrors = array();
  3994. if (isset($data['Event']['Attribute'])) {
  3995. $data['Event']['Attribute'] = array_values($data['Event']['Attribute']);
  3996. foreach ($data['Event']['Attribute'] as $k => $attribute) {
  3997. $nothingToChange = false;
  3998. $result = $this->Attribute->editAttribute($attribute, $this->id, $user, 0, $this->Log, $force, $nothingToChange);
  3999. if ($result !== true) {
  4000. $validationErrors['Attribute'][] = $result;
  4001. }
  4002. if (!$nothingToChange) {
  4003. $changed = true;
  4004. }
  4005. }
  4006. }
  4007. if (isset($data['Event']['Object'])) {
  4008. $data['Event']['Object'] = array_values($data['Event']['Object']);
  4009. foreach ($data['Event']['Object'] as $k => $object) {
  4010. $nothingToChange = false;
  4011. $result = $this->Object->editObject($object, $this->id, $user, $this->Log, $force, $nothingToChange);
  4012. if ($result !== true) {
  4013. $validationErrors['Object'][] = $result;
  4014. }
  4015. if (!$nothingToChange) {
  4016. $changed = true;
  4017. }
  4018. }
  4019. foreach ($data['Event']['Object'] as $object) {
  4020. if (isset($object['ObjectReference'])) {
  4021. foreach ($object['ObjectReference'] as $objectRef) {
  4022. $nothingToChange = false;
  4023. $result = $this->Object->ObjectReference->captureReference($objectRef, $this->id, $user, $this->Log, $force, $nothingToChange);
  4024. if ($result && !$nothingToChange) {
  4025. $changed = true;
  4026. }
  4027. }
  4028. }
  4029. }
  4030. }
  4031. if (isset($data['Event']['EventReport'])) {
  4032. foreach ($data['Event']['EventReport'] as $i => $report) {
  4033. $nothingToChange = false;
  4034. $result = $this->EventReport->editReport($user, $report, $this->id, true, $nothingToChange);
  4035. if (!empty($result)) {
  4036. $validationErrors['EventReport'][] = $result;
  4037. }
  4038. if (!$nothingToChange) {
  4039. $changed = true;
  4040. }
  4041. }
  4042. }
  4043. if (isset($data['Event']['Tag']) && $user['Role']['perm_tagger']) {
  4044. foreach ($data['Event']['Tag'] as $tag) {
  4045. $tag_id = $this->EventTag->Tag->captureTag($tag, $user);
  4046. if ($tag_id) {
  4047. $nothingToChange = false;
  4048. $tag['id'] = $tag_id;
  4049. $result = $this->EventTag->handleEventTag($this->id, $tag, $nothingToChange);
  4050. if ($result && !$nothingToChange) {
  4051. $changed = true;
  4052. }
  4053. } else {
  4054. // If we couldn't attach the tag it is most likely because we couldn't create it - which could have many reasons
  4055. // However, if a tag couldn't be added, it could also be that the user is a tagger but not a tag editor
  4056. // In which case if no matching tag is found, no tag ID is returned. Logging these is pointless as it is the correct behaviour.
  4057. if ($user['Role']['perm_tag_editor']) {
  4058. $this->Log->create();
  4059. $this->Log->save(array(
  4060. 'org' => $user['Organisation']['name'],
  4061. 'model' => 'Event',
  4062. 'model_id' => $this->id,
  4063. 'email' => $user['email'],
  4064. 'action' => 'edit',
  4065. 'user_id' => $user['id'],
  4066. 'title' => 'Failed create or attach Tag ' . $tag['name'] . ' to the event.',
  4067. 'change' => ''
  4068. ));
  4069. }
  4070. }
  4071. }
  4072. }
  4073. // zeroq: if sightings then attach to event
  4074. if (isset($data['Sighting']) && !empty($data['Sighting'])) {
  4075. $this->Sighting = ClassRegistry::init('Sighting');
  4076. foreach ($data['Sighting'] as $s) {
  4077. $result = $this->Sighting->saveSightings($s['attribute_uuid'], false, $s['date_sighting'], $user, $s['type'], $s['source'], $s['uuid']);
  4078. }
  4079. }
  4080. // if published -> do the actual publishing
  4081. if ($changed && (!empty($data['Event']['published']) && 1 == $data['Event']['published'])) {
  4082. // The edited event is from a remote server ?
  4083. if ($passAlong) {
  4084. if ($server['Server']['publish_without_email'] == 0) {
  4085. $st = "enabled";
  4086. } else {
  4087. $st = "disabled";
  4088. }
  4089. $this->Log->create();
  4090. $this->Log->save(array(
  4091. 'org' => $user['Organisation']['name'],
  4092. 'model' => 'Event',
  4093. 'model_id' => $saveResult['Event']['id'],
  4094. 'email' => $user['email'],
  4095. 'action' => 'add',
  4096. 'user_id' => $user['id'],
  4097. 'title' => 'Event edited from Server(' . $server['Server']['id'] . ') - "' . $server['Server']['name'] . '" - Notification by mail ' . $st,
  4098. 'change' => ''
  4099. ));
  4100. } else {
  4101. $this->Log->create();
  4102. $this->Log->save(array(
  4103. 'org' => $user['Organisation']['name'],
  4104. 'model' => 'Event',
  4105. 'model_id' => $saveResult['Event']['id'],
  4106. 'email' => $user['email'],
  4107. 'action' => 'add',
  4108. 'user_id' => $user['id'],
  4109. 'title' => 'Event edited (locally)',
  4110. 'change' => ''
  4111. ));
  4112. }
  4113. // do the necessary actions to publish the event (email, upload,...)
  4114. if ((true != Configure::read('MISP.disablerestalert')) && (empty($server) || empty($server['Server']['publish_without_email']))) {
  4115. $this->sendAlertEmailRouter($id, $user, $existingEvent['Event']['publish_timestamp']);
  4116. }
  4117. $this->publish($existingEvent['Event']['id']);
  4118. }
  4119. return true;
  4120. }
  4121. return $this->validationErrors;
  4122. }
  4123. // format has to be:
  4124. // array('Event' => array(), 'Attribute' => array('ShadowAttribute' => array()), 'EventTag' => array(), 'ShadowAttribute' => array());
  4125. public function savePreparedEvent($event)
  4126. {
  4127. unset($event['Event']['id']);
  4128. $this->create();
  4129. $this->save($event['Event']);
  4130. $event['Event']['id'] = $this->id;
  4131. $objects = array('Attribute', 'ShadowAttribute', 'EventTag', 'Object');
  4132. foreach ($objects as $object_type) {
  4133. if (!empty($event[$object_type])) {
  4134. $saveMethod = '__savePrepared' . $object_type;
  4135. foreach ($event[$object_type] as $object) {
  4136. $this->$saveMethod($object, $event);
  4137. }
  4138. }
  4139. }
  4140. if (!empty($event['Object'])) {
  4141. $objectRefTypes = array('Attribute', 'Object');
  4142. foreach ($event['Object'] as $k => $object) {
  4143. foreach ($object['ObjectReference'] as $k2 => $objectRef) {
  4144. $savedObjectRef = $this->Object->ObjectReference->find('first', array(
  4145. 'recursive' => -1,
  4146. 'conditions' => array('ObjectReference.uuid' => $objectRef['uuid'])
  4147. ));
  4148. $objectRefType = intval($savedObjectRef['ObjectReference']['referenced_type']);
  4149. $element = $this->{$objectRefTypes[$objectRefType]}->find('first', array(
  4150. 'conditions' => array($objectRefTypes[$objectRefType] . '.uuid' => $objectRef['referenced_uuid']),
  4151. 'recursive' => -1,
  4152. 'fields' => array($objectRefTypes[$objectRefType] . '.id')
  4153. ));
  4154. $savedObjectRef['ObjectReference']['referenced_id'] = $element[$objectRefTypes[$objectRefType]]['id'];
  4155. $result = $this->Object->ObjectReference->save($savedObjectRef);
  4156. }
  4157. }
  4158. }
  4159. return $event['Event']['id'];
  4160. }
  4161. private function __savePreparedAttribute(&$attribute, $event, $object_id = 0)
  4162. {
  4163. unset($attribute['id']);
  4164. $attribute['event_id'] = $event['Event']['id'];
  4165. $attribute['object_id'] = $object_id;
  4166. $this->Attribute->create();
  4167. $this->Attribute->save($attribute);
  4168. foreach ($attribute['ShadowAttribute'] as $k => $sa) {
  4169. $this->__savePreparedShadowAttribute($sa, $event, $this->Attribute->id);
  4170. }
  4171. foreach ($attribute['AttributeTag'] as $k => $at) {
  4172. $this->__savePreparedAttributeTag($at, $event, $this->Attribute->id);
  4173. }
  4174. return true;
  4175. }
  4176. private function __savePreparedObject(&$object, $event)
  4177. {
  4178. unset($object['id']);
  4179. $object['event_id'] = $event['Event']['id'];
  4180. $this->Object->create();
  4181. $this->Object->save($object);
  4182. foreach ($object['Attribute'] as $k => $a) {
  4183. $this->__savePreparedAttribute($a, $event, $this->Object->id);
  4184. }
  4185. foreach ($object['ObjectReference'] as $objectRef) {
  4186. $this->__savePreparedObjectReference($objectRef, $event, $this->Object->id, $object['uuid']);
  4187. }
  4188. return true;
  4189. }
  4190. #referenced IDs have to be updated after everything else is done!
  4191. private function __savePreparedObjectReference($objectRef, $event, $object_id, $object_uuid)
  4192. {
  4193. unset($objectRef['id']);
  4194. $objectRef['event_id'] = $event['Event']['id'];
  4195. $objectRef['object_id'] = $object_id;
  4196. $objectRef['object_uuid'] = $object_uuid;
  4197. $this->Object->ObjectReference->create();
  4198. $this->Object->ObjectReference->save($objectRef);
  4199. return true;
  4200. }
  4201. private function __savePreparedShadowAttribute($shadow_attribute, $event, $old_id = 0)
  4202. {
  4203. unset($shadow_attribute['id']);
  4204. $shadow_attribute['event_id'] = $event['Event']['id'];
  4205. $shadow_attribute['old_id'] = $old_id;
  4206. $this->ShadowAttribute->create();
  4207. $this->ShadowAttribute->save($shadow_attribute);
  4208. return true;
  4209. }
  4210. private function __savePreparedEventTag($event_tag, $event)
  4211. {
  4212. unset($event_tag['id']);
  4213. $event_tag['event_id'] = $event['Event']['id'];
  4214. $this->EventTag->create();
  4215. $this->EventTag->save($event_tag);
  4216. return true;
  4217. }
  4218. private function __savePreparedAttributeTag($attribute_tag, $event, $attribute_id)
  4219. {
  4220. unset($attribute_tag['id']);
  4221. $attribute_tag['event_id'] = $event['Event']['id'];
  4222. $attribute_tag['attribute_id'] = $attribute_id;
  4223. $this->Attribute->AttributeTag->create();
  4224. $this->Attribute->AttributeTag->save($attribute_tag);
  4225. return true;
  4226. }
  4227. // pass an event or an attribute together with the server id.
  4228. // If the distribution of the object outright allows for it to be shared, return true
  4229. // If the distribution is org only / comm only, return false
  4230. // If the distribution is sharing group only, check if the sync user is in the sharing group or not, return true if yes, false if no
  4231. public function checkDistributionForPush($object, $server, $context = 'Event')
  4232. {
  4233. if (empty(Configure::read('MISP.host_org_id')) || !$server['Server']['internal'] || Configure::read('MISP.host_org_id') != $server['Server']['remote_org_id']) {
  4234. if ($object[$context]['distribution'] < 2) {
  4235. return false;
  4236. }
  4237. }
  4238. if ($object[$context]['distribution'] == 4) {
  4239. if ($context === 'Event') {
  4240. return $this->SharingGroup->checkIfServerInSG($object['SharingGroup'], $server);
  4241. } else {
  4242. return $this->SharingGroup->checkIfServerInSG($object[$context]['SharingGroup'], $server);
  4243. }
  4244. }
  4245. return true;
  4246. }
  4247. // Uploads this specific event or sightings to all remote servers
  4248. public function uploadEventToServersRouter($id, $passAlong = null, $scope = 'events')
  4249. {
  4250. $eventOrgcId = $this->find('first', array(
  4251. 'conditions' => array('Event.id' => $id),
  4252. 'recursive' => -1,
  4253. 'fields' => array('Event.orgc_id')
  4254. ));
  4255. // we create a fake site admin user object to fetch the event with everything included
  4256. // This replaces the old method of manually just fetching everything, staying consistent
  4257. // with the fetchEvent() output
  4258. $elevatedUser = array(
  4259. 'Role' => array(
  4260. 'perm_site_admin' => 1,
  4261. 'perm_sync' => 1
  4262. ),
  4263. 'org_id' => $eventOrgcId['Event']['orgc_id']
  4264. );
  4265. $elevatedUser['Role']['perm_site_admin'] = 1;
  4266. $elevatedUser['Role']['perm_sync'] = 1;
  4267. $elevatedUser['Role']['perm_audit'] = 0;
  4268. $event = $this->fetchEvent($elevatedUser, array('eventid' => $id, 'metadata' => 1));
  4269. if (empty($event)) {
  4270. return true;
  4271. }
  4272. $event = $event[0];
  4273. $event['Event']['locked'] = 1;
  4274. // get a list of the servers
  4275. $this->Server = ClassRegistry::init('Server');
  4276. if ($scope === 'sightings') {
  4277. $conditions = array('push_sightings' => 1);
  4278. } else {
  4279. $conditions = array('push' => 1);
  4280. }
  4281. if ($passAlong) {
  4282. $conditions[] = array('Server.id !=' => $passAlong);
  4283. }
  4284. $servers = $this->Server->find('all', array(
  4285. 'conditions' => $conditions,
  4286. 'order' => array('Server.priority ASC', 'Server.id ASC')
  4287. ));
  4288. // iterate over the servers and upload the event
  4289. if (empty($servers)) {
  4290. return true;
  4291. }
  4292. $uploaded = true;
  4293. $failedServers = array();
  4294. App::uses('SyncTool', 'Tools');
  4295. foreach ($servers as &$server) {
  4296. if (
  4297. ($scope === 'events' &&
  4298. (!isset($server['Server']['internal']) || !$server['Server']['internal']) && $event['Event']['distribution'] < 2) ||
  4299. ($scope === 'sightings' &&
  4300. (!isset($server['Server']['push_sightings']) || !$server['Server']['push_sightings']))
  4301. ) {
  4302. continue;
  4303. }
  4304. $syncTool = new SyncTool();
  4305. $HttpSocket = $syncTool->setupHttpSocket($server);
  4306. // Skip servers where the event has come from.
  4307. if (($passAlong != $server['Server']['id'])) {
  4308. $params = array();
  4309. if (!empty($server['Server']['push_rules'])) {
  4310. $push_rules = json_decode($server['Server']['push_rules'], true);
  4311. if (!empty($push_rules['tags']['NOT'])) {
  4312. $params['blockedAttributeTags'] = $push_rules['tags']['NOT'];
  4313. }
  4314. }
  4315. $params = array_merge($params, array(
  4316. 'eventid' => $id,
  4317. 'includeAttachments' => true,
  4318. 'includeAllTags' => true,
  4319. 'deleted' => array(0,1),
  4320. 'excludeGalaxy' => 1
  4321. ));
  4322. if (!empty($server['Server']['internal'])) {
  4323. $params['excludeLocalTags'] = 0;
  4324. }
  4325. $event = $this->fetchEvent($elevatedUser, $params);
  4326. $event = $event[0];
  4327. $event['Event']['locked'] = 1;
  4328. // attach sightings if needed
  4329. if ($scope === 'sightings') {
  4330. $this->Sighting = ClassRegistry::init('Sighting');
  4331. $fakeSyncUser = array(
  4332. 'org_id' => $server['Server']['remote_org_id'],
  4333. 'Role' => array(
  4334. 'perm_site_admin' => 0
  4335. )
  4336. );
  4337. $sightings = $this->Sighting->attachToEvent($event, $fakeSyncUser, null, false, true);
  4338. if (!empty($sightings)) {
  4339. $thisUploaded = $this->uploadSightingsToServer($sightings, $server, $event['Event']['uuid'], $HttpSocket);
  4340. } else {
  4341. $thisUploaded = true;
  4342. }
  4343. } else {
  4344. $thisUploaded = $this->uploadEventToServer($event, $server, $HttpSocket, $scope);
  4345. if (isset($this->data['ShadowAttribute'])) {
  4346. $this->Server->syncProposals($HttpSocket, $server, null, $id, $this);
  4347. }
  4348. }
  4349. if (!$thisUploaded) {
  4350. $uploaded = !$uploaded ? $uploaded : $thisUploaded;
  4351. $failedServers[] = $server['Server']['url'];
  4352. }
  4353. }
  4354. }
  4355. if (!$uploaded) {
  4356. if (empty($failedServers)) {
  4357. return true;
  4358. }
  4359. return $failedServers;
  4360. } else {
  4361. return true;
  4362. }
  4363. }
  4364. public function __getPrioWorkerIfPossible()
  4365. {
  4366. $this->ResqueStatus = new ResqueStatus\ResqueStatus(Resque::redis());
  4367. $workers = $this->ResqueStatus->getWorkers();
  4368. $workerType = 'default';
  4369. foreach ($workers as $worker) {
  4370. if ($worker['queue'] === 'prio') {
  4371. $workerType = 'prio';
  4372. }
  4373. }
  4374. return $workerType;
  4375. }
  4376. public function publishRouter($id, $passAlong = null, $user, $scope = 'events')
  4377. {
  4378. if (Configure::read('MISP.background_jobs')) {
  4379. $job_type = 'publish_' . $scope;
  4380. $function = 'publish';
  4381. $message = 'Publishing.';
  4382. if ($scope === 'sightings') {
  4383. $message = 'Publishing sightings.';
  4384. $function = 'publish_sightings';
  4385. }
  4386. $job = ClassRegistry::init('Job');
  4387. $job->create();
  4388. $data = array(
  4389. 'worker' => $this->__getPrioWorkerIfPossible(),
  4390. 'job_type' => 'publish_event',
  4391. 'job_input' => 'Event ID: ' . $id,
  4392. 'status' => 0,
  4393. 'retries' => 0,
  4394. 'org_id' => $user['org_id'],
  4395. 'org' => $user['Organisation']['name'],
  4396. 'message' => $message
  4397. );
  4398. $job->save($data);
  4399. $jobId = $job->id;
  4400. $process_id = CakeResque::enqueue(
  4401. 'prio',
  4402. 'EventShell',
  4403. array($function, $id, $passAlong, $jobId, $user['id']),
  4404. true
  4405. );
  4406. $job->saveField('process_id', $process_id);
  4407. return $process_id;
  4408. } elseif ($scope === 'sightings') {
  4409. $result = $this->publish_sightings($id, $passAlong);
  4410. return $result;
  4411. } else {
  4412. $result = $this->publish($id, $passAlong);
  4413. return $result;
  4414. }
  4415. }
  4416. public function publish_sightings($id, $passAlong = null, $jobId = null)
  4417. {
  4418. if (is_numeric($id)) {
  4419. $condition = array('Event.id' => $id);
  4420. } else {
  4421. $condition = array('Event.uuid' => $id);
  4422. }
  4423. $event = $this->find('first', array(
  4424. 'recursive' => -1,
  4425. 'conditions' => $condition
  4426. ));
  4427. if (empty($event)) {
  4428. return false;
  4429. }
  4430. if ($jobId) {
  4431. $this->Behaviors->unload('SysLogLogable.SysLogLogable');
  4432. } else {
  4433. // update the DB to set the sightings timestamp
  4434. // for background jobs, this should be done already
  4435. $fieldList = array('id', 'info', 'sighting_timestamp');
  4436. $event['Event']['sighting_timestamp'] = time();
  4437. $event['Event']['skip_zmq'] = 1;
  4438. $event['Event']['skip_kafka'] = 1;
  4439. $this->save($event, array('fieldList' => $fieldList));
  4440. }
  4441. $uploaded = $this->uploadEventToServersRouter($id, $passAlong, 'sightings');
  4442. return $uploaded;
  4443. }
  4444. // Performs all the actions required to publish an event
  4445. public function publish($id, $passAlong = null, $jobId = null)
  4446. {
  4447. $event = $this->find('first', array(
  4448. 'recursive' => -1,
  4449. 'conditions' => array('Event.id' => $id)
  4450. ));
  4451. if (empty($event)) {
  4452. return false;
  4453. }
  4454. if ($jobId) {
  4455. $this->Behaviors->unload('SysLogLogable.SysLogLogable');
  4456. } else {
  4457. // update the DB to set the published flag
  4458. // for background jobs, this should be done already
  4459. $fieldList = array('published', 'id', 'info', 'publish_timestamp');
  4460. $event['Event']['published'] = 1;
  4461. $event['Event']['publish_timestamp'] = time();
  4462. $event['Event']['skip_zmq'] = 1;
  4463. $event['Event']['skip_kafka'] = 1;
  4464. $this->save($event, array('fieldList' => $fieldList));
  4465. }
  4466. $pubToZmq = Configure::read('Plugin.ZeroMQ_enable');
  4467. $kafkaTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
  4468. $pubToKafka = Configure::read('Plugin.Kafka_enable') && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaTopic);
  4469. if ($pubToZmq || $pubToKafka) {
  4470. $hostOrgId = Configure::read('MISP.host_org_id');
  4471. if (!empty($hostOrgId)) {
  4472. $hostOrg = $this->Org->find('first', [
  4473. 'recursive' => -1,
  4474. 'conditions' => [
  4475. 'id' => $hostOrgId
  4476. ]
  4477. ]
  4478. );
  4479. }
  4480. if (empty($hostOrg)) {
  4481. $hostOrg = $this->Org->find('first', [
  4482. 'recursive' => -1,
  4483. 'order' => ['id ASC']
  4484. ]
  4485. );
  4486. $hostOrgId = $hostOrg['Org']['id'];
  4487. }
  4488. $user = array('org_id' => $hostOrgId, 'Role' => array('perm_sync' => 0, 'perm_audit' => 0, 'perm_site_admin' => 0), 'Organisation' => $hostOrg['Org']);
  4489. if ($pubToZmq) {
  4490. $params = array('eventid' => $id);
  4491. if (Configure::read('Plugin.ZeroMQ_include_attachments')) {
  4492. $params['includeAttachments'] = 1;
  4493. }
  4494. $fullEvent = $this->fetchEvent($user, $params);
  4495. if (!empty($fullEvent)) {
  4496. $pubSubTool = $this->getPubSubTool();
  4497. $pubSubTool->publishEvent($fullEvent[0], 'publish');
  4498. }
  4499. }
  4500. if ($pubToKafka) {
  4501. $params = array('eventid' => $id);
  4502. if (Configure::read('Plugin.Kafka_include_attachments')) {
  4503. $params['includeAttachments'] = 1;
  4504. }
  4505. $fullEvent = $this->fetchEvent($user, $params);
  4506. if (!empty($fullEvent)) {
  4507. $kafkaPubTool = $this->getKafkaPubTool();
  4508. $kafkaPubTool->publishJson($kafkaTopic, $fullEvent[0], 'publish');
  4509. }
  4510. }
  4511. }
  4512. $uploaded = $this->uploadEventToServersRouter($id, $passAlong);
  4513. return $uploaded;
  4514. }
  4515. // Sends out an email to all people within the same org with the request to be contacted about a specific event.
  4516. public function sendContactEmailRouter($id, $message, $creator_only, $user)
  4517. {
  4518. if (Configure::read('MISP.background_jobs')) {
  4519. $job = ClassRegistry::init('Job');
  4520. $job->create();
  4521. $data = array(
  4522. 'worker' => 'email',
  4523. 'job_type' => 'contact_alert',
  4524. 'job_input' => 'Owner ' . ($creator_only ? 'user' : 'org') . ' of event #' . $id,
  4525. 'status' => 0,
  4526. 'retries' => 0,
  4527. 'org_id' => $user['org_id'],
  4528. 'message' => 'Contacting.',
  4529. );
  4530. $job->save($data);
  4531. $jobId = $job->id;
  4532. $process_id = CakeResque::enqueue(
  4533. 'email',
  4534. 'EventShell',
  4535. array('contactemail', $id, $message, $creator_only, $user['id'], $jobId),
  4536. true
  4537. );
  4538. $job->saveField('process_id', $process_id);
  4539. return true;
  4540. } else {
  4541. return $this->sendContactEmail($id, $message, $creator_only, array('User' => $user));
  4542. }
  4543. }
  4544. public function generateLocked()
  4545. {
  4546. $this->User = ClassRegistry::init('User');
  4547. $this->User->recursive = -1;
  4548. $localOrgs = array();
  4549. $conditions = array();
  4550. $orgs = $this->User->find('all', array('fields' => array('DISTINCT org_id')));
  4551. foreach ($orgs as $k => $org) {
  4552. $orgs[$k]['User']['count'] = $this->User->getOrgMemberCount($orgs[$k]['User']['org_id']);
  4553. if ($orgs[$k]['User']['count'] > 1) {
  4554. $localOrgs[] = $orgs[$k]['User']['org_id'];
  4555. $conditions['AND'][] = array('orgc !=' => $orgs[$k]['User']['org_id']);
  4556. } elseif ($orgs[$k]['User']['count'] == 1) {
  4557. // If we only have a single user for an org, check if that user is a sync user. If not, then it is a valid local org and the events created by him/her should be unlocked.
  4558. $this->User->recursive = 1;
  4559. $user = ($this->User->find('first', array(
  4560. 'fields' => array('id', 'role_id'),
  4561. 'conditions' => array('org_id' => $org['User']['org_id']),
  4562. 'contain' => array('Role' => array(
  4563. 'fields' => array('id', 'perm_sync'),
  4564. ))
  4565. )));
  4566. if (!$user['Role']['perm_sync']) {
  4567. $conditions['AND'][] = array('orgc !=' => $orgs[$k]['User']['org_id']);
  4568. }
  4569. }
  4570. }
  4571. // Don't lock stuff that's already locked
  4572. $conditions['AND'][] = array('locked !=' => true);
  4573. $this->recursive = -1;
  4574. $toBeUpdated = $this->find('count', array(
  4575. 'conditions' => $conditions
  4576. ));
  4577. $this->updateAll(
  4578. array('Event.locked' => 1),
  4579. $conditions
  4580. );
  4581. return $toBeUpdated;
  4582. }
  4583. public function reportValidationIssuesEvents()
  4584. {
  4585. $this->Behaviors->detach('Regexp');
  4586. // get all events..
  4587. $events = $this->find('all', array('recursive' => -1));
  4588. // for all events..
  4589. $result = array();
  4590. $k = 0;
  4591. $i = 0;
  4592. foreach ($events as $k => $event) {
  4593. $this->set($event);
  4594. if (!$this->validates()) {
  4595. $errors = $this->validationErrors;
  4596. $result[$i]['id'] = $event['Event']['id'];
  4597. $result[$i]['error'] = $errors;
  4598. $result[$i]['details'] = $event;
  4599. $i++;
  4600. }
  4601. }
  4602. return array($result, $k);
  4603. }
  4604. public function generateThreatLevelFromRisk()
  4605. {
  4606. $risk = array('Undefined' => 4, 'Low' => 3, 'Medium' => 2, 'High' => 1);
  4607. $events = $this->find('all', array('recursive' => -1));
  4608. $k = 0;
  4609. foreach ($events as $k => $event) {
  4610. if ($event['Event']['threat_level_id'] == 0 && isset($event['Event']['risk'])) {
  4611. $event['Event']['threat_level_id'] = $risk[$event['Event']['risk']];
  4612. $this->save($event);
  4613. }
  4614. }
  4615. return $k;
  4616. }
  4617. // check two version strings. If version 1 is older than 2, return -1, if they are the same return 0, if version 2 is older return 1
  4618. public function compareVersions($version1, $version2)
  4619. {
  4620. $version1Array = explode('.', $version1);
  4621. $version2Array = explode('.', $version2);
  4622. if ($version1Array[0] != $version2Array[0]) {
  4623. if ($version1Array[0] > $version2Array[0]) {
  4624. return 1;
  4625. } else {
  4626. return -1;
  4627. }
  4628. }
  4629. if ($version1Array[1] != $version2Array[1]) {
  4630. if ($version1Array[1] > $version2Array[1]) {
  4631. return 1;
  4632. } else {
  4633. return -1;
  4634. }
  4635. }
  4636. if ($version1Array[2] != $version2Array[2]) {
  4637. if ($version1Array[2] > $version2Array[2]) {
  4638. return 1;
  4639. } else {
  4640. return -1;
  4641. }
  4642. }
  4643. }
  4644. // main dispatch method for updating an incoming xmlArray - pass xmlArray to all of the appropriate transformation methods to make all the changes necessary to save the imported event
  4645. public function updateXMLArray($xmlArray, $response = true)
  4646. {
  4647. if (isset($xmlArray['xml_version']) && $response) {
  4648. $xmlArray['response']['xml_version'] = $xmlArray['xml_version'];
  4649. unset($xmlArray['xml_version']);
  4650. }
  4651. if (!$response) {
  4652. $xmlArray = array('response' => $xmlArray);
  4653. }
  4654. // if a version is set, it must be at least 2.2.0 - check the version and save the result of the comparison
  4655. if (isset($xmlArray['response']['xml_version'])) {
  4656. $version = $this->compareVersions($xmlArray['response']['xml_version'], $this->mispVersion);
  4657. }
  4658. // if no version is set, set the version to older (-1) manually
  4659. else {
  4660. $version = -1;
  4661. }
  4662. // same version, proceed normally
  4663. if ($version != 0) {
  4664. // The xml is from an instance that is newer than the local instance, let the user know that the admin needs to upgrade before it could be imported
  4665. if ($version == 1) {
  4666. throw new Exception('This XML file is from a MISP instance that is newer than the current instance. Please contact your administrator about upgrading this instance.');
  4667. }
  4668. // if the xml contains an event or events from an older MISP instance, let's try to upgrade it!
  4669. // Let's manually set the version to something below 2.2.0 if there is no version set in the xml
  4670. if (!isset($xmlArray['response']['xml_version'])) {
  4671. $xmlArray['response']['xml_version'] = '2.1.0';
  4672. }
  4673. // Upgrade from versions below 2.2.0 will need to replace the risk field with threat level id
  4674. if ($this->compareVersions($xmlArray['response']['xml_version'], '2.2.0') < 0) {
  4675. if ($response) {
  4676. $xmlArray['response'] = $this->__updateXMLArray220($xmlArray['response']);
  4677. } else {
  4678. $xmlArray = $this->__updateXMLArray220($xmlArray);
  4679. }
  4680. }
  4681. }
  4682. unset($xmlArray['response']['xml_version']);
  4683. if ($response) {
  4684. return $xmlArray;
  4685. } else {
  4686. return $xmlArray['response'];
  4687. }
  4688. }
  4689. // replaces the old risk value with the new threat level id
  4690. private function __updateXMLArray220($xmlArray)
  4691. {
  4692. $risk = array('Undefined' => 4, 'Low' => 3, 'Medium' => 2, 'High' => 1);
  4693. if (isset($xmlArray['Event'][0])) {
  4694. foreach ($xmlArray['Event'] as &$event) {
  4695. if (!isset($event['threat_level_id'])) {
  4696. $event['threat_level_id'] = $risk[$event['risk']];
  4697. }
  4698. }
  4699. } else {
  4700. if (!isset($xmlArray['Event']['threat_level_id']) && isset($xmlArray['Event']['risk'])) {
  4701. $xmlArray['Event']['threat_level_id'] = $risk[$xmlArray['Event']['risk']];
  4702. }
  4703. }
  4704. return $xmlArray;
  4705. }
  4706. public function checkIfNewer($incomingEvent)
  4707. {
  4708. $localEvent = $this->find('first', array('conditions' => array('uuid' => $incomingEvent['uuid']), 'recursive' => -1, 'fields' => array('Event.uuid', 'Event.timestamp')));
  4709. if (empty($localEvent) || $incomingEvent['timestamp'] > $localEvent['Event']['timestamp']) {
  4710. return true;
  4711. }
  4712. return false;
  4713. }
  4714. public function removeOlder(&$eventArray, $scope = 'events')
  4715. {
  4716. if ($scope === 'sightings' ) {
  4717. $field = 'sighting_timestamp';
  4718. } else {
  4719. $field = 'timestamp';
  4720. }
  4721. $uuidsToCheck = array();
  4722. foreach ($eventArray as $k => &$event) {
  4723. $uuidsToCheck[$event['uuid']] = $k;
  4724. }
  4725. $localEvents = array();
  4726. $temp = $this->find('all', array('recursive' => -1, 'fields' => array('Event.uuid', 'Event.' . $field, 'Event.locked')));
  4727. foreach ($temp as $e) {
  4728. $localEvents[$e['Event']['uuid']] = array($field => $e['Event'][$field], 'locked' => $e['Event']['locked']);
  4729. }
  4730. foreach ($uuidsToCheck as $uuid => $eventArrayId) {
  4731. // remove all events for the sighting sync if the remote is not aware of the new field yet
  4732. if (!isset($eventArray[$eventArrayId][$field])) {
  4733. unset($eventArray[$eventArrayId]);
  4734. } else {
  4735. if (isset($localEvents[$uuid])
  4736. && ($localEvents[$uuid][$field] >= $eventArray[$eventArrayId][$field]
  4737. || ($scope === 'events' && !$localEvents[$uuid]['locked'])))
  4738. {
  4739. unset($eventArray[$eventArrayId]);
  4740. }
  4741. }
  4742. }
  4743. }
  4744. public function sharingGroupRequired($field)
  4745. {
  4746. if ($this->data[$this->alias]['distribution'] == 4) {
  4747. return (!empty($field));
  4748. }
  4749. return true;
  4750. }
  4751. // convenience method to check whether a user can see an event
  4752. public function checkIfAuthorised($user, $id)
  4753. {
  4754. if (!isset($user['id'])) {
  4755. throw new MethodNotAllowedException('Invalid user.');
  4756. }
  4757. $this->id = $id;
  4758. if (!$this->exists()) {
  4759. return false;
  4760. }
  4761. if ($user['Role']['perm_site_admin']) {
  4762. return true;
  4763. }
  4764. $event = $this->find('first', array(
  4765. 'conditions' => array('id' => $id),
  4766. 'recursive' => -1,
  4767. 'fields' => array('id', 'sharing_group_id', 'distribution', 'org_id')
  4768. ));
  4769. if ($event['Event']['org_id'] == $user['org_id'] || ($event['Event']['distribution'] > 0 && $event['Event']['distribution'] < 4)) {
  4770. return true;
  4771. }
  4772. if ($event['Event']['distribution'] == 4 && $this->SharingGroup->checkIfAuthorised($user, $event['Event']['sharing_group_id'])) {
  4773. return true;
  4774. }
  4775. return false;
  4776. }
  4777. // expects a date string in the YYYY-MM-DD format
  4778. // returns the passed string or false if the format is invalid
  4779. // based on the fix provided by stevengoosensB
  4780. public function dateFieldCheck($date)
  4781. {
  4782. // regex check for from / to field by stevengoossensB
  4783. return (preg_match('/^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])$/', $date)) ? $date : false;
  4784. }
  4785. private function __prepareAttributeForView(
  4786. $attribute,
  4787. $correlatedAttributes,
  4788. $correlatedShadowAttributes,
  4789. $filterType = false,
  4790. $sightingsData
  4791. ) {
  4792. $attribute['objectType'] = 'attribute';
  4793. $include = true;
  4794. if ($filterType) {
  4795. /* proposal */
  4796. if ($filterType['proposal'] == 0) { // `both`
  4797. // pass, do not consider as `both` is selected
  4798. } else if (!empty($attribute['ShadowAttribute'])) { // `include only`
  4799. $include = $include && ($filterType['proposal'] == 1);
  4800. } else { // `exclude`
  4801. $include = $include && ($filterType['proposal'] == 2);
  4802. }
  4803. /* correlation */
  4804. if ($filterType['correlation'] == 0) { // `both`
  4805. // pass, do not consider as `both` is selected
  4806. } else if (in_array($attribute['id'], $correlatedAttributes)) { // `include only`
  4807. $include = $include && ($filterType['correlation'] == 1);
  4808. } else { // `exclude`
  4809. $include = $include && ($filterType['correlation'] == 2);
  4810. }
  4811. /* feed */
  4812. if ($filterType['feed'] == 0) { // `both`
  4813. // pass, do not consider as `both` is selected
  4814. } else if (!empty($attribute['Feed'])) { // `include only`
  4815. $include = $include && ($filterType['feed'] == 1);
  4816. } else { // `exclude`
  4817. $include = $include && ($filterType['feed'] == 2);
  4818. }
  4819. /* server */
  4820. if ($filterType['server'] == 0) { // `both`
  4821. // pass, do not consider as `both` is selected
  4822. } else if (!empty($attribute['Server'])) { // `include only`
  4823. $include = $include && ($filterType['server'] == 1);
  4824. } else { // `exclude`
  4825. $include = $include && ($filterType['server'] == 2);
  4826. }
  4827. /* sightings */
  4828. if ($filterType['sighting'] == 0) { // `both`
  4829. // pass, do not consider as `both` is selected
  4830. } else if (isset($sightingsData['data'][$attribute['id']])) { // `include only`
  4831. $include = $include && ($filterType['sighting'] == 1);
  4832. } else { // `exclude`
  4833. $include = $include && ($filterType['sighting'] == 2);
  4834. }
  4835. /* TypeGroupings */
  4836. if (
  4837. $filterType['attributeFilter'] != 'all'
  4838. && isset($this->Attribute->typeGroupings[$filterType['attributeFilter']])
  4839. && !in_array($attribute['type'], $this->Attribute->typeGroupings[$filterType['attributeFilter']])
  4840. ) {
  4841. $include = false;
  4842. }
  4843. if ($filterType['warning'] == 0) { // `both`
  4844. // pass, do not consider as `both` is selected
  4845. } else if (!empty($attribute['warnings']) || !empty($attribute['validationIssue'])) { // `include only`
  4846. $include = $include && ($filterType['warning'] == 1);
  4847. } else { // `exclude`
  4848. $include = $include && ($filterType['warning'] == 2);
  4849. }
  4850. }
  4851. if (!$include) {
  4852. return null;
  4853. }
  4854. if (!empty($attribute['ShadowAttribute'])) {
  4855. $temp = array();
  4856. foreach ($attribute['ShadowAttribute'] as $k => $proposal) {
  4857. $result = $this->__prepareProposalForView($proposal, $correlatedShadowAttributes, $filterType);
  4858. if ($result) {
  4859. $temp[] = $result;
  4860. }
  4861. }
  4862. $attribute['ShadowAttribute'] = $temp;
  4863. }
  4864. return $this->__prepareGenericForView($attribute);
  4865. }
  4866. private function __prepareProposalForView($proposal, $correlatedShadowAttributes, $filterType = false)
  4867. {
  4868. if ($proposal['proposal_to_delete']) {
  4869. $proposal['objectType'] = 'proposal_delete';
  4870. } else {
  4871. $proposal['objectType'] = 'proposal';
  4872. }
  4873. $include = true;
  4874. if ($filterType) {
  4875. $include = $filterType['proposal'] != 2;
  4876. /* correlation */
  4877. if ($filterType['correlation'] == 0) { // `both`
  4878. // pass, do not consider as `both` is selected
  4879. } else if (in_array($proposal['id'], $correlatedShadowAttributes)) { // `include only`
  4880. $include = $include && ($filterType['correlation'] == 1);
  4881. } else { // `exclude`
  4882. $include = $include && ($filterType['correlation'] == 2);
  4883. }
  4884. /* feed */
  4885. if ($filterType['feed'] == 0) { // `both`
  4886. // pass, do not consider as `both` is selected
  4887. } else if (!empty($proposal['Feed'])) { // `include only`
  4888. $include = $include && ($filterType['feed'] == 1);
  4889. } else { // `exclude`
  4890. $include = $include && ($filterType['feed'] == 2);
  4891. }
  4892. /* server */
  4893. if ($filterType['server'] == 0) { // `both`
  4894. // pass, do not consider as `both` is selected
  4895. } else if (!empty($attribute['Server'])) { // `include only`
  4896. $include = $include && ($filterType['server'] == 1);
  4897. } else { // `exclude`
  4898. $include = $include && ($filterType['server'] == 2);
  4899. }
  4900. /* TypeGroupings */
  4901. if (
  4902. $filterType['attributeFilter'] != 'all'
  4903. && isset($this->Attribute->typeGroupings[$filterType['attributeFilter']])
  4904. && !in_array($proposal['type'], $this->Attribute->typeGroupings[$filterType['attributeFilter']])
  4905. ) {
  4906. $include = false;
  4907. }
  4908. /* warning */
  4909. if ($filterType['warning'] == 0) { // `both`
  4910. // pass, do not consider as `both` is selected
  4911. } else if (!empty($proposal['warnings']) || !empty($proposal['validationIssue'])) { // `include only`
  4912. $include = $include && ($filterType['warning'] == 1);
  4913. } else { // `exclude`
  4914. $include = $include && ($filterType['warning'] == 2);
  4915. }
  4916. }
  4917. if (!$include) {
  4918. return null;
  4919. }
  4920. return $this->__prepareGenericForView($proposal);
  4921. }
  4922. private function __prepareObjectForView(
  4923. $object,
  4924. $correlatedAttributes,
  4925. $correlatedShadowAttributes,
  4926. $filterType = false,
  4927. $sightingsData
  4928. ) {
  4929. $object['category'] = $object['meta-category'];
  4930. $include = empty($filterType['attributeFilter']) || $filterType['attributeFilter'] == 'object' || $filterType['attributeFilter'] == 'all' || $object['meta-category'] === $filterType['attributeFilter'];
  4931. if (!$include) {
  4932. return null;
  4933. }
  4934. if (!empty($object['Attribute'])) {
  4935. $temp = array();
  4936. foreach ($object['Attribute'] as $attribute) {
  4937. $result = $this->__prepareAttributeForView(
  4938. $attribute,
  4939. $correlatedAttributes,
  4940. $correlatedShadowAttributes,
  4941. false,
  4942. $sightingsData
  4943. );
  4944. if ($result) {
  4945. $temp[] = $result;
  4946. }
  4947. }
  4948. $object['Attribute'] = $temp;
  4949. }
  4950. // filters depend on child objects
  4951. if (in_array($filterType['attributeFilter'], array('correlation', 'proposal', 'warning'))
  4952. || $filterType['correlation'] != 0
  4953. || $filterType['proposal'] != 0
  4954. || $filterType['warning'] != 0
  4955. || $filterType['sighting'] != 0
  4956. || $filterType['feed'] != 0
  4957. || $filterType['server'] != 0
  4958. ) {
  4959. $include = $this->__checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes, $sightingsData);
  4960. if (!$include) {
  4961. return null;
  4962. }
  4963. }
  4964. return $object;
  4965. }
  4966. private function __checkObjectByFilter($object, $filterType, $correlatedAttributes, $correlatedShadowAttributes, $sightingsData)
  4967. {
  4968. if (empty($object['Attribute'])) { // reject empty object
  4969. return false;
  4970. }
  4971. /* proposal */
  4972. if ($filterType['proposal'] == 0) { // `both`
  4973. // pass, do not consider as `both` is selected
  4974. } else if ($filterType['proposal'] == 1 || $filterType['proposal'] == 2) {
  4975. $flagKeep = false;
  4976. foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 proposal
  4977. if (!empty($attribute['ShadowAttribute'])) {
  4978. $flagKeep = ($filterType['proposal'] == 1); // keep if proposal are included
  4979. break;
  4980. }
  4981. }
  4982. if (!$flagKeep) {
  4983. return false;
  4984. }
  4985. }
  4986. /* warning */
  4987. if ($filterType['warning'] == 0) { // `both`
  4988. // pass, do not consider as `both` is selected
  4989. } else if ($filterType['warning'] == 1 || $filterType['warning'] == 2) {
  4990. $flagKeep = false;
  4991. foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 warning
  4992. if (!empty($attribute['warnings'])) {
  4993. $flagKeep = ($filterType['warning'] == 1); // keep if warnings are included
  4994. } else {
  4995. $flagKeep = ($filterType['warning'] == 2); // keep if warnings are excluded
  4996. }
  4997. if (!$flagKeep && !empty($attribute['ShadowAttribute'])) {
  4998. foreach ($attribute['ShadowAttribute'] as $shadowAttribute) {
  4999. if (!empty($shadowAttribute['warnings'])) {
  5000. $flagKeep = ($filterType['warning'] == 1); // do not keep if warning are excluded
  5001. break;
  5002. }
  5003. }
  5004. }
  5005. if ($flagKeep) {
  5006. break;
  5007. }
  5008. }
  5009. if (!$flagKeep) {
  5010. return false;
  5011. }
  5012. }
  5013. /* correlation */
  5014. if ($filterType['correlation'] == 0) { // `both`
  5015. // pass, do not consider as `both` is selected
  5016. } else if ($filterType['correlation'] == 1 || $filterType['correlation'] == 2) {
  5017. $flagKeep = false;
  5018. foreach ($object['Attribute'] as $attribute) { // check if object contains at least 1 warning
  5019. if (in_array($attribute['id'], $correlatedAttributes)) {
  5020. $flagKeep = ($filterType['correlation'] == 1); // keep if correlations are included
  5021. } else {
  5022. $flagKeep = ($filterType['correlation'] == 2); // keep if correlations are excluded
  5023. }
  5024. if (!$flagKeep && !empty($attribute['ShadowAttribute'])) {
  5025. foreach ($attribute['ShadowAttribute'] as $shadowAttribute) {
  5026. if (in_array($shadowAttribute['id'], $correlatedShadowAttributes)) {
  5027. $flagKeep = ($filterType['correlation'] == 1); // keep if correlations are included
  5028. break;
  5029. }
  5030. }
  5031. }
  5032. if ($flagKeep) {
  5033. break;
  5034. }
  5035. }
  5036. if (!$flagKeep) {
  5037. return false;
  5038. }
  5039. }
  5040. /* sighting */
  5041. if ($filterType['sighting'] == 0) { // `both`
  5042. // pass, do not consider as `both` is selected
  5043. } else if ($filterType['sighting'] == 1 || $filterType['sighting'] == 2) {
  5044. $flagKeep = false;
  5045. foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 warning
  5046. if (isset($sightingsData['data'][$attribute['id']])) {
  5047. $flagKeep = ($filterType['sighting'] == 1); // keep if server are included
  5048. } else {
  5049. $flagKeep = ($filterType['sighting'] == 2); // keep if server are excluded
  5050. }
  5051. if (!$flagKeep && !empty($attribute['ShadowAttribute'])) {
  5052. foreach ($attribute['ShadowAttribute'] as $shadowAttribute) {
  5053. if (isset($sightingsData['data'][$attribute['id']])) {
  5054. $flagKeep = ($filterType['sighting'] == 1); // do not keep if server are excluded
  5055. break;
  5056. }
  5057. }
  5058. }
  5059. if ($flagKeep) {
  5060. break;
  5061. }
  5062. }
  5063. if (!$flagKeep) {
  5064. return false;
  5065. }
  5066. }
  5067. /* feed */
  5068. if ($filterType['feed'] == 0) { // `both`
  5069. // pass, do not consider as `both` is selected
  5070. } else if ($filterType['feed'] == 1 || $filterType['feed'] == 2) {
  5071. $flagKeep = false;
  5072. foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 warning
  5073. if (!empty($attribute['Feed'])) {
  5074. $flagKeep = ($filterType['feed'] == 1); // keep if feed are included
  5075. } else {
  5076. $flagKeep = ($filterType['feed'] == 2); // keep if feed are excluded
  5077. }
  5078. if (!$flagKeep && !empty($attribute['ShadowAttribute'])) {
  5079. foreach ($attribute['ShadowAttribute'] as $shadowAttribute) {
  5080. if (!empty($shadowAttribute['Feed'])) {
  5081. $flagKeep = ($filterType['feed'] == 1); // do not keep if feed are excluded
  5082. break;
  5083. }
  5084. }
  5085. }
  5086. if ($flagKeep) {
  5087. break;
  5088. }
  5089. }
  5090. if (!$flagKeep) {
  5091. return false;
  5092. }
  5093. }
  5094. /* server */
  5095. if ($filterType['server'] == 0) { // `both`
  5096. // pass, do not consider as `both` is selected
  5097. } else if ($filterType['server'] == 1 || $filterType['server'] == 2) {
  5098. $flagKeep = false;
  5099. foreach ($object['Attribute'] as $k => $attribute) { // check if object contains at least 1 warning
  5100. if (!empty($attribute['Server'])) {
  5101. $flagKeep = ($filterType['server'] == 1); // keep if server are included
  5102. } else {
  5103. $flagKeep = ($filterType['server'] == 2); // keep if server are excluded
  5104. }
  5105. if (!$flagKeep && !empty($attribute['ShadowAttribute'])) {
  5106. foreach ($attribute['ShadowAttribute'] as $shadowAttribute) {
  5107. if (!empty($shadowAttribute['Server'])) {
  5108. $flagKeep = ($filterType['server'] == 1); // do not keep if server are excluded
  5109. break;
  5110. }
  5111. }
  5112. }
  5113. if ($flagKeep) {
  5114. break;
  5115. }
  5116. }
  5117. if (!$flagKeep) {
  5118. return false;
  5119. }
  5120. }
  5121. return true;
  5122. }
  5123. private function __prepareGenericForView($object)
  5124. {
  5125. if ($this->Attribute->isImage($object)) {
  5126. if (!empty($object['data'])) {
  5127. $object['image'] = $object['data'];
  5128. } else {
  5129. if (extension_loaded('gd')) {
  5130. // if extension is loaded, the data is not passed to the view because it is asynchronously fetched
  5131. $object['image'] = true; // tell the view that it is an image despite not having the actual data
  5132. } else {
  5133. if ($object['objectType'] === 'proposal') {
  5134. $object['image'] = $this->ShadowAttribute->base64EncodeAttachment($object);
  5135. } else {
  5136. $object['image'] = $this->Attribute->base64EncodeAttachment($object);
  5137. }
  5138. }
  5139. }
  5140. }
  5141. if ($object['type'] === 'attachment' && $this->loadAttachmentScan()->isEnabled()) {
  5142. $type = $object['objectType'] === 'attribute' ? AttachmentScan::TYPE_ATTRIBUTE : AttachmentScan::TYPE_SHADOW_ATTRIBUTE;
  5143. $object['infected'] = $this->loadAttachmentScan()->isInfected($type, $object['id']);;
  5144. }
  5145. return $object;
  5146. }
  5147. public function rearrangeEventForView(&$event, $passedArgs = array(), $all = false, $sightingsData=array())
  5148. {
  5149. foreach ($event['Event'] as $k => $v) {
  5150. if (is_array($v)) {
  5151. $event[$k] = $v;
  5152. unset($event['Event'][$k]);
  5153. }
  5154. }
  5155. $filterType = array(
  5156. 'attributeFilter' => isset($passedArgs['attributeFilter']) ? $passedArgs['attributeFilter'] : 'all',
  5157. 'proposal' => isset($passedArgs['proposal']) ? $passedArgs['proposal'] : 0,
  5158. 'correlation' => isset($passedArgs['correlation']) ? $passedArgs['correlation'] : 0,
  5159. 'warning' => isset($passedArgs['warning']) ? $passedArgs['warning'] : 0,
  5160. 'deleted' => isset($passedArgs['deleted']) ? $passedArgs['deleted'] : 0,
  5161. 'toIDS' => isset($passedArgs['toIDS']) ? $passedArgs['toIDS'] : 0,
  5162. 'sighting' => isset($passedArgs['sighting']) ? $passedArgs['sighting'] : 0,
  5163. 'feed' => isset($passedArgs['feed']) ? $passedArgs['feed'] : 0,
  5164. 'server' => isset($passedArgs['server']) ? $passedArgs['server'] : 0
  5165. );
  5166. // update proposal, correlation and warning accordingly
  5167. if (in_array($filterType['attributeFilter'], array('proposal', 'correlation', 'warning'))) {
  5168. $filterType[$filterType['attributeFilter']] = 1;
  5169. }
  5170. $correlatedAttributes = isset($event['RelatedAttribute']) ? array_keys($event['RelatedAttribute']) : array();
  5171. $correlatedShadowAttributes = isset($event['RelatedShadowAttribute']) ? array_keys($event['RelatedShadowAttribute']) : array();
  5172. $objects = array();
  5173. if (isset($event['Attribute'])) {
  5174. foreach ($event['Attribute'] as $attribute) {
  5175. $result = $this->__prepareAttributeForView(
  5176. $attribute,
  5177. $correlatedAttributes,
  5178. $correlatedShadowAttributes,
  5179. $filterType,
  5180. $sightingsData
  5181. );
  5182. if ($result) {
  5183. $objects[] = $result;
  5184. }
  5185. }
  5186. unset($event['Attribute']);
  5187. }
  5188. if (isset($event['ShadowAttribute'])) {
  5189. foreach ($event['ShadowAttribute'] as $proposal) {
  5190. $result = $this->__prepareProposalForView(
  5191. $proposal,
  5192. $correlatedShadowAttributes,
  5193. $filterType
  5194. );
  5195. if ($result) {
  5196. $objects[] = $result;
  5197. }
  5198. }
  5199. unset($event['ShadowAttribute']);
  5200. }
  5201. if (isset($event['Object'])) {
  5202. foreach ($event['Object'] as $object) {
  5203. $object['objectType'] = 'object';
  5204. $result = $this->__prepareObjectForView(
  5205. $object,
  5206. $correlatedAttributes,
  5207. $correlatedShadowAttributes,
  5208. $filterType,
  5209. $sightingsData
  5210. );
  5211. if ($result) {
  5212. $objects[] = $result;
  5213. }
  5214. }
  5215. unset($event['Object']);
  5216. }
  5217. $event['objects'] = $objects;
  5218. $referencedByArray = array();
  5219. foreach ($event['objects'] as $object) {
  5220. if (!in_array($object['objectType'], array('attribute', 'object'))) {
  5221. continue;
  5222. }
  5223. if (!empty($object['ObjectReference'])) {
  5224. foreach ($object['ObjectReference'] as $reference) {
  5225. if (isset($reference['referenced_uuid'])) {
  5226. $referencedByArray[$reference['referenced_uuid']][$object['objectType']][] = array(
  5227. 'meta-category' => $object['meta-category'],
  5228. 'name' => $object['name'],
  5229. 'uuid' => $object['uuid'],
  5230. 'id' => isset($object['id']) ? $object['id'] : 0,
  5231. 'object_type' => $object['objectType'],
  5232. 'relationship_type' => $reference['relationship_type']
  5233. );
  5234. }
  5235. }
  5236. }
  5237. }
  5238. App::uses('CustomPaginationTool', 'Tools');
  5239. $customPagination = new CustomPaginationTool();
  5240. if ($all) {
  5241. $passedArgs['page'] = 0;
  5242. }
  5243. $params = $customPagination->applyRulesOnArray($event['objects'], $passedArgs, 'events', 'category');
  5244. foreach ($event['objects'] as $k => $object) {
  5245. if (isset($referencedByArray[$object['uuid']])) {
  5246. foreach ($referencedByArray[$object['uuid']] as $objectType => $references) {
  5247. $event['objects'][$k]['referenced_by'][$objectType] = $references;
  5248. }
  5249. }
  5250. }
  5251. $params['total_elements'] = count($event['objects']);
  5252. return $params;
  5253. }
  5254. // pass along a json from the server filter rules
  5255. // returns a conditions set to be merged into pagination / event fetch / etc
  5256. public function filterRulesToConditions($rules)
  5257. {
  5258. $rules = json_decode($rules, true);
  5259. $operators = array('OR', 'NOT');
  5260. foreach ($operators as $op) {
  5261. if (!empty($rules['tags'][$op])) {
  5262. $event_ids = $this->EventTag->find('list', array(
  5263. 'recursive' => -1,
  5264. 'conditions' => array('EventTag.tag_id' => $rules['tags'][$op]),
  5265. 'fields' => array('EventTag.event_id')
  5266. ));
  5267. $rules['events'][$op] = $event_ids;
  5268. }
  5269. }
  5270. $conditions = array();
  5271. $fields = array('events' => 'Event.id', 'orgs' => 'Event.orgc_id');
  5272. foreach ($fields as $k => $field) {
  5273. $temp = array();
  5274. if (!empty($rules[$k]['OR'])) {
  5275. $temp['OR'][$field] = $rules[$k]['OR'];
  5276. }
  5277. if (!empty($rules[$k]['NOT'])) {
  5278. $temp['AND'][$field . ' !='] = $rules[$k]['NOT'];
  5279. }
  5280. $conditions['AND'][] = $temp;
  5281. }
  5282. return $conditions;
  5283. }
  5284. public function fetchInitialObject($event_id, $object_id)
  5285. {
  5286. $initial_object = $this->Object->find('first', array(
  5287. 'conditions' => array('Object.id' => $object_id,
  5288. 'Object.event_id' => $event_id,
  5289. 'Object.deleted' => 0),
  5290. 'recursive' => -1,
  5291. 'fields' => array('Object.id', 'Object.uuid', 'Object.name')
  5292. ));
  5293. if (!empty($initial_object)) {
  5294. $initial_attributes = $this->Attribute->find('all', array(
  5295. 'conditions' => array('Attribute.object_id' => $object_id,
  5296. 'Attribute.deleted' => 0),
  5297. 'recursive' => -1,
  5298. 'fields' => array('Attribute.id', 'Attribute.uuid', 'Attribute.type',
  5299. 'Attribute.object_relation', 'Attribute.value')
  5300. ));
  5301. if (!empty($initial_attributes)) {
  5302. $initial_object['Attribute'] = array();
  5303. foreach ($initial_attributes as $initial_attribute) {
  5304. array_push($initial_object['Attribute'], $initial_attribute['Attribute']);
  5305. }
  5306. }
  5307. $initial_references = $this->Object->ObjectReference->find('all', array(
  5308. 'conditions' => array('ObjectReference.object_id' => $object_id,
  5309. 'ObjectReference.event_id' => $event_id,
  5310. 'ObjectReference.deleted' => 0),
  5311. 'recursive' => -1,
  5312. 'fields' => array('ObjectReference.referenced_uuid', 'ObjectReference.relationship_type')
  5313. ));
  5314. if (!empty($initial_references)) {
  5315. $initial_object['ObjectReference'] = array();
  5316. foreach ($initial_references as $initial_reference) {
  5317. array_push($initial_object['ObjectReference'], $initial_reference['ObjectReference']);
  5318. }
  5319. }
  5320. }
  5321. return $initial_object;
  5322. }
  5323. public function handleModuleResult($result, $event_id)
  5324. {
  5325. $resultArray = array();
  5326. $freetextResults = array();
  5327. App::uses('ComplexTypeTool', 'Tools');
  5328. $complexTypeTool = new ComplexTypeTool();
  5329. if (isset($result['results']) && !empty($result['results'])) {
  5330. foreach ($result['results'] as $k => &$r) {
  5331. if (!is_array($r['values'])) {
  5332. $r['values'] = array($r['values']);
  5333. }
  5334. if (!isset($r['types']) && isset($r['type'])) {
  5335. $r['types'] = array($r['type']);
  5336. }
  5337. if (!is_array($r['types'])) {
  5338. $r['types'] = array($r['types']);
  5339. }
  5340. if (isset($r['categories']) && !is_array($r['categories'])) {
  5341. $r['categories'] = array($r['categories']);
  5342. }
  5343. if (isset($r['tags']) && !is_array($r['tags'])) {
  5344. $r['tags'] = array($r['tags']);
  5345. }
  5346. foreach ($r['values'] as &$value) {
  5347. if (!is_array($r['values']) || !isset($r['values'][0])) {
  5348. $r['values'] = array($r['values']);
  5349. }
  5350. }
  5351. foreach ($r['values'] as $valueKey => &$value) {
  5352. if (empty($value)) {
  5353. unset($r['values'][$valueKey]);
  5354. continue;
  5355. }
  5356. if (in_array('freetext', $r['types'])) {
  5357. if (is_array($value)) {
  5358. $value = json_encode($value);
  5359. }
  5360. $this->Warninglist = ClassRegistry::init('Warninglist');
  5361. $complexTypeTool->setTLDs($this->Warninglist->fetchTLDLists());
  5362. $freetextResults = array_merge($freetextResults, $complexTypeTool->checkComplexRouter($value, 'FreeText'));
  5363. if (!empty($freetextResults)) {
  5364. foreach ($freetextResults as &$ft) {
  5365. $temp = array();
  5366. foreach ($ft['types'] as $type) {
  5367. $temp[$type] = $type;
  5368. }
  5369. $ft['event_id'] = $event_id;
  5370. $ft['types'] = $temp;
  5371. $ft['comment'] = isset($r['comment']) ? $r['comment'] : false;
  5372. }
  5373. }
  5374. $r['types'] = array_diff($r['types'], array('freetext'));
  5375. // if we just removed the only type in the result then more on to the next result
  5376. if (empty($r['types'])) {
  5377. continue 2;
  5378. }
  5379. $r['types'] = array_values($r['types']);
  5380. }
  5381. }
  5382. foreach ($r['values'] as &$value) {
  5383. $temp = array(
  5384. 'event_id' => $event_id,
  5385. 'types' => $r['types'],
  5386. 'default_type' => $r['types'][0],
  5387. 'comment' => isset($r['comment']) ? $r['comment'] : false,
  5388. 'to_ids' => isset($r['to_ids']) ? $r['to_ids'] : false,
  5389. 'value' => $value,
  5390. 'tags' => isset($r['tags']) ? $r['tags'] : false
  5391. );
  5392. if (isset($r['categories'])) {
  5393. $temp['categories'] = $r['categories'];
  5394. $temp['default_category'] = $r['categories'][0];
  5395. }
  5396. if (isset($r['data'])) {
  5397. $temp['data'] = $r['data'];
  5398. }
  5399. if (isset($r['distribution'])) {
  5400. $temp['distribution'] = $r['distribution'];
  5401. }
  5402. // if data_is_handled is set then MISP assumes that the sample is already zipped and encrypted
  5403. // in this case it will not try to do this by itself - however it also won't create additional hashes
  5404. if (isset($r['data_is_handled'])) {
  5405. $temp['data_is_handled'] = $r['data_is_handled'];
  5406. }
  5407. $resultArray[] = $temp;
  5408. }
  5409. }
  5410. $resultArray = array_merge($resultArray, $freetextResults);
  5411. }
  5412. return $resultArray;
  5413. }
  5414. public function handleMispFormatFromModuleResult(&$result)
  5415. {
  5416. $defaultDistribution = 5;
  5417. if (!empty(Configure::read('MISP.default_attribute_distribution'))) {
  5418. $defaultDistribution = Configure::read('MISP.default_attribute_distribution');
  5419. if ($defaultDistribution == 'event') {
  5420. $defaultDistribution = 5;
  5421. }
  5422. }
  5423. $event = array();
  5424. if (!empty($result['results']['Attribute'])) {
  5425. $attributes = array();
  5426. foreach ($result['results']['Attribute'] as &$tmp_attribute) {
  5427. $tmp_attribute = $this->__fillAttribute($tmp_attribute, $defaultDistribution);
  5428. $attributes[] = $tmp_attribute;
  5429. }
  5430. $event['Attribute'] = $attributes;
  5431. }
  5432. if (!empty($result['results']['Object'])) {
  5433. $object = array();
  5434. foreach ($result['results']['Object'] as $tmp_object) {
  5435. $tmp_object['distribution'] = (isset($tmp_object['distribution']) ? (int)$tmp_object['distribution'] : $defaultDistribution);
  5436. $tmp_object['sharing_group_id'] = (isset($tmp_object['sharing_group_id']) ? (int)$tmp_object['sharing_group_id'] : 0);
  5437. if (!empty($tmp_object['Attribute'])) {
  5438. foreach ($tmp_object['Attribute'] as &$tmp_attribute) {
  5439. $tmp_attribute = $this->__fillAttribute($tmp_attribute, $defaultDistribution);
  5440. }
  5441. }
  5442. $objects[] = $tmp_object;
  5443. }
  5444. $event['Object'] = $objects;
  5445. }
  5446. foreach (array('Tag', 'Galaxy') as $field) {
  5447. if (!empty($result['results'][$field])) {
  5448. $event[$field] = $result['results'][$field];
  5449. }
  5450. }
  5451. return $event;
  5452. }
  5453. private function __fillAttribute($attribute, $defaultDistribution)
  5454. {
  5455. if (is_array($attribute['type'])) {
  5456. $attribute_type = $attribute['type'][0];
  5457. if (empty($attribute['category'])) {
  5458. $categories = array();
  5459. foreach ($attribute['type'] as $type) {
  5460. $category = $this->Attribute->typeDefinitions[$type]['default_category'];
  5461. if (!in_array($category, $categories)) {
  5462. $categories[] = $category;
  5463. }
  5464. }
  5465. $attribute['category'] = count($categories) === 1 ? $categories[0] : $categories;
  5466. }
  5467. } else {
  5468. $attribute_type = $attribute['type'];
  5469. if (empty($attribute['category'])) {
  5470. $attribute['category'] = $this->Attribute->typedefinitions[$attribute_type]['default_category'];
  5471. }
  5472. }
  5473. if (!isset($attribute['to_ids'])) {
  5474. $attribute['to_ids'] = $this->Attribute->typeDefinitions[$attribute_type]['to_ids'];
  5475. }
  5476. $attribute['value'] = $this->Attribute->runRegexp($attribute['type'], $attribute['value']);
  5477. $attribute['distribution'] = (isset($attribute['distribution']) ? (int)$attribute['distribution'] : $defaultDistribution);
  5478. $attribute['sharing_group_id'] = (isset($attribute['sharing_group_id']) ? (int)$attribute['sharing_group_id'] : 0);
  5479. return $attribute;
  5480. }
  5481. public function export($user = false, $module = false, $options = array())
  5482. {
  5483. if (empty($user)) {
  5484. return 'Invalid user.';
  5485. }
  5486. if (empty($module)) {
  5487. return 'Invalid module.';
  5488. }
  5489. $this->Module = ClassRegistry::init('Module');
  5490. $module = $this->Module->getEnabledModule($module, 'Export');
  5491. $events = $this->fetchEvent($user, $options);
  5492. if (empty($events)) {
  5493. return 'Invalid event.';
  5494. }
  5495. $standard_format = false;
  5496. $modulePayload = array('module' => $module['name']);
  5497. if (!empty($module['meta']['require_standard_format'])) {
  5498. $standard_format = true;
  5499. }
  5500. if (isset($module['meta']['config'])) {
  5501. foreach ($module['meta']['config'] as $conf) {
  5502. $modulePayload['config'][$conf] = Configure::read('Plugin.Export_' . $module['name'] . '_' . $conf);
  5503. }
  5504. }
  5505. if ($standard_format) {
  5506. App::uses('JSONConverterTool', 'Tools');
  5507. $converter = new JSONConverterTool();
  5508. foreach ($events as $k => $event) {
  5509. $events[$k] = $converter->convert($event, false, true);
  5510. }
  5511. }
  5512. $modulePayload['data'] = $events;
  5513. $result = $this->Module->queryModuleServer($modulePayload, false, 'Export');
  5514. return array(
  5515. 'data' => $result['data'],
  5516. 'extension' => $module['mispattributes']['outputFileExtension'],
  5517. 'response' => $module['mispattributes']['responseType']
  5518. );
  5519. }
  5520. public function getSightingData(array $event)
  5521. {
  5522. if (empty($event['Sighting'])) {
  5523. return ['data' => [], 'csv' => []];
  5524. }
  5525. $this->Sighting = ClassRegistry::init('Sighting');
  5526. $sightingsData = array();
  5527. $sparklineData = array();
  5528. $startDates = array();
  5529. $range = $this->Sighting->getMaximumRange();
  5530. foreach ($event['Sighting'] as $sighting) {
  5531. $type = $this->Sighting->type[$sighting['type']];
  5532. if (!isset($sightingsData[$sighting['attribute_id']][$type])) {
  5533. $sightingsData[$sighting['attribute_id']][$type] = array('count' => 0);
  5534. }
  5535. $sightingsData[$sighting['attribute_id']][$type]['count']++;
  5536. $orgName = isset($sighting['Organisation']['name']) ? $sighting['Organisation']['name'] : 'Others';
  5537. if (!isset($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName])) {
  5538. $sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName] = array('count' => 1, 'date' => $sighting['date_sighting']);
  5539. } else {
  5540. $sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['count']++;
  5541. if ($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] < $sighting['date_sighting']) {
  5542. $sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] = $sighting['date_sighting'];
  5543. }
  5544. }
  5545. if ($sighting['type'] !== '0') {
  5546. continue;
  5547. }
  5548. if (!isset($startDates[$sighting['attribute_id']]) || $startDates[$sighting['attribute_id']] > $sighting['date_sighting']) {
  5549. if ($sighting['date_sighting'] >= $range) {
  5550. $startDates[$sighting['attribute_id']] = $sighting['date_sighting'];
  5551. }
  5552. }
  5553. if (!isset($startDates['event']) || $startDates['event'] > $sighting['date_sighting']) {
  5554. if ($sighting['date_sighting'] >= $range) {
  5555. $startDates['event'] = $sighting['date_sighting'];
  5556. }
  5557. }
  5558. $date = date("Y-m-d", $sighting['date_sighting']);
  5559. if (!isset($sparklineData[$sighting['attribute_id']][$date])) {
  5560. $sparklineData[$sighting['attribute_id']][$date] = 1;
  5561. } else {
  5562. $sparklineData[$sighting['attribute_id']][$date]++;
  5563. }
  5564. if (!isset($sparklineData['event'][$date])) {
  5565. $sparklineData['event'][$date] = 1;
  5566. } else {
  5567. $sparklineData['event'][$date]++;
  5568. }
  5569. }
  5570. $csv = array();
  5571. $today = strtotime(date('Y-m-d', time()));
  5572. foreach ($startDates as $k => $v) {
  5573. $startDates[$k] = date('Y-m-d', $v);
  5574. }
  5575. foreach ($sparklineData as $aid => $data) {
  5576. if (!isset($startDates[$aid])) {
  5577. continue;
  5578. }
  5579. $startDate = $startDates[$aid];
  5580. if (strtotime($startDate) < $range) {
  5581. $startDate = date('Y-m-d');
  5582. }
  5583. $startDate = date('Y-m-d', strtotime("-3 days", strtotime($startDate)));
  5584. $sighting = $data;
  5585. $csv[$aid] = 'Date,Close\n';
  5586. for ($date = $startDate; strtotime($date) <= $today; $date = date('Y-m-d', strtotime("+1 day", strtotime($date)))) {
  5587. if (isset($sighting[$date])) {
  5588. $csv[$aid] .= $date . ',' . $sighting[$date] . '\n';
  5589. } else {
  5590. $csv[$aid] .= $date . ',0\n';
  5591. }
  5592. }
  5593. }
  5594. return array(
  5595. 'data' => $sightingsData,
  5596. 'csv' => $csv
  5597. );
  5598. }
  5599. public function cacheSgids($user, $useCache = false)
  5600. {
  5601. if ($useCache && isset($this->assetCache['sgids'])) {
  5602. return $this->assetCache['sgids'];
  5603. } else {
  5604. $sgids = $this->SharingGroup->fetchAllAuthorised($user);
  5605. if (empty($sgids)) {
  5606. $sgids = array(-1);
  5607. }
  5608. if ($useCache) {
  5609. $this->assetCache['sgids'] = $sgids;
  5610. }
  5611. return $sgids;
  5612. }
  5613. }
  5614. private function __cacheSharingGroupData($user, $useCache = false)
  5615. {
  5616. if ($useCache && isset($this->assetCache['sharingGroupData'])) {
  5617. return $this->assetCache['sharingGroupData'];
  5618. } else {
  5619. $sharingGroupDataTemp = $this->SharingGroup->fetchAllAuthorised($user, 'simplified');
  5620. $sharingGroupData = array();
  5621. foreach ($sharingGroupDataTemp as $k => $v) {
  5622. if (isset($v['Organisation'])) {
  5623. $v['SharingGroup']['Organisation'] = $v['Organisation'];
  5624. }
  5625. if (isset($v['SharingGroupOrg'])) {
  5626. $v['SharingGroup']['SharingGroupOrg'] = $v['SharingGroupOrg'];
  5627. }
  5628. if (isset($v['SharingGroupServer'])) {
  5629. $v['SharingGroup']['SharingGroupServer'] = $v['SharingGroupServer'];
  5630. foreach ($v['SharingGroup']['SharingGroupServer'] as &$sgs) {
  5631. if ($sgs['server_id'] == 0) {
  5632. $sgs['Server'] = array(
  5633. 'id' => '0',
  5634. 'url' => $this->__getAnnounceBaseurl(),
  5635. 'name' => $this->__getAnnounceBaseurl()
  5636. );
  5637. }
  5638. }
  5639. }
  5640. $sharingGroupData[$v['SharingGroup']['id']] = array('SharingGroup' => $v['SharingGroup']);
  5641. }
  5642. if ($useCache) {
  5643. $this->assetCache['sharingGroupData'] = $sharingGroupData;
  5644. }
  5645. return $sharingGroupData;
  5646. }
  5647. }
  5648. private function __cachedelegatedEventIDs($user, $useCache = false)
  5649. {
  5650. if ($useCache && isset($this->assetCache['delegatedEventIDs'])) {
  5651. return $this->assetCache['delegatedEventIDs'];
  5652. } else {
  5653. $this->EventDelegation = ClassRegistry::init('EventDelegation');
  5654. $delegatedEventIDs = $this->EventDelegation->find('list', array(
  5655. 'conditions' => array('EventDelegation.org_id' => $user['org_id']),
  5656. 'fields' => array('event_id')
  5657. ));
  5658. if ($useCache) {
  5659. $this->assetCache['delegationEventIDs'] = $delegatedEventIDs;
  5660. }
  5661. return $delegatedEventIDs;
  5662. }
  5663. }
  5664. private function __generateCachedTagFilters($tagRules, $useCache = false)
  5665. {
  5666. if ($useCache && isset($this->assetCache['tagFilters'])) {
  5667. return $this->assetCache['tagFilters'];
  5668. } else {
  5669. $filters = array();
  5670. $args = $this->Attribute->dissectArgs($tagRules);
  5671. $tagArray = $this->EventTag->Tag->fetchEventTagIds($args[0], $args[1]);
  5672. if (!empty($tagArray[0])) {
  5673. $filters[] = ['OR' => ['Event.id' => $tagArray[0]]];
  5674. } else {
  5675. $filters[] = ['AND' => ['Event.id NOT IN' => $tagArray[1]]];
  5676. }
  5677. if ($useCache) {
  5678. $this->assetCache['tagFilters'] = $filters;
  5679. }
  5680. return $filters;
  5681. }
  5682. }
  5683. public function unpublishEvent($id, $proposalLock = false)
  5684. {
  5685. $event = $this->find('first', array(
  5686. 'recursive' => -1,
  5687. 'conditions' => array('Event.id' => $id)
  5688. ));
  5689. if (empty($event)) {
  5690. return false;
  5691. }
  5692. $event['Event']['published'] = 0;
  5693. $date = new DateTime();
  5694. $event['Event']['timestamp'] = $date->getTimestamp();
  5695. if ($proposalLock) {
  5696. $event['Event']['proposal_email_lock'] = 0;
  5697. }
  5698. $event['Event']['unpublishAction'] = true;
  5699. return $this->save($event);
  5700. }
  5701. public function upload_stix($user, $scriptDir, $filename, $stix_version, $original_file, $publish)
  5702. {
  5703. App::uses('Folder', 'Utility');
  5704. App::uses('File', 'Utility');
  5705. $tempFilePath = $scriptDir . DS . 'tmp' . DS . $filename;
  5706. if ($stix_version == '2') {
  5707. $scriptFile = $scriptDir . DS . 'stix2' . DS . 'stix2misp.py';
  5708. $shell_command = $this->getPythonVersion() . ' ' . $scriptFile . ' ' . $tempFilePath;
  5709. $output_path = $tempFilePath . '.stix2';
  5710. $stix_version = "STIX 2.0";
  5711. } elseif ($stix_version == '1' || $stix_version == '1.1' || $stix_version == '1.2') {
  5712. $scriptFile = $scriptDir . DS . 'stix2misp.py';
  5713. $shell_command = $this->getPythonVersion() . ' ' . $scriptFile . ' ' . $filename;
  5714. $output_path = $tempFilePath . '.json';
  5715. $stix_version = "STIX 1.1";
  5716. } else {
  5717. throw new MethodNotAllowedException('Invalid STIX version');
  5718. }
  5719. $shell_command .= ' ' . escapeshellarg(Configure::read('MISP.default_event_distribution')) . ' ' . escapeshellarg(Configure::read('MISP.default_attribute_distribution'));
  5720. $synonymsToTagNames = $this->__getTagNamesFromSynonyms($scriptDir);
  5721. if ($synonymsToTagNames) {
  5722. $shell_command .= ' ' . $synonymsToTagNames;
  5723. }
  5724. $shell_command .= ' 2>' . APP . 'tmp/logs/exec-errors.log';
  5725. $result = shell_exec($shell_command);
  5726. $result = preg_split("/\r\n|\n|\r/", trim($result));
  5727. $result = end($result);
  5728. $tempFile = file_get_contents($tempFilePath);
  5729. unlink($tempFilePath);
  5730. if (trim($result) == '1') {
  5731. $data = file_get_contents($output_path);
  5732. $data = json_decode($data, true);
  5733. if (empty($data['Event'])) {
  5734. $data = array('Event' => $data);
  5735. }
  5736. unlink($output_path);
  5737. $created_id = false;
  5738. $validationIssues = false;
  5739. $result = $this->_add($data, true, $user, '', null, false, null, $created_id, $validationIssues);
  5740. if ($result) {
  5741. if ($original_file && !is_numeric($result)) {
  5742. $this->add_original_file($tempFile, $original_file, $created_id, $stix_version);
  5743. }
  5744. if ($publish && $user['Role']['perm_publish']) {
  5745. $this->publish($this->getID(), null);
  5746. }
  5747. return $created_id;
  5748. }
  5749. return $validationIssues;
  5750. } else {
  5751. if (trim($result) == '2') {
  5752. $response = __('Issues while loading the stix file. ');
  5753. } elseif (trim($result) == '3') {
  5754. $response = __('Issues with the maec library. ');
  5755. } else {
  5756. $response = __('Issues executing the ingestion script or invalid input. ');
  5757. }
  5758. if (!$user['Role']['perm_site_admin']) {
  5759. $response .= __('Please ask your administrator to ');
  5760. } else {
  5761. $response .= __('Please ');
  5762. }
  5763. $response .= ' ' . __('check whether the dependencies for STIX are met via the diagnostic tool.');
  5764. return $response;
  5765. }
  5766. }
  5767. private function __getTagNamesFromSynonyms($scriptDir)
  5768. {
  5769. $synonymsToTagNames = $scriptDir . DS . 'tmp' . DS . 'synonymsToTagNames.json';
  5770. if (!file_exists($synonymsToTagNames) || (time() - filemtime($synonymsToTagNames)) > 600) {
  5771. if (empty($this->GalaxyCluster)) {
  5772. $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
  5773. }
  5774. $clusters = $this->GalaxyCluster->find('all', array(
  5775. 'recursive' => -1,
  5776. 'fields' => array(
  5777. 'GalaxyCluster.value',
  5778. 'MAX(GalaxyCluster.version)',
  5779. 'GalaxyCluster.tag_name',
  5780. 'GalaxyCluster.id'
  5781. ),
  5782. 'group' => array('GalaxyCluster.tag_name')
  5783. ));
  5784. $synonyms = $this->GalaxyCluster->GalaxyElement->find('all', array(
  5785. 'recursive' => -1,
  5786. 'fields' => array('galaxy_cluster_id', 'value'),
  5787. 'conditions' => array('key' => 'synonyms')
  5788. ));
  5789. $idToSynonyms = array();
  5790. foreach($synonyms as $synonym) {
  5791. $idToSynonyms[$synonym['GalaxyElement']['galaxy_cluster_id']][] = $synonym['GalaxyElement']['value'];
  5792. }
  5793. $mapping = array();
  5794. foreach($clusters as $cluster) {
  5795. $mapping[$cluster['GalaxyCluster']['value']][] = $cluster['GalaxyCluster']['tag_name'];
  5796. if (!empty($idToSynonyms[$cluster['GalaxyCluster']['id']])) {
  5797. foreach($idToSynonyms[$cluster['GalaxyCluster']['id']] as $synonym) {
  5798. $mapping[$synonym][] = $cluster['GalaxyCluster']['tag_name'];
  5799. }
  5800. }
  5801. }
  5802. $file = new File($synonymsToTagNames, true, 0644);
  5803. $file->write(json_encode($mapping));
  5804. $file->close();
  5805. }
  5806. return $synonymsToTagNames;
  5807. }
  5808. public function enrichmentRouter($options)
  5809. {
  5810. if (Configure::read('MISP.background_jobs')) {
  5811. $job = ClassRegistry::init('Job');
  5812. $job->create();
  5813. $data = array(
  5814. 'worker' => $this->__getPrioWorkerIfPossible(),
  5815. 'job_type' => 'enrichment',
  5816. 'job_input' => 'Event ID: ' . $options['event_id'] . ' modules: ' . json_encode($options['modules']),
  5817. 'status' => 0,
  5818. 'retries' => 0,
  5819. 'org_id' => $options['user']['org_id'],
  5820. 'org' => $options['user']['Organisation']['name'],
  5821. 'message' => 'Enriching event.',
  5822. );
  5823. $job->save($data);
  5824. $jobId = $job->id;
  5825. $process_id = CakeResque::enqueue(
  5826. 'prio',
  5827. 'EventShell',
  5828. array('enrichment', $options['user']['id'], $options['event_id'], json_encode($options['modules']), $jobId),
  5829. true
  5830. );
  5831. $job->saveField('process_id', $process_id);
  5832. return true;
  5833. } else {
  5834. $result = $this->enrichment($options);
  5835. return __('#' . $result . ' attributes have been created during the enrichment process.');
  5836. }
  5837. }
  5838. public function enrichment($params)
  5839. {
  5840. $option_fields = array('user', 'event_id', 'modules');
  5841. foreach ($option_fields as $option_field) {
  5842. if (empty($params[$option_field])) {
  5843. throw new MethodNotAllowedException(__('%s not set', $option_field));
  5844. }
  5845. }
  5846. $event = $this->fetchEvent($params['user'], array('eventid' => $params['event_id'], 'includeAttachments' => 1, 'flatten' => 1));
  5847. $this->Module = ClassRegistry::init('Module');
  5848. $enabledModules = $this->Module->getEnabledModules($params['user']);
  5849. if (empty($enabledModules)) {
  5850. return true;
  5851. }
  5852. $options = array();
  5853. foreach ($enabledModules['modules'] as $k => $temp) {
  5854. if (isset($temp['meta']['config'])) {
  5855. $settings = array();
  5856. foreach ($temp['meta']['config'] as $conf) {
  5857. $settings[$conf] = Configure::read('Plugin.Enrichment_' . $temp['name'] . '_' . $conf);
  5858. }
  5859. $enabledModules['modules'][$k]['config'] = $settings;
  5860. }
  5861. }
  5862. if (empty($event)) {
  5863. throw new MethodNotAllowedException('Invalid event.');
  5864. }
  5865. $attributes_added = 0;
  5866. $initial_objects = array();
  5867. $event_id = $event[0]['Event']['id'];
  5868. foreach ($event[0]['Attribute'] as $attribute) {
  5869. $object_id = $attribute['object_id'];
  5870. foreach ($enabledModules['modules'] as $module) {
  5871. if (in_array($module['name'], $params['modules'])) {
  5872. if (in_array($attribute['type'], $module['mispattributes']['input'])) {
  5873. $data = array('module' => $module['name'], 'event_id' => $event_id, 'attribute_uuid' => $attribute['uuid']);
  5874. if (!empty($module['config'])) {
  5875. $data['config'] = $module['config'];
  5876. }
  5877. if (!empty($module['mispattributes']['format']) && $module['mispattributes']['format'] == 'misp_standard') {
  5878. $data['attribute'] = $attribute;
  5879. if ($object_id != '0' && empty($initial_objects[$object_id])) {
  5880. $initial_objects[$object_id] = $this->fetchInitialObject($event_id, $object_id);
  5881. }
  5882. } else {
  5883. $data[$attribute['type']] = $attribute['value'];
  5884. }
  5885. $result = $this->Module->queryModuleServer($data, false, 'Enrichment');
  5886. if (!$result) {
  5887. throw new MethodNotAllowedException(h($module['name']) . ' service not reachable.');
  5888. }
  5889. //if (isset($result['error'])) $this->Session->setFlash($result['error']);
  5890. if (!is_array($result)) {
  5891. throw new Exception($result);
  5892. }
  5893. if (!empty($module['mispattributes']['format']) && $module['mispattributes']['format'] == 'misp_standard') {
  5894. if ($object_id != '0' && !empty($initial_objects[$object_id])) {
  5895. $result['initialObject'] = $initial_objects[$object_id];
  5896. }
  5897. $default_comment = $attribute['value'] . ': enriched via the ' . $module['name'] . ' module.';
  5898. $attributes_added += $this->processModuleResultsData($params['user'], $result['results'], $event_id, $default_comment, false, false, true);
  5899. } else {
  5900. $attributes = $this->handleModuleResult($result, $event_id);
  5901. foreach ($attributes as $a) {
  5902. $this->Attribute->create();
  5903. $a['distribution'] = $attribute['distribution'];
  5904. $a['sharing_group_id'] = $attribute['sharing_group_id'];
  5905. $comment = 'Attribute #' . $attribute['id'] . ' enriched by ' . $module['name'] . '.';
  5906. if (!empty($a['comment'])) {
  5907. $a['comment'] .= PHP_EOL . $comment;
  5908. } else {
  5909. $a['comment'] = $comment;
  5910. }
  5911. $a['type'] = empty($a['default_type']) ? $a['types'][0] : $a['default_type'];
  5912. $result = $this->Attribute->save($a);
  5913. if ($result) {
  5914. $attributes_added++;
  5915. }
  5916. }
  5917. }
  5918. }
  5919. }
  5920. }
  5921. }
  5922. return $attributes_added;
  5923. }
  5924. public function massageTags($data, $dataType = 'Event', $excludeGalaxy = false, $cullGalaxyTags = false)
  5925. {
  5926. $data['Galaxy'] = array();
  5927. // unset empty event tags that got added because the tag wasn't exportable
  5928. if (!empty($data[$dataType . 'Tag'])) {
  5929. if (!isset($this->GalaxyCluster)) {
  5930. $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
  5931. }
  5932. foreach ($data[$dataType . 'Tag'] as $k => &$dataTag) {
  5933. if (empty($dataTag['Tag'])) {
  5934. unset($data[$dataType . 'Tag'][$k]);
  5935. continue;
  5936. }
  5937. $dataTag['Tag']['local'] = empty($dataTag['local']) ? 0 : 1;
  5938. if (!isset($excludeGalaxy) || !$excludeGalaxy) {
  5939. if (substr($dataTag['Tag']['name'], 0, strlen('misp-galaxy:')) === 'misp-galaxy:') {
  5940. $cluster = $this->GalaxyCluster->getCluster($dataTag['Tag']['name']);
  5941. if ($cluster) {
  5942. $found = false;
  5943. $cluster['GalaxyCluster']['local'] = isset($dataTag['local']) ? $dataTag['local'] : false;
  5944. foreach ($data['Galaxy'] as $j => $galaxy) {
  5945. if ($galaxy['id'] == $cluster['GalaxyCluster']['Galaxy']['id']) {
  5946. $found = true;
  5947. $temp = $cluster;
  5948. unset($temp['GalaxyCluster']['Galaxy']);
  5949. $data['Galaxy'][$j]['GalaxyCluster'][] = $temp['GalaxyCluster'];
  5950. continue;
  5951. }
  5952. }
  5953. if (!$found) {
  5954. $data['Galaxy'][] = $cluster['GalaxyCluster']['Galaxy'];
  5955. $temp = $cluster;
  5956. unset($temp['GalaxyCluster']['Galaxy']);
  5957. $data['Galaxy'][count($data['Galaxy']) - 1]['GalaxyCluster'][] = $temp['GalaxyCluster'];
  5958. }
  5959. if ($cullGalaxyTags) {
  5960. unset($data[$dataType . 'Tag'][$k]);
  5961. }
  5962. }
  5963. }
  5964. }
  5965. }
  5966. }
  5967. $data[$dataType . 'Tag'] = array_values($data[$dataType . 'Tag']);
  5968. return $data;
  5969. }
  5970. public function insertLock($user, $id)
  5971. {
  5972. $eventLock = ClassRegistry::init('EventLock');
  5973. $eventLock->insertLock($user, $id);
  5974. }
  5975. private function __logUploadResult($server, $event, $newTextBody)
  5976. {
  5977. $this->Log = ClassRegistry::init('Log');
  5978. $this->Log->create();
  5979. $this->Log->save(array(
  5980. 'org' => 'SYSTEM',
  5981. 'model' => 'Server',
  5982. 'model_id' => $server['Server']['id'],
  5983. 'email' => 'SYSTEM',
  5984. 'action' => 'warning',
  5985. 'user_id' => 0,
  5986. 'title' => 'Uploading Event (' . $event['Event']['id'] . ') to Server (' . $server['Server']['id'] . ')',
  5987. 'change' => 'Returned message: ' . $newTextBody,
  5988. ));
  5989. return false;
  5990. }
  5991. public function processFreeTextData(array $user, $attributes, $id, $default_comment = '', $proposals = false, $adhereToWarninglists = false, $jobId = false, $returnRawResults = false)
  5992. {
  5993. $event = $this->find('first', array(
  5994. 'conditions' => array('id' => $id),
  5995. 'recursive' => -1,
  5996. 'fields' => array('orgc_id', 'id', 'uuid'),
  5997. ));
  5998. if (empty($event)) {
  5999. return false;
  6000. }
  6001. $results = array();
  6002. $objectType = $proposals ? 'ShadowAttribute' : 'Attribute';
  6003. if ($adhereToWarninglists) {
  6004. $this->Warninglist = ClassRegistry::init('Warninglist');
  6005. }
  6006. $saved = 0;
  6007. $failed = 0;
  6008. $attributeSources = array('attributes', 'ontheflyattributes');
  6009. $ontheflyattributes = array();
  6010. $i = 0;
  6011. if ($jobId) {
  6012. $this->Job = ClassRegistry::init('Job');
  6013. $total = count($attributeSources);
  6014. }
  6015. foreach ($attributeSources as $sourceKey => $source) {
  6016. foreach (${$source} as $k => $attribute) {
  6017. if ($attribute['type'] == 'ip-src/ip-dst') {
  6018. $types = array('ip-src', 'ip-dst');
  6019. } elseif ($attribute['type'] == 'ip-src|port/ip-dst|port') {
  6020. $types = array('ip-src|port', 'ip-dst|port');
  6021. } elseif ($attribute['type'] == 'malware-sample') {
  6022. if (!isset($attribute['data_is_handled']) || !$attribute['data_is_handled']) {
  6023. $result = $this->Attribute->handleMaliciousBase64($id, $attribute['value'], $attribute['data'], array('md5', 'sha1', 'sha256'), $objectType == 'ShadowAttribute' ? true : false);
  6024. if (!$result['success']) {
  6025. $failed++;
  6026. continue;
  6027. }
  6028. $attribute['data'] = $result['data'];
  6029. $shortValue = $attribute['value'];
  6030. $attribute['value'] = $shortValue . '|' . $result['md5'];
  6031. $additionalHashes = array('sha1', 'sha256');
  6032. foreach ($additionalHashes as $hash) {
  6033. $temp = $attribute;
  6034. $temp['type'] = 'filename|' . $hash;
  6035. $temp['value'] = $shortValue . '|' . $result[$hash];
  6036. unset($temp['data']);
  6037. $ontheflyattributes[] = $temp;
  6038. }
  6039. }
  6040. $types = array($attribute['type']);
  6041. } else {
  6042. $types = array($attribute['type']);
  6043. }
  6044. foreach ($types as $type) {
  6045. $this->$objectType->create();
  6046. $attribute['type'] = $type;
  6047. if (empty($attribute['comment'])) {
  6048. $attribute['comment'] = $default_comment;
  6049. }
  6050. $attribute['event_id'] = $id;
  6051. if ($objectType == 'ShadowAttribute') {
  6052. $attribute['org_id'] = $user['org_id'];
  6053. $attribute['event_org_id'] = $event['Event']['orgc_id'];
  6054. $attribute['email'] = $user['email'];
  6055. $attribute['event_uuid'] = $event['Event']['uuid'];
  6056. }
  6057. // adhere to the warninglist
  6058. if ($adhereToWarninglists) {
  6059. if (!$this->Warninglist->filterWarninglistAttribute($attribute)) {
  6060. if ($adhereToWarninglists === 'soft') {
  6061. $attribute['to_ids'] = 0;
  6062. } else {
  6063. // just ignore the attribute
  6064. continue;
  6065. }
  6066. }
  6067. }
  6068. $saved_attribute = $this->$objectType->save($attribute);
  6069. if ($saved_attribute) {
  6070. $results[] = $saved_attribute;
  6071. // If Tags, attach each tags to attribute
  6072. if (!empty($attribute['tags'])) {
  6073. foreach (explode(",", $attribute['tags']) as $tagName) {
  6074. $tagId = $this->Attribute->AttributeTag->Tag->captureTag(array('name' => trim($tagName)), array('Role' => $user['Role']));
  6075. if ($tagId === false) {
  6076. continue; // user don't have permission to use that tag
  6077. }
  6078. if (!$this->Attribute->AttributeTag->attachTagToAttribute($saved_attribute['Attribute']['id'], $id, $tagId)) {
  6079. throw new MethodNotAllowedException(__('Could not add tags.'));
  6080. }
  6081. }
  6082. }
  6083. $saved++;
  6084. } else {
  6085. $lastError = $this->$objectType->validationErrors;
  6086. $failed++;
  6087. }
  6088. }
  6089. if ($jobId) {
  6090. if ($i % 20 === 0) {
  6091. $this->Job->saveProgress($jobId, 'Attribute ' . $i . '/' . $total, $i * 80 / $total);
  6092. }
  6093. }
  6094. }
  6095. }
  6096. $emailResult = '';
  6097. $messageScope = $objectType == 'ShadowAttribute' ? 'proposals' : 'attributes';
  6098. if ($saved > 0) {
  6099. if ($objectType != 'ShadowAttribute') {
  6100. $this->unpublishEvent($id);
  6101. } else {
  6102. if (!$this->ShadowAttribute->sendProposalAlertEmail($id)) {
  6103. $emailResult = " but sending out the alert e-mails has failed for at least one recipient";
  6104. }
  6105. }
  6106. }
  6107. $messageScopeSaved = $this->__apply_inflector($saved, $messageScope);
  6108. if ($failed > 0) {
  6109. if ($failed == 1) {
  6110. $messageScopeFailed = Inflector::singularize($messageScope);
  6111. $message = $saved . ' ' . $messageScopeSaved . ' created' . $emailResult . '. ' . $failed . ' ' . $messageScopeFailed . ' could not be saved. Reason for the failure: ' . json_encode($lastError);
  6112. } else {
  6113. $message = $saved . ' ' . $messageScopeSaved . ' created' . $emailResult . '. ' . $failed . ' ' . $messageScope . ' could not be saved. This may be due to attributes with similar values already existing.';
  6114. }
  6115. } else {
  6116. $message = $saved . ' ' . $messageScopeSaved . ' created' . $emailResult . '.';
  6117. }
  6118. if ($jobId) {
  6119. $this->Job->saveStatus($jobId, true, __('Processing complete. %s', $message));
  6120. }
  6121. if (!empty($returnRawResults)) {
  6122. return $results;
  6123. }
  6124. return $message;
  6125. }
  6126. public function processModuleResultsData($user, $resolved_data, $id, $default_comment = '', $jobId = false, $adhereToWarninglists = false, $event_level = false)
  6127. {
  6128. if ($jobId) {
  6129. $this->Job = ClassRegistry::init('Job');
  6130. $this->Job->id = $jobId;
  6131. }
  6132. $failed_attributes = $failed_objects = $failed_object_attributes = 0;
  6133. $saved_attributes = $saved_objects = $saved_object_attributes = 0;
  6134. $items_count = 0;
  6135. $failed = array();
  6136. $recovered_uuids = array();
  6137. foreach (array('Attribute', 'Object') as $feature) {
  6138. if (isset($resolved_data[$feature])) {
  6139. $items_count += count($resolved_data[$feature]);
  6140. }
  6141. }
  6142. if (!empty($resolved_data['Tag'])) {
  6143. foreach ($resolved_data['Tag'] as $tag) {
  6144. $tag_id = $this->EventTag->Tag->captureTag($tag, $user);
  6145. if ($tag_id) {
  6146. $this->EventTag->attachTagToEvent($id, $tag_id);
  6147. }
  6148. }
  6149. }
  6150. if (!empty($resolved_data['Attribute'])) {
  6151. $total_attributes = count($resolved_data['Attribute']);
  6152. foreach ($resolved_data['Attribute'] as $a => $attribute) {
  6153. $this->Attribute->create();
  6154. if (empty($attribute['comment'])) {
  6155. $attribute['comment'] = $default_comment;
  6156. }
  6157. if (!empty($attribute['data']) && !empty($attribute['encrypt'])) {
  6158. $attribute = $this->Attribute->onDemandEncrypt($attribute);
  6159. }
  6160. $attribute['event_id'] = $id;
  6161. if ($this->Attribute->save($attribute)) {
  6162. $saved_attributes++;
  6163. if (!empty($attribute['Tag'])) {
  6164. foreach ($attribute['Tag'] as $tag) {
  6165. $tag_id = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user);
  6166. if ($tag_id) {
  6167. $this->Attribute->AttributeTag->attachTagToAttribute($this->Attribute->id, $id, $tag_id);
  6168. }
  6169. }
  6170. }
  6171. } else {
  6172. $failed_attributes++;
  6173. $lastAttributeError = $this->Attribute->validationErrors;
  6174. $original_uuid = $this->Object->Attribute->find('first', array(
  6175. 'conditions' => array('Attribute.event_id' => $id, 'Attribute.object_id' => 0, 'Attribute.deleted' => 0,
  6176. 'Attribute.type' => $attribute['type'], 'Attribute.value' => $attribute['value']),
  6177. 'recursive' => -1,
  6178. 'fields' => array('Attribute.uuid')
  6179. ));
  6180. if (!empty($original_uuid)) {
  6181. $recovered_uuids[$attribute['uuid']] = $original_uuid['Attribute']['uuid'];
  6182. } else {
  6183. $failed[] = $attribute['uuid'];
  6184. }
  6185. }
  6186. if ($jobId) {
  6187. $current = ($a + 1);
  6188. $this->Job->saveField('message', 'Attribute ' . $current . '/' . $total_attributes);
  6189. $this->Job->saveField('progress', ($current * 100 / $items_count));
  6190. }
  6191. }
  6192. } else {
  6193. $total_attributes = 0;
  6194. }
  6195. if (!empty($resolved_data['Object'])) {
  6196. $initial_object_id = isset($resolved_data['initialObject']) ? $resolved_data['initialObject']['Object']['id'] : "0";
  6197. $total_objects = count($resolved_data['Object']);
  6198. $references = array();
  6199. foreach ($resolved_data['Object'] as $o => $object) {
  6200. if (isset($object['meta_category']) && !isset($object['meta-category'])) {
  6201. $object['meta-category'] = $object['meta_category'];
  6202. unset($object['meta_category']);
  6203. }
  6204. if (empty($object['comment'])) {
  6205. $object['comment'] = $default_comment;
  6206. }
  6207. $object['event_id'] = $id;
  6208. if (isset($object['id']) && $object['id'] == $initial_object_id) {
  6209. $initial_object = $resolved_data['initialObject'];
  6210. $recovered_uuids[$object['uuid']] = $initial_object['Object']['uuid'];
  6211. if ($object['name'] != $initial_object['Object']['name']) {
  6212. throw new NotFoundException(__('Invalid object.'));
  6213. }
  6214. $initial_attributes = array();
  6215. if (!empty($initial_object['Attribute'])) {
  6216. foreach ($initial_object['Attribute'] as $initial_attribute) {
  6217. $initial_attributes[$initial_attribute['object_relation']][] = $initial_attribute['value'];
  6218. }
  6219. }
  6220. $initial_references = array();
  6221. if (!empty($initial_object['ObjectReference'])) {
  6222. foreach ($initial_object['ObjectReference'] as $initial_reference) {
  6223. $initial_references[$initial_reference['relationship_type']][] = $initial_reference['referenced_uuid'];
  6224. }
  6225. }
  6226. if (!empty($object['Attribute'])) {
  6227. foreach ($object['Attribute'] as $object_attribute) {
  6228. $object_relation = $object_attribute['object_relation'];
  6229. if (isset($initial_attributes[$object_relation]) && in_array($object_attribute['value'], $initial_attributes[$object_relation])) {
  6230. continue;
  6231. }
  6232. if ($this->__saveObjectAttribute($object_attribute, $default_comment, $id, $initial_object_id, $user)) {
  6233. $saved_object_attributes++;
  6234. } else {
  6235. $failed_object_attributes++;
  6236. $lastObjectAttributeError = $this->Attribute->validationErrors;
  6237. }
  6238. }
  6239. }
  6240. if (!empty($object['ObjectReference'])) {
  6241. foreach ($object['ObjectReference'] as $object_reference) {
  6242. array_push($references, array('objectId' => $initial_object_id, 'reference' => $object_reference));
  6243. }
  6244. }
  6245. $saved_objects++;
  6246. } else {
  6247. if (!empty($object['Attribute'])) {
  6248. $current_object_id = $this->__findCurrentObjectId($id, $object['Attribute']);
  6249. if ($current_object_id) {
  6250. $original_uuid = $this->Object->find('first', array(
  6251. 'conditions' => array('Object.id' => $current_object_id, 'Object.event_id' => $id,
  6252. 'Object.name' => $object['name'], 'Object.deleted' => 0),
  6253. 'recursive' => -1,
  6254. 'fields' => array('Object.uuid')
  6255. ));
  6256. if (!empty($original_uuid)) {
  6257. $recovered_uuids[$object['uuid']] = $original_uuid['Object']['uuid'];
  6258. }
  6259. $object_id = $current_object_id;
  6260. } else {
  6261. $this->Object->create();
  6262. if ($this->Object->save($object)) {
  6263. $object_id = $this->Object->id;
  6264. foreach ($object['Attribute'] as $object_attribute) {
  6265. if ($this->__saveObjectAttribute($object_attribute, $default_comment, $id, $object_id, $user)) {
  6266. $saved_object_attributes++;
  6267. } else {
  6268. $failed_object_attributes++;
  6269. $lastObjectAttributeError = $this->Attribute->validationErrors;
  6270. }
  6271. }
  6272. $saved_objects++;
  6273. } else {
  6274. $failed_objects++;
  6275. $lastObjectError = $this->Object->validationErrors;
  6276. $failed[] = $object['uuid'];
  6277. continue;
  6278. }
  6279. }
  6280. } else {
  6281. $this->Object->create();
  6282. if ($this->Object->save($object)) {
  6283. $object_id = $this->Object->id;
  6284. $saved_objects++;
  6285. } else {
  6286. $failed_objects++;
  6287. $lastObjectError = $this->Object->validationErrors;
  6288. $failed[] = $object['uuid'];
  6289. continue;
  6290. }
  6291. }
  6292. if (!empty($object['ObjectReference'])) {
  6293. foreach($object['ObjectReference'] as $object_reference) {
  6294. array_push($references, array('objectId' => $object_id, 'reference' => $object_reference));
  6295. }
  6296. }
  6297. }
  6298. if ($jobId) {
  6299. $current = ($o + 1);
  6300. $this->Job->saveField('message', 'Object ' . $current . '/' . $total_objects);
  6301. $this->Job->saveField('progress', (($current + $total_attributes) * 100 / $items_count));
  6302. }
  6303. }
  6304. }
  6305. if (!empty($references)) {
  6306. $reference_errors = array();
  6307. foreach($references as $reference) {
  6308. $object_id = $reference['objectId'];
  6309. $reference = $reference['reference'];
  6310. if (in_array($reference['object_uuid'], $failed) || in_array($reference['referenced_uuid'], $failed)) {
  6311. continue;
  6312. }
  6313. if (isset($recovered_uuids[$reference['object_uuid']])) {
  6314. $reference['object_uuid'] = $recovered_uuids[$reference['object_uuid']];
  6315. }
  6316. if (isset($recovered_uuids[$reference['referenced_uuid']])) {
  6317. $reference['referenced_uuid'] = $recovered_uuids[$reference['referenced_uuid']];
  6318. }
  6319. $current_reference = $this->Object->ObjectReference->find('all', array(
  6320. 'conditions' => array('ObjectReference.object_id' => $object_id,
  6321. 'ObjectReference.referenced_uuid' => $reference['referenced_uuid'],
  6322. 'ObjectReference.relationship_type' => $reference['relationship_type'],
  6323. 'ObjectReference.event_id' => $id, 'ObjectReference.deleted' => 0),
  6324. 'recursive' => -1,
  6325. 'fields' => ('ObjectReference.uuid')
  6326. ));
  6327. if (!empty($current_reference)) {
  6328. continue;
  6329. }
  6330. list($referenced_id, $referenced_uuid, $referenced_type) = $this->Object->ObjectReference->getReferencedInfo(
  6331. $reference['referenced_uuid'],
  6332. array('Event' => array('id' => $id)),
  6333. false
  6334. );
  6335. if (!$referenced_id && !$referenced_uuid && !$referenced_type) {
  6336. continue;
  6337. }
  6338. $reference = array(
  6339. 'event_id' => $id,
  6340. 'referenced_id' => $referenced_id,
  6341. 'referenced_uuid' => $referenced_uuid,
  6342. 'referenced_type' => $referenced_type,
  6343. 'object_id' => $object_id,
  6344. 'object_uuid' => $reference['object_uuid'],
  6345. 'relationship_type' => $reference['relationship_type']
  6346. );
  6347. $this->Object->ObjectReference->create();
  6348. if (!$this->Object->ObjectReference->save($reference)) {
  6349. $reference_errors[] = $this->Object->ObjectReference->validationErrors;
  6350. }
  6351. }
  6352. }
  6353. if ($saved_attributes > 0 || $saved_objects > 0) {
  6354. $event = $this->find('first', array(
  6355. 'conditions' => array('Event.id' => $id),
  6356. 'recursive' => -1
  6357. ));
  6358. if ($event['Event']['published'] == 1) {
  6359. $event['Event']['published'] = 0;
  6360. }
  6361. $date = new DateTime();
  6362. $event['Event']['timestamp'] = $date->getTimestamp();
  6363. $this->save($event);
  6364. }
  6365. if ($event_level) {
  6366. return $saved_attributes + $saved_object_attributes;
  6367. }
  6368. $message = '';
  6369. if ($saved_attributes > 0) {
  6370. $message .= $saved_attributes . ' ' . $this->__apply_inflector($saved_attributes, 'attribute') . ' created. ';
  6371. }
  6372. if ($failed_attributes > 0) {
  6373. if ($failed_attributes == 1) {
  6374. $reason = ' attribute could not be saved. Reason for the failure: ' . json_encode($lastAttributeError) . ' ';
  6375. } else {
  6376. $reason = ' attributes could not be saved. This may be due to attributes with similar values already existing. ';
  6377. }
  6378. $message .= $failed_attributes . $reason;
  6379. }
  6380. if ($saved_objects > 0) {
  6381. $message .= $saved_objects . ' ' . $this->__apply_inflector($saved_objects, 'object') . ' created';
  6382. if ($saved_object_attributes > 0) {
  6383. $message .= ' (including a total of ' . $saved_object_attributes . ' object ' . $this->__apply_inflector($saved_object_attributes, 'attribute') . '). ';
  6384. } else {
  6385. $message .= '. ';
  6386. }
  6387. }
  6388. if ($failed_objects > 0) {
  6389. if ($failed_objects == 1) {
  6390. $reason = ' object could not be saved. Reason for the failure: ';
  6391. } else {
  6392. $reason = ' objects could not be saved. An example of reason for the failure: ';
  6393. }
  6394. $message .= $failed_objects . $reason . json_encode($lastObjectError) . ' ';
  6395. }
  6396. if ($failed_object_attributes > 0) {
  6397. if ($failed_object_attributes == 1) {
  6398. $reason = 'object attribute could not be saved. Reason for the failure: ';
  6399. } else {
  6400. $reason = 'object attributes could not be saved. An example of reason for the failure: ';
  6401. }
  6402. $message .= 'By the way, ' . $failed_object_attributes . $reason . json_encode($lastObjectAttributeError) . '.';
  6403. }
  6404. if (!empty($reference_errors)) {
  6405. $reference_error = sizeof($reference_errors) == 1 ? 'a reference is' : 'some references are';
  6406. $message .= ' Also, be aware that ' . $reference_error . ' missing: ';
  6407. foreach ($reference_errors as $error) {
  6408. $message .= $error;
  6409. }
  6410. $message .= 'you can have a look at the module results view you just left, to compare.';
  6411. }
  6412. if ($jobId) {
  6413. $this->Job->saveField('message', 'Processing complete. ' . $message);
  6414. $this->Job->saveField('progress', 100);
  6415. }
  6416. return $message;
  6417. }
  6418. private function __apply_inflector($count, $scope)
  6419. {
  6420. return ($count == 1 ? Inflector::singularize($scope) : Inflector::pluralize($scope));
  6421. }
  6422. private function __findCurrentObjectId($event_id, $attributes)
  6423. {
  6424. $conditions = array();
  6425. foreach($attributes as $attribute) {
  6426. $conditions[] = array('AND' => array(
  6427. 'Attribute.object_relation' => $attribute['object_relation'],
  6428. 'Attribute.value' => $attribute['value'],
  6429. 'Attribute.type' => $attribute['type']
  6430. ));
  6431. }
  6432. $ids = array();
  6433. foreach ($this->Object->Attribute->find('all', array(
  6434. 'conditions' => array(
  6435. 'Attribute.event_id' => $event_id,
  6436. 'Attribute.object_id !=' => 0,
  6437. 'Attribute.deleted' => 0,
  6438. 'OR' => $conditions
  6439. ),
  6440. 'recursive' => -1,
  6441. 'fields' => array('Attribute.object_id'))) as $found_id) {
  6442. $ids[] = $found_id['Attribute']['object_id'];
  6443. }
  6444. $attributes_count = sizeof($attributes);
  6445. foreach (array_count_values($ids) as $id => $count) {
  6446. if ($count >= $attributes_count) {
  6447. return $id;
  6448. }
  6449. }
  6450. return 0;
  6451. }
  6452. private function __saveObjectAttribute($attribute, $default_comment, $event_id, $object_id, $user)
  6453. {
  6454. $attribute['object_id'] = $object_id;
  6455. $attribute['event_id'] = $event_id;
  6456. if (empty($attribute['comment'])) {
  6457. $attribute['comment'] = $default_comment;
  6458. }
  6459. if (!empty($attribute['data']) && !empty($attribute['encrypt'])) {
  6460. $attribute = $this->Attribute->onDemandEncrypt($attribute);
  6461. }
  6462. $this->Attribute->create();
  6463. $attribute_save = $this->Attribute->save($attribute);
  6464. if ($attribute_save) {
  6465. if (!empty($attribute['Tag'])) {
  6466. foreach ($attribute['Tag'] as $tag) {
  6467. $tag_id = $this->Attribute->AttributeTag->Tag->captureTag($tag, $user);
  6468. if ($tag_id) {
  6469. $this->Attribute->AttributeTag->attachTagToAttribute($this->Attribute->id, $event_id, $tag_id);
  6470. }
  6471. }
  6472. }
  6473. }
  6474. return $attribute_save;
  6475. }
  6476. public function processFreeTextDataRouter($user, $attributes, $id, $default_comment = '', $proposals = false, $adhereToWarninglists = false, $returnRawResults = false)
  6477. {
  6478. if (Configure::read('MISP.background_jobs') && count($attributes) > 5) { // on background process just big attributes batch
  6479. list($job, $randomFileName, $tempFile) = $this->__initiateProcessJob($user, $id);
  6480. $tempData = array(
  6481. 'user' => $user,
  6482. 'attributes' => $attributes,
  6483. 'id' => $id,
  6484. 'default_comment' => $default_comment,
  6485. 'proposals' => $proposals,
  6486. 'adhereToWarninglists' => $adhereToWarninglists,
  6487. 'jobId' => $job->id,
  6488. );
  6489. $writeResult = $tempFile->write(json_encode($tempData));
  6490. if (!$writeResult) {
  6491. return $this->processFreeTextData($user, $attributes, $id, $default_comment, $proposals, $adhereToWarninglists, false, $returnRawResults);
  6492. }
  6493. $tempFile->close();
  6494. $process_id = CakeResque::enqueue(
  6495. 'prio',
  6496. 'EventShell',
  6497. array('processfreetext', $randomFileName),
  6498. true
  6499. );
  6500. $job->saveField('process_id', $process_id);
  6501. return 'Freetext ingestion queued for background processing. Attributes will be added to the event as they are being processed.';
  6502. } else {
  6503. return $this->processFreeTextData($user, $attributes, $id, $default_comment, $proposals, $adhereToWarninglists, false, $returnRawResults);
  6504. }
  6505. }
  6506. public function processModuleResultsDataRouter($user, $resolved_data, $id, $default_comment = '', $adhereToWarninglists = false)
  6507. {
  6508. if (Configure::read('MISP.background_jobs')) {
  6509. list($job, $randomFileName, $tempFile) = $this->__initiateProcessJob($user, $id, 'module_results');
  6510. $tempData = array(
  6511. 'user' => $user,
  6512. 'misp_format' => $resolved_data,
  6513. 'id' => $id,
  6514. 'default_comment' => $default_comment,
  6515. 'jobId' => $job->id
  6516. );
  6517. $writeResult = $tempFile->write(json_encode($tempData));
  6518. if ($writeResult) {
  6519. $tempFile->close();
  6520. $process_id = CakeResque::enqueue(
  6521. 'prio',
  6522. 'EventShell',
  6523. array('processmoduleresult', $randomFileName),
  6524. true
  6525. );
  6526. $job->saveField('process_id', $process_id);
  6527. return 'Module results ingestion queued for background processing. Related data will be added to the event as it is being processed.';
  6528. }
  6529. $tempFile->delete();
  6530. }
  6531. return $this->processModuleResultsData($user, $resolved_data, $id, $default_comment = '');
  6532. }
  6533. private function __initiateProcessJob($user, $id, $format = 'freetext')
  6534. {
  6535. $job = ClassRegistry::init('Job');
  6536. $job->create();
  6537. $data = array(
  6538. 'worker' => 'default',
  6539. 'job_type' => __('process_' . $format . '_data'),
  6540. 'job_input' => 'Event: ' . $id,
  6541. 'status' => 0,
  6542. 'retries' => 0,
  6543. 'org_id' => $user['org_id'],
  6544. 'org' => $user['Organisation']['name'],
  6545. 'message' => 'Processing...'
  6546. );
  6547. $job->save($data);
  6548. $randomFileName = $this->generateRandomFileName() . '.json';
  6549. App::uses('Folder', 'Utility');
  6550. App::uses('File', 'Utility');
  6551. $tempdir = new Folder(APP . 'tmp/cache/ingest', true, 0755);
  6552. $tempFile = new File(APP . 'tmp/cache/ingest' . DS . $randomFileName, true, 0644);
  6553. return array($job, $randomFileName, $tempFile);
  6554. }
  6555. /**
  6556. * Get tag from cache by given ID.
  6557. *
  6558. * @param int $tagId
  6559. * @param bool $justExportable If true, return just exportable tags.
  6560. * @return array|null
  6561. */
  6562. private function __getCachedTag($tagId, $justExportable)
  6563. {
  6564. if (!isset($this->assetCache['tags'][$tagId])) {
  6565. return null;
  6566. }
  6567. $tag = $this->assetCache['tags'][$tagId];
  6568. if ($justExportable && !$tag['exportable']) {
  6569. return null;
  6570. }
  6571. return $tag;
  6572. }
  6573. /**
  6574. * Fetches all tags for event and event attributes in one query and save to cache.
  6575. *
  6576. * @param array $event
  6577. * @param bool $justExportable If true, cache just exportable tags.
  6578. */
  6579. private function __precacheTagsForEvent(array $event, $justExportable)
  6580. {
  6581. $tagIds = [];
  6582. if (!empty($event['EventTag'])) {
  6583. foreach ($event['EventTag'] as $eventTag) {
  6584. $tagIds[$eventTag['tag_id']] = true;
  6585. }
  6586. }
  6587. if (!empty($event['Attribute'])) {
  6588. foreach ($event['Attribute'] as $attribute) {
  6589. if (!empty($attribute['AttributeTag'])) {
  6590. foreach ($attribute['AttributeTag'] as $attributeTag) {
  6591. $tagIds[$attributeTag['tag_id']] = true;
  6592. }
  6593. }
  6594. }
  6595. }
  6596. $notCachedTags = array_diff(array_keys($tagIds), isset($this->assetCache['tags']) ? array_keys($this->assetCache['tags']) : []);
  6597. if (empty($notCachedTags)) {
  6598. return;
  6599. }
  6600. $conditions = ['id' => $notCachedTags];
  6601. if ($justExportable) {
  6602. $conditions['exportable'] = 1;
  6603. }
  6604. $tags = $this->EventTag->Tag->find('all', [
  6605. 'recursive' => -1,
  6606. 'conditions' => $conditions,
  6607. ]);
  6608. foreach ($tags as $tag) {
  6609. $this->assetCache['tags'][$tag['Tag']['id']] = $tag['Tag'];
  6610. }
  6611. }
  6612. /**
  6613. * Attach tags to attributes and event.
  6614. *
  6615. * @param array $event
  6616. * @param bool $justExportable If true, attach just exportable tags.
  6617. */
  6618. private function __attachTags(array &$event, $justExportable)
  6619. {
  6620. $this->__precacheTagsForEvent($event, $justExportable);
  6621. if (!empty($event['EventTag'])) {
  6622. foreach ($event['EventTag'] as $etk => $eventTag) {
  6623. $tag = $this->__getCachedTag($eventTag['tag_id'], $justExportable);
  6624. if ($tag !== null) {
  6625. $event['EventTag'][$etk]['Tag'] = $tag;
  6626. }
  6627. }
  6628. }
  6629. if (!empty($event['Attribute'])) {
  6630. foreach ($event['Attribute'] as $ak => $attribute) {
  6631. if (!empty($attribute['AttributeTag'])) {
  6632. foreach ($attribute['AttributeTag'] as $atk => $attributeTag) {
  6633. $tag = $this->__getCachedTag($attributeTag['tag_id'], $justExportable);
  6634. if ($tag !== null) {
  6635. $event['Attribute'][$ak]['AttributeTag'][$atk]['Tag'] = $tag;
  6636. }
  6637. }
  6638. }
  6639. }
  6640. }
  6641. }
  6642. /**
  6643. * Attach referenced object to ObjectReference. Since reference can be just to attribute or object in the same event,
  6644. * we just find proper element in event.
  6645. *
  6646. * @param array $event
  6647. * @param array $fields
  6648. */
  6649. private function __attachReferences(array &$event, array $fields)
  6650. {
  6651. if (!isset($event['Object'])) {
  6652. return;
  6653. }
  6654. foreach ($event['Object'] as $k => $object) {
  6655. foreach ($object['ObjectReference'] as $k2 => $reference) {
  6656. // find referenced object in current event
  6657. $type = $reference['referenced_type'] == 0 ? 'Attribute' : 'Object';
  6658. $found = null;
  6659. foreach ($event[$type] as $o) {
  6660. if ($o['id'] == $reference['referenced_id']) {
  6661. $found = $o;
  6662. break;
  6663. }
  6664. }
  6665. if ($found) {
  6666. // copy requested fields
  6667. $reference = [];
  6668. foreach (array_merge($fields['common'], $fields[$type]) as $field) {
  6669. $reference[$field] = $found[$field];
  6670. }
  6671. $event['Object'][$k]['ObjectReference'][$k2][$type] = $reference;
  6672. }
  6673. }
  6674. }
  6675. }
  6676. public function restSearch($user, $returnFormat, $filters, $paramsOnly = false, $jobId = false, &$elementCounter = 0, &$renderView = false)
  6677. {
  6678. if (!isset($this->validFormats[$returnFormat][1])) {
  6679. throw new NotFoundException('Invalid output format.');
  6680. }
  6681. App::uses($this->validFormats[$returnFormat][1], 'Export');
  6682. $exportTool = new $this->validFormats[$returnFormat][1]();
  6683. if ($jobId) {
  6684. $this->Job = ClassRegistry::init('Job');
  6685. $this->Job->id = $jobId;
  6686. }
  6687. if (!empty($exportTool->use_default_filters)) {
  6688. $exportTool->setDefaultFilters($filters);
  6689. }
  6690. if (empty($exportTool->non_restrictive_export)) {
  6691. if (!isset($filters['to_ids'])) {
  6692. $filters['to_ids'] = 1;
  6693. }
  6694. if (!isset($filters['published'])) {
  6695. $filters['published'] = 1;
  6696. }
  6697. $filters['allow_proposal_blocking'] = 1;
  6698. }
  6699. if (!empty($exportTool->renderView)) {
  6700. $renderView = $exportTool->renderView;
  6701. }
  6702. if (!empty($filters['ignore'])) {
  6703. $filters['to_ids'] = array(0, 1);
  6704. $filters['published'] = array(0, 1);
  6705. }
  6706. if (!empty($filters['quickFilter'])) {
  6707. $filters['searchall'] = $filters['quickFilter'];
  6708. if (!empty($filters['value'])) {
  6709. unset($filters['value']);
  6710. }
  6711. }
  6712. if (isset($filters['searchall'])) {
  6713. if (!empty($filters['value'])) {
  6714. $filters['wildcard'] = $filters['value'];
  6715. } else {
  6716. $filters['wildcard'] = $filters['searchall'];
  6717. }
  6718. }
  6719. if (isset($filters['tag']) and !isset($filters['tags'])) {
  6720. $filters['tags'] = $filters['tag'];
  6721. }
  6722. $subqueryElements = $this->harvestSubqueryElements($filters);
  6723. $filters = $this->addFiltersFromSubqueryElements($filters, $subqueryElements);
  6724. $filters = $this->addFiltersFromUserSettings($user, $filters);
  6725. if (empty($exportTool->mock_query_only)) {
  6726. $filters['include_attribute_count'] = 1;
  6727. $eventid = $this->filterEventIds($user, $filters, $elementCounter);
  6728. $eventCount = count($eventid);
  6729. $eventids_chunked = $this->__clusterEventIds($exportTool, $eventid);
  6730. unset($eventid);
  6731. } else {
  6732. $eventids_chunked = array();
  6733. }
  6734. if (!empty($exportTool->additional_params)) {
  6735. $filters = array_merge($filters, $exportTool->additional_params);
  6736. }
  6737. $exportToolParams = array(
  6738. 'user' => $user,
  6739. 'params' => array(),
  6740. 'returnFormat' => $returnFormat,
  6741. 'scope' => 'Event',
  6742. 'filters' => $filters
  6743. );
  6744. if (empty($exportTool->non_restrictive_export)) {
  6745. if (!isset($filters['to_ids'])) {
  6746. $filters['to_ids'] = 1;
  6747. }
  6748. if (!isset($filters['published'])) {
  6749. $filters['published'] = 1;
  6750. }
  6751. }
  6752. $tmpfile = new TmpFileTool();
  6753. $tmpfile->write($exportTool->header($exportToolParams));
  6754. $i = 0;
  6755. if (!empty($filters['withAttachments'])) {
  6756. $filters['includeAttachments'] = 1;
  6757. }
  6758. $this->Allowedlist = ClassRegistry::init('Allowedlist');
  6759. $separator = $exportTool->separator($exportToolParams);
  6760. foreach ($eventids_chunked as $chunk) {
  6761. $filters['eventid'] = $chunk;
  6762. if (!empty($filters['tags']['NOT'])) {
  6763. $filters['blockedAttributeTags'] = $filters['tags']['NOT'];
  6764. unset($filters['tags']['NOT']);
  6765. }
  6766. $result = $this->fetchEvent($user, $filters,true);
  6767. $result = $this->Allowedlist->removeAllowedlistedFromArray($result, false);
  6768. foreach ($result as $event) {
  6769. if ($jobId && $i % 10 == 0) {
  6770. $this->Job->saveField('progress', intval((100 * $i) / $eventCount));
  6771. $this->Job->saveField('message', 'Converting Event ' . $i . '/' . $eventCount . '.');
  6772. }
  6773. $temp = $exportTool->handler($event, $exportToolParams);
  6774. if ($temp !== '') {
  6775. $tmpfile->writeWithSeparator($temp, $separator);
  6776. $i++;
  6777. }
  6778. }
  6779. }
  6780. unset($result);
  6781. unset($temp);
  6782. $tmpfile->write($exportTool->footer($exportToolParams));
  6783. return $tmpfile->finish();
  6784. }
  6785. /*
  6786. * Receive a list of eventids in the id=>count format
  6787. * Chunk them by the attribute count to fit the memory limits
  6788. *
  6789. */
  6790. private function __clusterEventIds($exportTool, $eventIds)
  6791. {
  6792. $memory_in_mb = $this->Attribute->convert_to_memory_limit_to_mb(ini_get('memory_limit'));
  6793. $default_attribute_memory_coefficient = Configure::check('MISP.default_attribute_memory_coefficient') ? Configure::read('MISP.default_attribute_memory_coefficient') : 80;
  6794. $default_event_memory_divisor = Configure::check('MISP.default_event_memory_multiplier') ? Configure::read('MISP.default_event_memory_divisor') : 3;
  6795. $memory_scaling_factor = isset($exportTool->memory_scaling_factor) ? $exportTool->memory_scaling_factor : $default_attribute_memory_coefficient;
  6796. // increase the cost per attribute to account for the overhead of object metadata
  6797. $memory_scaling_factor = $memory_scaling_factor / $default_event_memory_divisor;
  6798. $limit = $memory_in_mb * $memory_scaling_factor;
  6799. $eventIdList = array();
  6800. $continue = true;
  6801. $i = 0;
  6802. $current_chunk_size = 0;
  6803. $largest_event = 0;
  6804. $largest_event_id = 0;
  6805. foreach ($eventIds as $id => $count) {
  6806. if ($count > $largest_event) {
  6807. $largest_event = $count;
  6808. $largest_event_id = $id;
  6809. }
  6810. if ($current_chunk_size == 0 && $count > $limit) {
  6811. $eventIdList[$i][] = $id;
  6812. $current_chunk_size = $count;
  6813. $i++;
  6814. } else {
  6815. if (($current_chunk_size + $count) > $limit) {
  6816. $i++;
  6817. $eventIdList[$i][] = $id;
  6818. $current_chunk_size = $count;
  6819. } else {
  6820. $current_chunk_size += $count;
  6821. $eventIdList[$i][] = $id;
  6822. }
  6823. }
  6824. }
  6825. if ($largest_event/$memory_scaling_factor > $memory_in_mb) {
  6826. $this->Log = ClassRegistry::init('Log');
  6827. $this->Log->create();
  6828. $this->Log->save(array(
  6829. 'org' => 'SYSTEM',
  6830. 'model' => 'Event',
  6831. 'model_id' => 0,
  6832. 'email' => 'SYSTEM',
  6833. 'action' => 'error',
  6834. 'title' => sprintf('Event fetch potential memory exhaustion. During the fetching of events, a large event (#%s) was detected that exceeds the available PHP memory. Consider raising the PHP max_memory setting to at least %sM', $largest_event_id, ceil($largest_event/$memory_scaling_factor)),
  6835. 'change' => null,
  6836. ));
  6837. }
  6838. return $eventIdList;
  6839. }
  6840. public function add_original_file($file, $original_filename, $event_id, $format)
  6841. {
  6842. if (!Configure::check('MISP.default_attribute_distribution') || Configure::read('MISP.default_attribute_distribution') === 'event') {
  6843. $distribution = 5;
  6844. } else {
  6845. $distribution = Configure::read('MISP.default_attribute_distribution');
  6846. }
  6847. $this->Object->create();
  6848. $object = array(
  6849. 'name' => 'original-imported-file',
  6850. 'meta-category' => 'file',
  6851. 'description' => 'Object describing the original file used to import data in MISP.',
  6852. 'template_uuid' => '4cd560e9-2cfe-40a1-9964-7b2e797ecac5',
  6853. 'template_version' => '2',
  6854. 'event_id' => $event_id,
  6855. 'distribution' => $distribution
  6856. );
  6857. $this->Object->save($object);
  6858. $object_id = $this->Object->id;
  6859. $attributes = array(
  6860. array(
  6861. 'type' => 'attachment',
  6862. 'category' => 'External analysis',
  6863. 'to_ids' => false,
  6864. 'event_id' => $event_id,
  6865. 'distribution' => $distribution,
  6866. 'object_relation' => 'imported-sample',
  6867. 'value' => $original_filename,
  6868. 'data' => base64_encode($file),
  6869. 'object_id' => $object_id,
  6870. 'disable_correlation' => true
  6871. ),
  6872. array(
  6873. 'type' => 'text',
  6874. 'category' => 'Other',
  6875. 'to_ids' => false,
  6876. 'event_id' => $event_id,
  6877. 'distribution' => $distribution,
  6878. 'object_id' => $object_id,
  6879. 'object_relation' => 'format',
  6880. 'value' => $format,
  6881. 'disable_correlation' => true
  6882. )
  6883. );
  6884. foreach ($attributes as $attribute) {
  6885. $this->Attribute->create();
  6886. $this->Attribute->save($attribute);
  6887. }
  6888. return true;
  6889. }
  6890. public function getRequiredTaxonomies()
  6891. {
  6892. $this->Taxonomy = ClassRegistry::init('Taxonomy');
  6893. $required_taxonomies = $this->Taxonomy->find('list', array(
  6894. 'recursive' => -1,
  6895. 'conditions' => array('Taxonomy.required' => 1, 'Taxonomy.enabled' => 1),
  6896. 'fields' => array('Taxonomy.namespace')
  6897. ));
  6898. return $required_taxonomies;
  6899. }
  6900. public function checkIfPublishable($id)
  6901. {
  6902. $required_taxonomies = $this->getRequiredTaxonomies();
  6903. if (!empty($required_taxonomies)) {
  6904. $tags = $this->EventTag->find('all', array(
  6905. 'conditions' => array('EventTag.event_id' => $id),
  6906. 'recursive' => -1,
  6907. 'contain' => array('Tag')
  6908. ));
  6909. $missing = array();
  6910. foreach ($required_taxonomies as $required_taxonomy) {
  6911. $found = false;
  6912. foreach ($tags as $tag) {
  6913. $name = explode(':', $tag['Tag']['name']);
  6914. if (count($name) > 1) {
  6915. if ($name[0] == $required_taxonomy) {
  6916. $found = true;
  6917. break;
  6918. }
  6919. }
  6920. }
  6921. if (!$found) {
  6922. $missing[] = $required_taxonomy;
  6923. }
  6924. }
  6925. if (!empty($missing)) {
  6926. return $missing;
  6927. }
  6928. }
  6929. return true;
  6930. }
  6931. public function harvestSubqueryElements($options)
  6932. {
  6933. $acceptedRules = array(
  6934. 'galaxy' => 1,
  6935. 'org' => array('sector', 'local', 'nationality')
  6936. );
  6937. $subqueryElement = array(
  6938. 'galaxy' => array(),
  6939. 'org' => array(),
  6940. );
  6941. foreach($options as $rule => $value) {
  6942. $split = explode(".", $rule, 2);
  6943. if (count($split) > 1) {
  6944. $scope = $split[0];
  6945. $element = $split[1];
  6946. if (isset($acceptedRules[$scope])) {
  6947. if (is_array($acceptedRules[$scope]) && !in_array($element, $acceptedRules[$scope])) {
  6948. continue;
  6949. } else {
  6950. $subqueryElement[$scope][$element] = $value;
  6951. }
  6952. }
  6953. }
  6954. }
  6955. return $subqueryElement;
  6956. }
  6957. public function addFiltersFromSubqueryElements($filters, $subqueryElements)
  6958. {
  6959. if (!empty($subqueryElements['galaxy'])) {
  6960. $this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
  6961. $tagsFromGalaxyMeta = $this->GalaxyCluster->getClusterTagsFromMeta($subqueryElements['galaxy']);
  6962. if (empty($tagsFromGalaxyMeta)) {
  6963. $filters['eventid'] = -1;
  6964. }
  6965. if (!empty($filters['tags'])) {
  6966. $filters['tags'][] = $tagsFromGalaxyMeta;
  6967. } else {
  6968. $filters['tags'] = $tagsFromGalaxyMeta;
  6969. }
  6970. }
  6971. if (!empty($subqueryElements['org'])) {
  6972. $Organisation = ClassRegistry::init('Organisation');
  6973. $orgcIdsFromMeta = $Organisation->getOrgIdsFromMeta($subqueryElements['org']);
  6974. if (!empty($filters['org'])) {
  6975. $filters['org'][] = $orgcIdsFromMeta;
  6976. } else {
  6977. $filters['org'] = $orgcIdsFromMeta;
  6978. }
  6979. }
  6980. return $filters;
  6981. }
  6982. public function addFiltersFromUserSettings($user, $filters)
  6983. {
  6984. $this->UserSetting = ClassRegistry::init('UserSetting');
  6985. $defaultParameters = $this->UserSetting->getDefaulRestSearchParameters($user);
  6986. $filters = array_replace_recursive($defaultParameters, $filters);
  6987. return $filters;
  6988. }
  6989. /**
  6990. * @param array $event
  6991. */
  6992. public function removeGalaxyClusterTags(array &$event)
  6993. {
  6994. $galaxyTagIds = array();
  6995. foreach ($event['Galaxy'] as $galaxy) {
  6996. foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
  6997. $galaxyTagIds[$galaxyCluster['tag_id']] = true;
  6998. }
  6999. }
  7000. if (empty($galaxyTagIds)) {
  7001. return;
  7002. }
  7003. foreach ($event['EventTag'] as $k => $eventTag) {
  7004. if (isset($galaxyTagIds[$eventTag['tag_id']])) {
  7005. unset($event['EventTag'][$k]);
  7006. }
  7007. }
  7008. }
  7009. public function recoverEvent($id)
  7010. {
  7011. $this->Log = ClassRegistry::init('Log');
  7012. $result = $this->Log->recoverDeletedEvent($id);
  7013. return $result;
  7014. }
  7015. /**
  7016. * @param array $event Event with assigned `EventTag`
  7017. * @return string
  7018. */
  7019. private function getEmailSubjectMarkForEvent(array $event)
  7020. {
  7021. $subjTag = Configure::read('MISP.email_subject_tag') ?: "tlp";
  7022. $tagLen = strlen($subjTag);
  7023. foreach ($event['EventTag'] as $tag) {
  7024. $tagName = $tag['Tag']['name'];
  7025. if (strncasecmp($subjTag, $tagName, $tagLen) === 0 && strlen($tagName) > $tagLen && ($tagName[$tagLen] === ':' || $tagName[$tagLen] === '=')) {
  7026. if (Configure::read('MISP.email_subject_include_tag_name') === false) {
  7027. return trim(substr($tagName, $tagLen + 1), '"');
  7028. } else {
  7029. return $tagName;
  7030. }
  7031. }
  7032. }
  7033. // default value if no match found
  7034. return Configure::read('MISP.email_subject_TLP_string') ?: "tlp:amber";
  7035. }
  7036. public function getExtendingEventIdsFromEvent($user, $eventID)
  7037. {
  7038. $event = $this->fetchSimpleEvent($user, $eventID);
  7039. if (!empty($event)) {
  7040. $extendingEventIds = $this->fetchSimpleEventIds($user, ['conditions' => [
  7041. 'extends_uuid' => $event['Event']['uuid']
  7042. ]]);
  7043. return $extendingEventIds;
  7044. }
  7045. return [];
  7046. }
  7047. }