Nessuna descrizione

manage_cases_routes.py 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. # IRIS Source Code
  2. # contact@dfir-iris.org
  3. #
  4. # This program is free software; you can redistribute it and/or
  5. # modify it under the terms of the GNU Lesser General Public
  6. # License as published by the Free Software Foundation; either
  7. # version 3 of the License, or (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. # Lesser General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU Lesser General Public License
  15. # along with this program; if not, write to the Free Software Foundation,
  16. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  17. import logging as log
  18. import os
  19. import traceback
  20. import urllib.parse
  21. from flask import Blueprint
  22. from flask import request
  23. from flask_login import current_user
  24. from werkzeug import Response
  25. from werkzeug.utils import secure_filename
  26. from app import db
  27. from app.blueprints.rest.parsing import parse_comma_separated_identifiers
  28. from app.blueprints.rest.endpoints import endpoint_deprecated
  29. from app.datamgmt.alerts.alerts_db import get_alert_status_by_name
  30. from app.datamgmt.case.case_db import get_case
  31. from app.datamgmt.iris_engine.modules_db import get_pipelines_args_from_name
  32. from app.datamgmt.iris_engine.modules_db import iris_module_exists
  33. from app.datamgmt.manage.manage_cases_db import get_filtered_cases
  34. from app.datamgmt.manage.manage_cases_db import close_case, map_alert_resolution_to_case_status
  35. from app.datamgmt.manage.manage_cases_db import get_case_details_rt
  36. from app.datamgmt.manage.manage_cases_db import list_cases_dict
  37. from app.datamgmt.manage.manage_cases_db import reopen_case
  38. from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access
  39. from app.iris_engine.module_handler.module_handler import call_modules_hook
  40. from app.iris_engine.module_handler.module_handler import configure_module_on_init
  41. from app.iris_engine.module_handler.module_handler import instantiate_module_from_name
  42. from app.iris_engine.tasker.tasks import task_case_update
  43. from app.iris_engine.utils.common import build_upload_path
  44. from app.iris_engine.utils.tracker import track_activity
  45. from app.models.authorization import CaseAccessLevel
  46. from app.models.authorization import Permissions
  47. from app.schema.marshables import CaseSchema
  48. from app.schema.marshables import CaseDetailsSchema
  49. from app.util import add_obj_history_entry
  50. from app.blueprints.access_controls import ac_requires_case_identifier
  51. from app.blueprints.access_controls import ac_api_requires
  52. from app.blueprints.access_controls import ac_api_return_access_denied
  53. from app.blueprints.responses import response_error
  54. from app.blueprints.responses import response_success
  55. from app.blueprints.rest.parsing import parse_pagination_parameters
  56. from app.business.cases import cases_delete
  57. from app.business.cases import cases_update
  58. from app.business.cases import cases_create
  59. from app.business.errors import BusinessProcessingError
  60. manage_cases_rest_blueprint = Blueprint('manage_case_rest', __name__)
  61. @manage_cases_rest_blueprint.route('/manage/cases/<int:identifier>', methods=['GET'])
  62. @endpoint_deprecated('GET', '/api/v2/cases/<int:identifier>')
  63. @ac_api_requires()
  64. def get_case_api(identifier):
  65. if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.read_only, CaseAccessLevel.full_access]):
  66. return ac_api_return_access_denied(caseid=identifier)
  67. res = get_case_details_rt(identifier)
  68. if res:
  69. return response_success(data=res)
  70. return response_error(f'Case ID {identifier} not found')
  71. @manage_cases_rest_blueprint.route('/manage/cases/filter', methods=['GET'])
  72. @ac_api_requires()
  73. def manage_case_filter() -> Response:
  74. pagination_parameters = parse_pagination_parameters(request)
  75. case_ids_str = request.args.get('case_ids', None, type=str)
  76. if case_ids_str:
  77. try:
  78. case_ids_str = parse_comma_separated_identifiers(case_ids_str)
  79. except ValueError:
  80. return response_error('Invalid case id')
  81. case_customer_id = request.args.get('case_customer_id', None, type=str)
  82. case_name = request.args.get('case_name', None, type=str)
  83. case_description = request.args.get('case_description', None, type=str)
  84. case_classification_id = request.args.get('case_classification_id', None, type=int)
  85. case_owner_id = request.args.get('case_owner_id', None, type=int)
  86. case_opening_user_id = request.args.get('case_opening_user_id', None, type=int)
  87. case_severity_id = request.args.get('case_severity_id', None, type=int)
  88. case_state_id = request.args.get('case_state_id', None, type=int)
  89. case_soc_id = request.args.get('case_soc_id', None, type=str)
  90. start_open_date = request.args.get('start_open_date', None, type=str)
  91. end_open_date = request.args.get('end_open_date', None, type=str)
  92. draw = request.args.get('draw', 1, type=int)
  93. search_value = request.args.get('search[value]', type=str) # Get the search value from the request
  94. if type(draw) is not int:
  95. draw = 1
  96. filtered_cases = get_filtered_cases(
  97. current_user.id,
  98. pagination_parameters,
  99. case_ids=case_ids_str,
  100. case_customer_id=case_customer_id,
  101. case_name=case_name,
  102. case_description=case_description,
  103. case_classification_id=case_classification_id,
  104. case_owner_id=case_owner_id,
  105. case_opening_user_id=case_opening_user_id,
  106. case_severity_id=case_severity_id,
  107. case_state_id=case_state_id,
  108. case_soc_id=case_soc_id,
  109. start_open_date=start_open_date,
  110. end_open_date=end_open_date,
  111. search_value=search_value
  112. )
  113. if filtered_cases is None:
  114. return response_error('Filtering error')
  115. cases = {
  116. 'total': filtered_cases.total,
  117. 'cases': CaseDetailsSchema().dump(filtered_cases.items, many=True),
  118. 'last_page': filtered_cases.pages,
  119. 'current_page': filtered_cases.page,
  120. 'next_page': filtered_cases.next_num if filtered_cases.has_next else None,
  121. 'draw': draw
  122. }
  123. return response_success(data=cases)
  124. @manage_cases_rest_blueprint.route('/manage/cases/delete/<int:identifier>', methods=['POST'])
  125. @endpoint_deprecated('DELETE', '/api/v2/cases/<int:identifier>')
  126. @ac_api_requires(Permissions.standard_user)
  127. def api_delete_case(identifier):
  128. if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]):
  129. return ac_api_return_access_denied(caseid=identifier)
  130. try:
  131. cases_delete(identifier)
  132. return response_success('Case successfully deleted')
  133. except BusinessProcessingError as e:
  134. return response_error(e.get_message())
  135. @manage_cases_rest_blueprint.route('/manage/cases/reopen/<int:identifier>', methods=['POST'])
  136. @ac_api_requires(Permissions.standard_user)
  137. def api_reopen_case(identifier):
  138. if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]):
  139. return ac_api_return_access_denied(caseid=identifier)
  140. if not identifier:
  141. return response_error("No case ID provided")
  142. case = get_case(identifier)
  143. if not case:
  144. return response_error("Tried to reopen an non-existing case")
  145. res = reopen_case(identifier)
  146. if not res:
  147. return response_error("Tried to reopen an non-existing case")
  148. # Reopen the related alerts
  149. if case.alerts:
  150. merged_status = get_alert_status_by_name('Merged')
  151. for alert in case.alerts:
  152. if alert.alert_status_id != merged_status.status_id:
  153. alert.alert_status_id = merged_status.status_id
  154. track_activity(f"alert ID {alert.alert_id} status updated to merged due to case #{identifier} being reopen",
  155. caseid=identifier, ctx_less=False)
  156. db.session.add(alert)
  157. case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier)
  158. add_obj_history_entry(case, 'case reopen')
  159. track_activity("reopen case ID {}".format(identifier), caseid=identifier)
  160. case_schema = CaseSchema()
  161. return response_success("Case reopen successfully", data=case_schema.dump(res))
  162. @manage_cases_rest_blueprint.route('/manage/cases/close/<int:identifier>', methods=['POST'])
  163. @ac_api_requires(Permissions.standard_user)
  164. def api_case_close(identifier):
  165. if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]):
  166. return ac_api_return_access_denied(caseid=identifier)
  167. if not identifier:
  168. return response_error("No case ID provided")
  169. case = get_case(identifier)
  170. if not case:
  171. return response_error("Tried to close an non-existing case")
  172. res = close_case(identifier)
  173. if not res:
  174. return response_error("Tried to close an non-existing case")
  175. # Close the related alerts
  176. if case.alerts:
  177. close_status = get_alert_status_by_name('Closed')
  178. case_status_id_mapped = map_alert_resolution_to_case_status(case.status_id)
  179. for alert in case.alerts:
  180. if alert.alert_status_id != close_status.status_id:
  181. alert.alert_status_id = close_status.status_id
  182. alert = call_modules_hook('on_postload_alert_update', data=alert, caseid=identifier)
  183. if alert.alert_resolution_status_id != case_status_id_mapped:
  184. alert.alert_resolution_status_id = case_status_id_mapped
  185. alert = call_modules_hook('on_postload_alert_resolution_update', data=alert, caseid=identifier)
  186. track_activity(f"closing alert ID {alert.alert_id} due to case #{identifier} being closed",
  187. caseid=identifier, ctx_less=False)
  188. db.session.add(alert)
  189. case = call_modules_hook('on_postload_case_update', data=case, caseid=identifier)
  190. add_obj_history_entry(case, 'case closed')
  191. track_activity("closed case ID {}".format(identifier), caseid=identifier, ctx_less=False)
  192. case_schema = CaseSchema()
  193. return response_success("Case closed successfully", data=case_schema.dump(res))
  194. @manage_cases_rest_blueprint.route('/manage/cases/add', methods=['POST'])
  195. @endpoint_deprecated('POST', '/api/v2/cases')
  196. @ac_api_requires(Permissions.standard_user)
  197. def api_add_case():
  198. case_schema = CaseSchema()
  199. try:
  200. case = cases_create(request.get_json())
  201. return response_success('Case created', data=case_schema.dump(case))
  202. except BusinessProcessingError as e:
  203. return response_error(e.get_message(), data=e.get_data())
  204. @manage_cases_rest_blueprint.route('/manage/cases/list', methods=['GET'])
  205. @ac_api_requires(Permissions.standard_user)
  206. def api_list_case():
  207. data = list_cases_dict(current_user.id)
  208. return response_success("", data=data)
  209. @manage_cases_rest_blueprint.route('/manage/cases/update/<int:identifier>', methods=['POST'])
  210. @endpoint_deprecated('PUT', '/api/v2/cases/<int:identifier>')
  211. @ac_api_requires(Permissions.standard_user)
  212. def update_case_info(identifier):
  213. if not ac_fast_check_current_user_has_case_access(identifier, [CaseAccessLevel.full_access]):
  214. return ac_api_return_access_denied(caseid=identifier)
  215. case_schema = CaseSchema()
  216. try:
  217. case, msg = cases_update(identifier, request.get_json())
  218. return response_success(msg, data=case_schema.dump(case))
  219. except BusinessProcessingError as e:
  220. return response_error(e.get_message(), data=e.get_data())
  221. @manage_cases_rest_blueprint.route('/manage/cases/trigger-pipeline', methods=['POST'])
  222. @ac_api_requires(Permissions.standard_user)
  223. @ac_requires_case_identifier()
  224. def update_case_files(caseid):
  225. if not ac_fast_check_current_user_has_case_access(caseid, [CaseAccessLevel.full_access]):
  226. return ac_api_return_access_denied(caseid=caseid)
  227. # case update request. The files should have already arrived with the request upload_files
  228. try:
  229. # Create the update task
  230. jsdata = request.get_json()
  231. if not jsdata:
  232. return response_error('Not a JSON content')
  233. pipeline = jsdata.get('pipeline')
  234. try:
  235. pipeline_mod = pipeline.split("-")[0]
  236. pipeline_name = pipeline.split("-")[1]
  237. except Exception as e:
  238. log.error(e.__str__())
  239. return response_error('Malformed request')
  240. ppl_config = get_pipelines_args_from_name(pipeline_mod)
  241. if not ppl_config:
  242. return response_error('Malformed request')
  243. pl_args = ppl_config['pipeline_args']
  244. pipeline_args = {}
  245. for argi in pl_args:
  246. arg = argi[0]
  247. fetch_arg = jsdata.get('args_' + arg)
  248. if argi[1] == 'required' and (not fetch_arg or fetch_arg == ""):
  249. return response_error("Required arguments are not set")
  250. if fetch_arg:
  251. pipeline_args[arg] = fetch_arg
  252. else:
  253. pipeline_args[arg] = None
  254. status = task_case_update(
  255. module=pipeline_mod,
  256. pipeline=pipeline_name,
  257. pipeline_args=pipeline_args,
  258. caseid=caseid)
  259. if status.is_success():
  260. # The job has been created, so return. The progress can be followed on the dashboard
  261. return response_success("Case task created")
  262. # We got some errors and cannot continue
  263. return response_error(status.get_message(), data=status.get_data())
  264. except Exception as _:
  265. traceback.print_exc()
  266. return response_error('Fail to update case', data=traceback.print_exc())
  267. @manage_cases_rest_blueprint.route('/manage/cases/upload_files', methods=['POST'])
  268. @ac_api_requires(Permissions.standard_user)
  269. @ac_requires_case_identifier()
  270. def manage_cases_uploadfiles(caseid):
  271. """
  272. Handles the entire the case management, i.e creation, update, list and files imports
  273. :param path: Path within the URL
  274. :return: Depends on the path, either a page a JSON
  275. """
  276. if not ac_fast_check_current_user_has_case_access(caseid, [CaseAccessLevel.full_access]):
  277. return ac_api_return_access_denied(caseid=caseid)
  278. # Files uploads of a case. Get the files, create the folder tree
  279. # The request "add" will start the check + import of the files.
  280. f = request.files.get('file')
  281. is_update = request.form.get('is_update', type=str)
  282. pipeline = request.form.get('pipeline', '', type=str)
  283. try:
  284. pipeline_mod = pipeline.split("-")[0]
  285. except Exception as e:
  286. log.error(e.__str__())
  287. return response_error('Malformed request')
  288. if not iris_module_exists(pipeline_mod):
  289. return response_error('Missing pipeline')
  290. mod, _ = instantiate_module_from_name(pipeline_mod)
  291. status = configure_module_on_init(mod)
  292. if status.is_failure():
  293. return response_error("Path for upload {} is not built ! Unreachable pipeline".format(
  294. os.path.join(f.filename)))
  295. case_customer = None
  296. case_name = None
  297. if is_update == "true":
  298. case = get_case(caseid)
  299. if case:
  300. case_name = case.name
  301. case_customer = case.client.name
  302. else:
  303. case_name = urllib.parse.quote(request.form.get('case_name', '', type=str), safe='')
  304. case_customer = request.form.get('case_customer', type=str)
  305. fpath = build_upload_path(case_customer=case_customer,
  306. case_name=urllib.parse.unquote(case_name),
  307. module=pipeline_mod,
  308. create=is_update
  309. )
  310. f.filename = secure_filename(f.filename)
  311. status = mod.pipeline_files_upload(fpath, f, case_customer, case_name, is_update)
  312. if status.is_success():
  313. return response_success(status.get_message())
  314. return response_error(status.get_message())