Brak opisu

datastore_db.py 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. #
  2. # IRIS Source Code
  3. # Copyright (C) 2022 - DFIR IRIS Team
  4. # contact@dfir-iris.org
  5. # Created by whitekernel - 2022-05-17
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU Lesser General Public
  9. # License as published by the Free Software Foundation; either
  10. # version 3 of the License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public License
  18. # along with this program; if not, write to the Free Software Foundation,
  19. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  20. import datetime
  21. from pathlib import Path
  22. from flask_login import current_user
  23. from sqlalchemy import and_
  24. from sqlalchemy import func
  25. from app import app
  26. from app import db
  27. from app.models.models import CaseReceivedFile
  28. from app.models.models import DataStoreFile
  29. from app.models.models import DataStorePath
  30. from app.models.models import Ioc
  31. from app.models.models import IocType
  32. from app.models.models import Tlp
  33. def datastore_get_root(cid):
  34. dsp_root = DataStorePath.query.filter(
  35. and_(DataStorePath.path_case_id == cid,
  36. DataStorePath.path_is_root == True
  37. )
  38. ).first()
  39. if dsp_root is None:
  40. init_ds_tree(cid)
  41. dsp_root = DataStorePath.query.filter(
  42. and_(DataStorePath.path_case_id == cid,
  43. DataStorePath.path_is_root == True
  44. )
  45. ).first()
  46. return dsp_root
  47. def ds_list_tree(cid):
  48. dsp_root = datastore_get_root(cid)
  49. dsp = DataStorePath.query.filter(
  50. and_(DataStorePath.path_case_id == cid,
  51. DataStorePath.path_is_root == False
  52. )
  53. ).order_by(
  54. DataStorePath.path_parent_id
  55. ).all()
  56. dsf = DataStoreFile.query.filter(
  57. DataStoreFile.file_case_id == cid
  58. ).all()
  59. dsp_root = dsp_root
  60. droot_id = f"d-{dsp_root.path_id}"
  61. path_tree = {
  62. droot_id: {
  63. "name": dsp_root.path_name,
  64. "type": "directory",
  65. "is_root": True,
  66. "children": {}
  67. }
  68. }
  69. droot_children = path_tree[droot_id]["children"]
  70. files_nodes = {}
  71. for dfile in dsf:
  72. dfnode = dfile.__dict__
  73. dfnode.pop('_sa_instance_state')
  74. dfnode.pop('file_local_name')
  75. dfnode['type'] = "file"
  76. dnode_id = f"f-{dfile.file_id}"
  77. dnode_parent_id = f"d-{dfile.file_parent_id}"
  78. if dnode_parent_id == droot_id:
  79. droot_children.update({
  80. dnode_id: dfnode
  81. })
  82. elif dnode_parent_id in files_nodes:
  83. files_nodes[dnode_parent_id].update({
  84. dnode_id: dfnode
  85. })
  86. else:
  87. files_nodes[dnode_parent_id] = {dnode_id: dfnode}
  88. for dpath in dsp:
  89. dpath_id = f"d-{dpath.path_id}"
  90. dpath_parent_id = f"d-{dpath.path_parent_id}"
  91. path_node = {
  92. dpath_id: {
  93. "name": dpath.path_name,
  94. "type": "directory",
  95. "children": {}
  96. }
  97. }
  98. path_node_files = files_nodes.get(dpath_id)
  99. if path_node_files:
  100. path_node[dpath_id]["children"].update(path_node_files)
  101. if dpath_parent_id == droot_id:
  102. droot_children.update(path_node)
  103. else:
  104. datastore_iter_tree(dpath_parent_id, path_node, droot_children)
  105. return path_tree
  106. def init_ds_tree(cid):
  107. dsp_root = DataStorePath.query.filter(
  108. and_(DataStorePath.path_case_id == cid,
  109. DataStorePath.path_is_root == True
  110. )
  111. ).all()
  112. if dsp_root:
  113. return dsp_root
  114. dsp_root = DataStorePath()
  115. dsp_root.path_name = f'Case {cid}'
  116. dsp_root.path_is_root = True
  117. dsp_root.path_case_id = cid
  118. dsp_root.path_parent_id = 0
  119. db.session.add(dsp_root)
  120. db.session.commit()
  121. for path in ['Evidences', 'IOCs', 'Images']:
  122. dsp_init = DataStorePath()
  123. dsp_init.path_parent_id = dsp_root.path_id
  124. dsp_init.path_case_id = cid
  125. dsp_init.path_name = path
  126. dsp_init.path_is_root = False
  127. db.session.add(dsp_init)
  128. db.session.commit()
  129. return dsp_root
  130. def datastore_iter_tree(path_parent_id, path_node, tree):
  131. for parent_id, node in tree.items():
  132. if parent_id == path_parent_id:
  133. node["children"].update(path_node)
  134. return tree
  135. if isinstance(node, dict):
  136. datastore_iter_tree(path_parent_id, path_node, node)
  137. return None
  138. def datastore_add_child_node(parent_node, folder_name, cid):
  139. try:
  140. dsp_base = DataStorePath.query.filter(
  141. DataStorePath.path_id == parent_node,
  142. DataStorePath.path_case_id == cid
  143. ).first()
  144. except Exception:
  145. return True, f'Unable to request datastore for parent node : {parent_node}', None
  146. if dsp_base is None:
  147. return True, 'Parent node is invalid for this case', None
  148. dsp = DataStorePath()
  149. dsp.path_case_id = cid
  150. dsp.path_name = folder_name
  151. dsp.path_parent_id = parent_node
  152. dsp.path_is_root = False
  153. db.session.add(dsp)
  154. db.session.commit()
  155. return False, 'Folder added', dsp
  156. def datastore_rename_node(parent_node, folder_name, cid):
  157. try:
  158. dsp_base = DataStorePath.query.filter(
  159. DataStorePath.path_id == parent_node,
  160. DataStorePath.path_case_id == cid
  161. ).first()
  162. except Exception:
  163. return True, f'Unable to request datastore for parent node : {parent_node}', None
  164. if dsp_base is None:
  165. return True, 'Parent node is invalid for this case', None
  166. dsp_base.path_name = folder_name
  167. db.session.commit()
  168. return False, 'Folder renamed', dsp_base
  169. def datastore_delete_node(node_id, cid):
  170. try:
  171. dsp_base = DataStorePath.query.filter(
  172. DataStorePath.path_id == node_id,
  173. DataStorePath.path_case_id == cid
  174. ).first()
  175. except Exception:
  176. return True, f'Unable to request datastore for parent node : {node_id}'
  177. if dsp_base is None:
  178. return True, 'Parent node is invalid for this case'
  179. datastore_iter_deletion(dsp_base, cid)
  180. return False, 'Folder and children deleted'
  181. def datastore_iter_deletion(dsp, cid):
  182. dsp_children = DataStorePath.query.filter(
  183. and_(DataStorePath.path_case_id == cid,
  184. DataStorePath.path_is_root == False,
  185. DataStorePath.path_parent_id == dsp.path_id
  186. )
  187. ).all()
  188. for dsp_child in dsp_children:
  189. datastore_iter_deletion(dsp_child, cid)
  190. datastore_delete_files_of_path(dsp.path_id, cid)
  191. db.session.delete(dsp)
  192. db.session.commit()
  193. return None
  194. def datastore_delete_files_of_path(node_id, cid):
  195. dsf_list = DataStoreFile.query.filter(
  196. and_(DataStoreFile.file_parent_id == node_id,
  197. DataStoreFile.file_case_id == cid
  198. )
  199. ).all()
  200. for dsf_list_item in dsf_list:
  201. fln = Path(dsf_list_item.file_local_name)
  202. if fln.is_file():
  203. fln.unlink(missing_ok=True)
  204. db.session.delete(dsf_list_item)
  205. db.session.commit()
  206. return
  207. def datastore_get_path_node(node_id, cid):
  208. return DataStorePath.query.filter(
  209. DataStorePath.path_id == node_id,
  210. DataStorePath.path_case_id == cid
  211. ).first()
  212. def datastore_get_interactive_path_node(cid):
  213. dsp = DataStorePath.query.filter(
  214. DataStorePath.path_name == 'Notes Upload',
  215. DataStorePath.path_case_id == cid
  216. ).first()
  217. if not dsp:
  218. dsp_root = datastore_get_root(cid)
  219. dsp = DataStorePath()
  220. dsp.path_case_id = cid
  221. dsp.path_parent_id = dsp_root.path_id
  222. dsp.path_name = 'Notes Upload'
  223. dsp.path_is_root = False
  224. db.session.add(dsp)
  225. db.session.commit()
  226. return dsp
  227. def datastore_get_standard_path(datastore_file, cid):
  228. root_path = Path(app.config['DATASTORE_PATH'])
  229. if datastore_file.file_is_ioc:
  230. target_path = root_path / 'IOCs'
  231. elif datastore_file.file_is_evidence:
  232. target_path = root_path / 'Evidences'
  233. else:
  234. target_path = root_path / 'Regulars'
  235. target_path = target_path / f"case-{cid}"
  236. if not target_path.is_dir():
  237. target_path.mkdir(parents=True, exist_ok=True)
  238. return target_path / f"dsf-{datastore_file.file_uuid}"
  239. def datastore_get_file(file_id, cid):
  240. dsf = DataStoreFile.query.filter(
  241. DataStoreFile.file_id == file_id,
  242. DataStoreFile.file_case_id == cid
  243. ).first()
  244. return dsf
  245. def datastore_delete_file(cur_id, cid):
  246. dsf = DataStoreFile.query.filter(
  247. DataStoreFile.file_id == cur_id,
  248. DataStoreFile.file_case_id == cid
  249. ).first()
  250. if dsf is None:
  251. return True, 'Invalid DS file ID for this case'
  252. fln = Path(dsf.file_local_name)
  253. if fln.is_file():
  254. fln.unlink(missing_ok=True)
  255. db.session.delete(dsf)
  256. db.session.commit()
  257. return False, f'File {cur_id} deleted'
  258. def datastore_add_file_as_ioc(dsf, caseid):
  259. ioc = Ioc.query.filter(
  260. Ioc.ioc_value == dsf.file_sha256
  261. ).first()
  262. ioc_type_id = IocType.query.filter(
  263. IocType.type_name == 'sha256'
  264. ).first()
  265. ioc_tlp_id = Tlp.query.filter(
  266. Tlp.tlp_name == 'amber'
  267. ).first()
  268. if ioc is None:
  269. ioc = Ioc()
  270. ioc.ioc_value = dsf.file_sha256
  271. ioc.ioc_description = f"SHA256 of {dsf.file_original_name}. Imported from datastore."
  272. ioc.ioc_type_id = ioc_type_id.type_id
  273. ioc.ioc_tlp_id = ioc_tlp_id.tlp_id
  274. ioc.ioc_tags = "datastore"
  275. ioc.user_id = current_user.id
  276. db.session.add(ioc)
  277. db.session.commit()
  278. def datastore_add_file_as_evidence(dsf, caseid):
  279. crf = CaseReceivedFile.query.filter(
  280. CaseReceivedFile.file_hash == dsf.file_sha256
  281. ).first()
  282. if crf is None:
  283. crf = CaseReceivedFile()
  284. crf.file_hash = dsf.file_sha256
  285. crf.file_description = f"Imported from datastore. {dsf.file_description}"
  286. crf.case_id = caseid
  287. crf.date_added = datetime.datetime.now()
  288. crf.filename = dsf.file_original_name
  289. crf.file_size = dsf.file_size
  290. crf.user_id = current_user.id
  291. db.session.add(crf)
  292. db.session.commit()
  293. return
  294. def datastore_get_local_file_path(file_id, caseid):
  295. dsf = DataStoreFile.query.filter(
  296. DataStoreFile.file_id == file_id,
  297. DataStoreFile.file_case_id == caseid
  298. ).first()
  299. if dsf is None:
  300. return True, 'Invalid DS file ID for this case'
  301. return False, dsf
  302. def datastore_filter_tree(filter_d, caseid):
  303. names = filter_d.get('name')
  304. storage_names = filter_d.get('storage_name')
  305. tags = filter_d.get('tag')
  306. descriptions = filter_d.get('description')
  307. is_ioc = filter_d.get('is_ioc')
  308. is_evidence = filter_d.get('is_evidence')
  309. has_password = filter_d.get('has_password')
  310. file_id = filter_d.get('id')
  311. file_uuid = filter_d.get('uuid')
  312. file_sha256 = filter_d.get('sha256')
  313. condition = (DataStoreFile.file_case_id == caseid)
  314. if file_id:
  315. for fid in file_id:
  316. if fid:
  317. fid = fid.replace('dsf-', '')
  318. condition = and_(condition, DataStoreFile.file_id == fid)
  319. if file_uuid:
  320. for fuid in file_uuid:
  321. if fuid:
  322. fuid = fuid.replace('dsf-', '')
  323. condition = and_(condition, DataStoreFile.file_uuid == fuid)
  324. if file_sha256:
  325. for fsha in file_sha256:
  326. if fsha:
  327. condition = and_(condition, func.lower(DataStoreFile.file_sha256) == fsha.lower())
  328. if names:
  329. for name in names:
  330. condition = and_(condition,
  331. DataStoreFile.file_original_name.ilike(f'%{name}%'))
  332. if storage_names:
  333. for name in storage_names:
  334. condition = and_(condition,
  335. DataStoreFile.file_local_name.ilike(f'%{name}%'))
  336. if tags:
  337. for tag in tags:
  338. condition = and_(condition,
  339. DataStoreFile.file_tags.ilike(f'%{tag}%'))
  340. if descriptions:
  341. for description in descriptions:
  342. condition = and_(condition,
  343. DataStoreFile.file_description.ilike(f'%{description}%'))
  344. if is_ioc is not None:
  345. condition = and_(condition,
  346. (DataStoreFile.file_is_ioc == True))
  347. if is_evidence is not None:
  348. condition = and_(condition,
  349. (DataStoreFile.file_is_evidence == True))
  350. if has_password is not None:
  351. condition = and_(condition,
  352. (DataStoreFile.file_password != ""))
  353. dsp_root = DataStorePath.query.filter(
  354. and_(DataStorePath.path_case_id == caseid,
  355. DataStorePath.path_is_root == True
  356. )
  357. ).first()
  358. if dsp_root is None:
  359. init_ds_tree(caseid)
  360. dsp_root = DataStorePath.query.filter(
  361. and_(DataStorePath.path_case_id == caseid,
  362. DataStorePath.path_is_root == True
  363. )
  364. ).first()
  365. dsp = DataStorePath.query.filter(
  366. and_(DataStorePath.path_case_id == caseid,
  367. DataStorePath.path_is_root == False
  368. )
  369. ).order_by(
  370. DataStorePath.path_parent_id
  371. ).all()
  372. try:
  373. dsf = DataStoreFile.query.filter(
  374. condition
  375. ).all()
  376. except Exception as e:
  377. return None, str(e)
  378. dsp_root = dsp_root
  379. droot_id = f"d-{dsp_root.path_id}"
  380. path_tree = {
  381. droot_id: {
  382. "name": dsp_root.path_name,
  383. "type": "directory",
  384. "is_root": True,
  385. "children": {}
  386. }
  387. }
  388. droot_children = path_tree[droot_id]["children"]
  389. files_nodes = {}
  390. for dfile in dsf:
  391. dfnode = dfile.__dict__
  392. dfnode.pop('_sa_instance_state')
  393. dfnode.pop('file_local_name')
  394. dfnode['type'] = "file"
  395. dnode_id = f"f-{dfile.file_id}"
  396. dnode_parent_id = f"d-{dfile.file_parent_id}"
  397. if dnode_parent_id == droot_id:
  398. droot_children.update({
  399. dnode_id: dfnode
  400. })
  401. elif dnode_parent_id in files_nodes:
  402. files_nodes[dnode_parent_id].update({
  403. dnode_id: dfnode
  404. })
  405. else:
  406. files_nodes[dnode_parent_id] = {dnode_id: dfnode}
  407. for dpath in dsp:
  408. dpath_id = f"d-{dpath.path_id}"
  409. dpath_parent_id = f"d-{dpath.path_parent_id}"
  410. path_node = {
  411. dpath_id: {
  412. "name": dpath.path_name,
  413. "type": "directory",
  414. "children": {}
  415. }
  416. }
  417. path_node_files = files_nodes.get(dpath_id)
  418. if path_node_files:
  419. path_node[dpath_id]["children"].update(path_node_files)
  420. if dpath_parent_id == droot_id:
  421. droot_children.update(path_node)
  422. else:
  423. datastore_iter_tree(dpath_parent_id, path_node, droot_children)
  424. return path_tree, 'Success'