暂无描述

cases.py 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. # IRIS Source Code
  2. # Copyright (C) 2024 - DFIR-IRIS
  3. # contact@dfir-iris.org
  4. #
  5. # This program is free software; you can redistribute it and/or
  6. # modify it under the terms of the GNU Lesser General Public
  7. # License as published by the Free Software Foundation; either
  8. # version 3 of the License, or (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  13. # Lesser General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with this program; if not, write to the Free Software Foundation,
  17. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18. import datetime
  19. import logging as log
  20. import traceback
  21. from flask_login import current_user
  22. from marshmallow.exceptions import ValidationError
  23. from app import app
  24. from app import db
  25. from app.util import add_obj_history_entry
  26. from app.schema.marshables import CaseSchema
  27. from app.models.models import ReviewStatusList
  28. from app.business.errors import BusinessProcessingError
  29. from app.business.iocs import iocs_exports_to_json
  30. from app.iris_engine.module_handler.module_handler import call_modules_hook
  31. from app.iris_engine.utils.tracker import track_activity
  32. from app.iris_engine.access_control.utils import ac_set_new_case_access
  33. from app.datamgmt.case.case_db import case_db_exists
  34. from app.datamgmt.case.case_db import save_case_tags
  35. from app.datamgmt.case.case_db import register_case_protagonists
  36. from app.datamgmt.case.case_db import get_review_id_from_name
  37. from app.datamgmt.alerts.alerts_db import get_alert_status_by_name
  38. from app.datamgmt.manage.manage_case_templates_db import case_template_pre_modifier
  39. from app.datamgmt.manage.manage_case_templates_db import case_template_post_modifier
  40. from app.datamgmt.manage.manage_access_control_db import user_has_client_access
  41. from app.datamgmt.manage.manage_case_state_db import get_case_state_by_name
  42. from app.datamgmt.manage.manage_cases_db import delete_case
  43. from app.datamgmt.manage.manage_cases_db import reopen_case
  44. from app.datamgmt.manage.manage_cases_db import map_alert_resolution_to_case_status
  45. from app.datamgmt.manage.manage_cases_db import close_case
  46. from app.datamgmt.case.case_db import get_case
  47. from app.datamgmt.reporter.report_db import export_caseinfo_json
  48. from app.datamgmt.reporter.report_db import process_md_images_links_for_report
  49. from app.datamgmt.reporter.report_db import export_case_evidences_json
  50. from app.datamgmt.reporter.report_db import export_case_tm_json
  51. from app.datamgmt.reporter.report_db import export_case_assets_json
  52. from app.datamgmt.reporter.report_db import export_case_tasks_json
  53. from app.datamgmt.reporter.report_db import export_case_comments_json
  54. from app.datamgmt.reporter.report_db import export_case_notes_json
  55. def _load(request_data, **kwargs):
  56. try:
  57. add_case_schema = CaseSchema()
  58. return add_case_schema.load(request_data, **kwargs)
  59. except ValidationError as e:
  60. raise BusinessProcessingError('Data error', e.messages)
  61. def cases_get_by_identifier(case_identifier):
  62. return get_case(case_identifier)
  63. def cases_exists(identifier):
  64. return case_db_exists(identifier)
  65. def cases_create(request_data):
  66. # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument
  67. request_data = call_modules_hook('on_preload_case_create', request_data, None)
  68. case = _load(request_data)
  69. case.owner_id = current_user.id
  70. case.severity_id = 4
  71. case_template_id = request_data.pop('case_template_id', None)
  72. if case_template_id and len(case_template_id) > 0:
  73. case = case_template_pre_modifier(case, case_template_id)
  74. if case is None:
  75. raise BusinessProcessingError(f'Invalid Case template ID {case_template_id}')
  76. case.state_id = get_case_state_by_name('Open').state_id
  77. case.save()
  78. if case_template_id and len(case_template_id) > 0:
  79. try:
  80. case, logs = case_template_post_modifier(case, case_template_id)
  81. if len(logs) > 0:
  82. raise BusinessProcessingError(f'Could not update new case with {case_template_id}', logs)
  83. except Exception as e:
  84. log.error(e.__str__())
  85. raise BusinessProcessingError(f'Unexpected error when loading template {case_template_id} to new case.')
  86. ac_set_new_case_access(None, case.case_id, case.client_id)
  87. # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument
  88. case = call_modules_hook('on_postload_case_create', case, None)
  89. add_obj_history_entry(case, 'created')
  90. track_activity(f'new case "{case.name}" created', caseid=case.case_id, ctx_less=False)
  91. return case
  92. def cases_delete(case_identifier):
  93. if case_identifier == 1:
  94. track_activity(f'tried to delete case {case_identifier}, but case is the primary case',
  95. caseid=case_identifier, ctx_less=True)
  96. raise BusinessProcessingError('Cannot delete a primary case to keep consistency')
  97. try:
  98. call_modules_hook('on_preload_case_delete', data=case_identifier, caseid=case_identifier)
  99. if not delete_case(case_identifier):
  100. track_activity(f'tried to delete case {case_identifier}, but it doesn\'t exist',
  101. caseid=case_identifier, ctx_less=True)
  102. raise BusinessProcessingError('Tried to delete a non-existing case')
  103. call_modules_hook('on_postload_case_delete', data=case_identifier, caseid=case_identifier)
  104. track_activity(f'case {case_identifier} deleted successfully', ctx_less=True)
  105. except Exception as e:
  106. app.logger.exception(e)
  107. raise BusinessProcessingError('Cannot delete the case. Please check server logs for additional informations')
  108. def cases_update(case_identifier, request_data):
  109. case_i = get_case(case_identifier)
  110. if not case_i:
  111. raise BusinessProcessingError('Case not found')
  112. try:
  113. previous_case_state = case_i.state_id
  114. case_previous_reviewer_id = case_i.reviewer_id
  115. closed_state_id = get_case_state_by_name('Closed').state_id
  116. # If user tries to update the customer, check if the user has access to the new customer
  117. if request_data.get('case_customer') and request_data.get('case_customer') != case_i.client_id:
  118. if not user_has_client_access(current_user.id, request_data.get('case_customer')):
  119. raise BusinessProcessingError('Invalid customer ID. Permission denied.')
  120. if 'case_name' in request_data:
  121. short_case_name = request_data.get('case_name').replace(f'#{case_i.case_id} - ', '')
  122. request_data['case_name'] = f'#{case_i.case_id} - {short_case_name}'
  123. request_data['case_customer'] = case_i.client_id if not request_data.get('case_customer') else request_data.get(
  124. 'case_customer')
  125. request_data['reviewer_id'] = None if request_data.get('reviewer_id') == '' else request_data.get('reviewer_id')
  126. case = _load(request_data, instance=case_i, partial=True)
  127. db.session.commit()
  128. if previous_case_state != case.state_id:
  129. if case.state_id == closed_state_id:
  130. track_activity('case closed', caseid=case_identifier)
  131. res = close_case(case_identifier)
  132. if not res:
  133. raise BusinessProcessingError('Tried to close an non-existing case')
  134. # Close the related alerts
  135. if case.alerts:
  136. close_status = get_alert_status_by_name('Closed')
  137. case_status_id_mapped = map_alert_resolution_to_case_status(case.status_id)
  138. for alert in case.alerts:
  139. if alert.alert_status_id != close_status.status_id:
  140. alert.alert_status_id = close_status.status_id
  141. alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=case_identifier)
  142. if alert.alert_resolution_status_id != case_status_id_mapped:
  143. alert.alert_resolution_status_id = case_status_id_mapped
  144. alert = call_modules_hook('on_postload_alert_resolution_update', data=alert,
  145. caseid=case_identifier)
  146. track_activity(
  147. f'closing alert ID {alert.alert_id} due to case #{case_identifier} being closed',
  148. caseid=case_identifier, ctx_less=False)
  149. db.session.add(alert)
  150. elif previous_case_state == closed_state_id and case.state_id != closed_state_id:
  151. track_activity('case re-opened', caseid=case_identifier)
  152. res = reopen_case(case_identifier)
  153. if not res:
  154. raise BusinessProcessingError('Tried to re-open an non-existing case')
  155. if case_previous_reviewer_id != case.reviewer_id:
  156. if case.reviewer_id is None:
  157. track_activity('case reviewer removed', caseid=case_identifier)
  158. case.review_status_id = get_review_id_from_name(ReviewStatusList.not_reviewed)
  159. else:
  160. track_activity('case reviewer changed', caseid=case_identifier)
  161. register_case_protagonists(case.case_id, request_data.get('protagonists'))
  162. save_case_tags(request_data.get('case_tags'), case_i)
  163. case = call_modules_hook('on_postload_case_update', data=case, caseid=case_identifier)
  164. add_obj_history_entry(case_i, 'case info updated')
  165. track_activity(f'case updated "{case.name}"', caseid=case_identifier)
  166. return case, 'Updated'
  167. except ValidationError as e:
  168. raise BusinessProcessingError('Data error', e.messages)
  169. except BusinessProcessingError as e:
  170. raise e
  171. except Exception as e:
  172. log.error(e.__str__())
  173. log.error(traceback.format_exc())
  174. raise BusinessProcessingError('Data error', str(e))
  175. def cases_export_to_json(case_id):
  176. """Fully export a case a JSON"""
  177. export = {}
  178. case = export_caseinfo_json(case_id)
  179. if not case:
  180. export['errors'] = ["Invalid case number"]
  181. return export
  182. case['description'] = process_md_images_links_for_report(case['description'])
  183. export['case'] = case
  184. export['evidences'] = export_case_evidences_json(case_id)
  185. export['timeline'] = export_case_tm_json(case_id)
  186. export['iocs'] = iocs_exports_to_json(case_id)
  187. export['assets'] = export_case_assets_json(case_id)
  188. export['tasks'] = export_case_tasks_json(case_id)
  189. export['comments'] = export_case_comments_json(case_id)
  190. export['notes'] = export_case_notes_json(case_id)
  191. export['export_date'] = datetime.datetime.utcnow()
  192. return export
  193. def cases_export_to_report_json(case_id):
  194. """Fully export of a case for report generation"""
  195. export = {}
  196. case = export_caseinfo_json(case_id)
  197. if not case:
  198. export['errors'] = ["Invalid case number"]
  199. return export
  200. case['description'] = process_md_images_links_for_report(case['description'])
  201. export['case'] = case
  202. export['evidences'] = export_case_evidences_json(case_id)
  203. export['timeline'] = export_case_tm_json(case_id)
  204. export['iocs'] = iocs_exports_to_json(case_id)
  205. export['assets'] = export_case_assets_json(case_id)
  206. export['tasks'] = export_case_tasks_json(case_id)
  207. export['notes'] = export_case_notes_json(case_id)
  208. export['comments'] = export_case_comments_json(case_id)
  209. export['export_date'] = datetime.datetime.utcnow()
  210. return export