暂无描述

notes.py 8.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  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. from datetime import datetime
  19. from flask_login import current_user
  20. from marshmallow import ValidationError
  21. from app import db
  22. from app import app
  23. from app.business.errors import BusinessProcessingError, UnhandledBusinessError
  24. from app.datamgmt.case.case_notes_db import get_note
  25. from app.iris_engine.module_handler.module_handler import call_modules_hook
  26. from app.iris_engine.utils.tracker import track_activity
  27. from app.models.models import NoteRevisions
  28. from app.models.authorization import User
  29. from app.schema.marshables import CaseNoteSchema
  30. from app.util import add_obj_history_entry
  31. def _load(request_data, note_schema=None):
  32. try:
  33. if note_schema is None:
  34. note_schema = CaseNoteSchema()
  35. return note_schema.load(request_data)
  36. except ValidationError as e:
  37. raise BusinessProcessingError('Data error', e.messages)
  38. def notes_create(request_json, case_identifier):
  39. """
  40. Create a note.
  41. :param request_json: The request data.
  42. :param case_identifier: The case identifier.
  43. """
  44. try:
  45. request_data = call_modules_hook('on_preload_note_create', data=request_json, caseid=case_identifier)
  46. note_schema = CaseNoteSchema()
  47. note_schema.verify_directory_id(request_data, caseid=case_identifier)
  48. note = _load(request_data)
  49. note.note_creationdate = datetime.utcnow()
  50. note.note_lastupdate = datetime.utcnow()
  51. note.note_user = current_user.id
  52. note.note_case_id = case_identifier
  53. db.session.add(note)
  54. db.session.flush()
  55. note_revision = NoteRevisions(
  56. note_id=note.note_id,
  57. revision_number=1,
  58. note_title=note.note_title,
  59. note_content=note.note_content,
  60. note_user=note.note_user,
  61. revision_timestamp=datetime.utcnow()
  62. )
  63. db.session.add(note_revision)
  64. add_obj_history_entry(note, 'created note', commit=True)
  65. note = call_modules_hook('on_postload_note_create', data=note, caseid=case_identifier)
  66. track_activity(f"created note \"{note.note_title}\"", caseid=case_identifier)
  67. return note
  68. except ValidationError as e:
  69. raise BusinessProcessingError('Data error', e.messages)
  70. except Exception as e:
  71. raise BusinessProcessingError('Unexpected error server-side', e)
  72. def notes_update(identifier: int = None, request_json: dict = None, case_identifier: int = None):
  73. """
  74. Update a note by its identifier.
  75. :param identifier: The note identifier.
  76. :param request_json: The request data.
  77. :param case_identifier: The case identifier.
  78. """
  79. try:
  80. addnote_schema = CaseNoteSchema()
  81. note = get_note(identifier, caseid=case_identifier)
  82. if not note:
  83. raise BusinessProcessingError("Invalid note ID for this case")
  84. request_data = call_modules_hook('on_preload_note_update', data=request_json, caseid=case_identifier)
  85. latest_version = db.session.query(
  86. NoteRevisions
  87. ).filter_by(
  88. note_id=identifier
  89. ).order_by(
  90. NoteRevisions.revision_number.desc()
  91. ).first()
  92. revision_number = 1 if latest_version is None else latest_version.revision_number + 1
  93. no_changes = False
  94. if revision_number > 1:
  95. if latest_version.note_title == request_data.get('note_title') and latest_version.note_content == request_data.get('note_content'):
  96. no_changes = True
  97. app.logger.debug(f"Note {identifier} has not changed, skipping versioning")
  98. if not no_changes:
  99. note_version = NoteRevisions(
  100. note_id=note.note_id,
  101. revision_number=revision_number,
  102. note_title=note.note_title,
  103. note_content=note.note_content,
  104. note_user=current_user.id,
  105. revision_timestamp=datetime.utcnow()
  106. )
  107. db.session.add(note_version)
  108. db.session.commit()
  109. request_data['note_id'] = identifier
  110. addnote_schema.load(request_data, partial=True, instance=note)
  111. note.update_date = datetime.utcnow()
  112. note.user_id = current_user.id
  113. add_obj_history_entry(note, 'updated note', commit=True)
  114. note = call_modules_hook('on_postload_note_update', data=note, caseid=case_identifier)
  115. track_activity(f"updated note \"{note.note_title}\"", caseid=case_identifier)
  116. return note
  117. except ValidationError as e:
  118. raise BusinessProcessingError('Data error', e.messages)
  119. except Exception as e:
  120. raise UnhandledBusinessError('Unexpected error server-side', str(e))
  121. def notes_list_revisions(identifier: int = None, case_identifier: int = None):
  122. """
  123. List the revisions of a note by its identifier.
  124. :param identifier: The note identifier.
  125. :param case_identifier: The case identifier.
  126. :return: The note history.
  127. """
  128. try:
  129. note = get_note(identifier, caseid=case_identifier)
  130. if not note:
  131. raise BusinessProcessingError("Invalid note ID for this case")
  132. note_versions = NoteRevisions.query.with_entities(
  133. NoteRevisions.revision_number,
  134. NoteRevisions.revision_timestamp,
  135. User.name.label('user_name')
  136. ).join(
  137. User, NoteRevisions.note_user == User.id
  138. ).order_by(
  139. NoteRevisions.revision_number.desc()
  140. ).filter(
  141. NoteRevisions.note_id == identifier
  142. ).all()
  143. return note_versions
  144. except ValidationError as e:
  145. raise BusinessProcessingError('Data error', e.messages)
  146. except Exception as e:
  147. raise UnhandledBusinessError('Unexpected error server-side', str(e))
  148. def notes_get_revision(identifier: int = None, revision_number: int = None, case_identifier: int = None):
  149. """
  150. Get a note revision by its identifier and revision number.
  151. :param identifier: The note identifier.
  152. :param revision_number: The revision number.
  153. :param case_identifier: The case identifier.
  154. :return: The note revision.
  155. """
  156. try:
  157. note = get_note(identifier, caseid=case_identifier)
  158. if not note:
  159. raise BusinessProcessingError("Invalid note ID for this case")
  160. note_revision = NoteRevisions.query.filter(
  161. NoteRevisions.note_id == identifier,
  162. NoteRevisions.revision_number == revision_number
  163. ).first()
  164. return note_revision
  165. except ValidationError as e:
  166. raise BusinessProcessingError('Data error', e.messages)
  167. except Exception as e:
  168. raise UnhandledBusinessError('Unexpected error server-side', str(e))
  169. def notes_delete_revision(identifier: int = None, revision_number: int = None, case_identifier: int = None):
  170. """
  171. Delete a note revision by its identifier and revision number.
  172. :param identifier: The note identifier.
  173. :param revision_number: The revision number.
  174. :param case_identifier: The case identifier.
  175. """
  176. try:
  177. note = get_note(identifier, caseid=case_identifier)
  178. if not note:
  179. raise BusinessProcessingError("Invalid note ID for this case")
  180. note_revision = NoteRevisions.query.filter(
  181. NoteRevisions.note_id == identifier,
  182. NoteRevisions.revision_number == revision_number
  183. ).first()
  184. if not note_revision:
  185. raise BusinessProcessingError("Invalid note revision number")
  186. db.session.delete(note_revision)
  187. db.session.commit()
  188. track_activity(f"deleted note revision {revision_number} of note \"{note.note_title}\"", caseid=case_identifier)
  189. except ValidationError as e:
  190. raise BusinessProcessingError('Data error', e.messages)
  191. except Exception as e:
  192. raise UnhandledBusinessError('Unexpected error server-side', str(e))