From 5daa778e6056b35ba4eed56e0854477688a4445b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 11:28:57 +0200 Subject: [PATCH 01/28] Started implementation of PUT /api/v2/cases/{case_identifier}/events/{identifier} --- source/app/blueprints/rest/v2/case_objects/events.py | 10 ++++++++++ tests/tests_rest_events.py | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index b050ca7ce..d5333ac56 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -77,6 +77,16 @@ def get_event(case_identifier, identifier): return response_api_error(e.get_message(), data=e.get_data()) +@case_events_blueprint.put('/') +@ac_api_requires() +def update_event(case_identifier, identifier): + event = events_get(identifier) + + schema = EventSchema() + result = schema.dump(event) + return response_api_success(result) + + def _check_event_and_case_identifier_match(event: CasesEvent, case_identifier): if event.case_id != case_identifier: raise BusinessProcessingError(f'Event {event.event_id} does not belong to case {case_identifier}') diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 316d24edf..ed74f9536 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -168,3 +168,14 @@ def test_get_event_should_return_children_when_event_is_parent_of_another_event( child_identifier = response['event_id'] response = self._subject.get(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() self.assertEqual(child_identifier, response['children'][0]['event_id']) + + def test_update_event_should_return_200(self): + case_identifier = self._subject.create_dummy_case() + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', {}) + self.assertEqual(200, response.status_code) From c4c6c5cb9773b4bd4412dc4d7b496f56f0cef629 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:26:59 +0200 Subject: [PATCH 02/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should update field title --- .../rest/case/case_timeline_routes.py | 43 +++-------------- .../blueprints/rest/v2/case_objects/events.py | 2 + source/app/business/events.py | 47 ++++++++++++++++--- tests/tests_rest_events.py | 14 +++++- 4 files changed, 62 insertions(+), 44 deletions(-) diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 680e02176..1766ec137 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -74,6 +74,7 @@ from app.blueprints.responses import response_success from app.business.errors import BusinessProcessingError from app.business.events import events_create +from app.business.events import events_update case_timeline_rest_blueprint = Blueprint('case_timeline_rest', __name__) @@ -692,42 +693,10 @@ def case_edit_event(cur_id, caseid): if not event: return response_error("Invalid event ID for this case") - event_schema = EventSchema() - - request_data = call_modules_hook('on_preload_event_update', data=request.get_json(), caseid=caseid) + request_json = request.get_json() + event = events_update(event, request_json) - request_data['event_id'] = cur_id - event = event_schema.load(request_data, instance=event) - - event.event_date, event.event_date_wtz = event_schema.validate_date( - request_data.get(u'event_date'), - request_data.get(u'event_tz') - ) - - event.case_id = caseid - add_obj_history_entry(event, 'updated') - - update_timeline_state(caseid=caseid) - db.session.commit() - - save_event_category(event.event_id, request_data.get('event_category_id')) - - setattr(event, 'event_category_id', request_data.get('event_category_id')) - - success, log = update_event_assets(event.event_id, caseid, request_data.get('event_assets'), - request_data.get('event_iocs'), request_data.get('event_sync_iocs_assets')) - if not success: - return response_error('Error while saving linked assets', data=log) - - success, log = update_event_iocs(event_id=event.event_id, - caseid=caseid, - iocs_list=request_data.get('event_iocs')) - if not success: - return response_error('Error while saving linked iocs', data=log) - - event = call_modules_hook('on_postload_event_update', data=event, caseid=caseid) - - track_activity(f"updated event \"{event.event_title}\"", caseid=caseid) + event_schema = EventSchema() event_dump = event_schema.dump(event) collab_notify(case_id=caseid, object_type='events', @@ -737,8 +706,8 @@ def case_edit_event(cur_id, caseid): return response_success("Event updated", data=event_dump) - except marshmallow.exceptions.ValidationError as e: - return response_error(msg="Data error", data=e.normalized_messages()) + except BusinessProcessingError as e: + return response_error(e.get_message(), data=e.get_data()) @case_timeline_rest_blueprint.route('/case/timeline/events/add', methods=['POST']) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index d5333ac56..55188efb5 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -27,6 +27,7 @@ from app.blueprints.access_controls import ac_api_return_access_denied from app.business.events import events_create from app.business.events import events_get +from app.business.events import events_update from app.models.cases import CasesEvent from app.schema.marshables import EventSchema from app.business.errors import BusinessProcessingError @@ -81,6 +82,7 @@ def get_event(case_identifier, identifier): @ac_api_requires() def update_event(case_identifier, identifier): event = events_get(identifier) + event = events_update(event, request.get_json()) schema = EventSchema() result = schema.dump(event) diff --git a/source/app/business/events.py b/source/app/business/events.py index d70236c5f..84700fbd2 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -18,6 +18,7 @@ from datetime import datetime +import marshmallow from flask_login import current_user from marshmallow.exceptions import ValidationError @@ -38,8 +39,11 @@ def _load(request_data, **kwargs): try: - evidence_schema = EventSchema() - return evidence_schema.load(request_data, **kwargs) + schema = EventSchema() + event = schema.load(request_data, **kwargs) + event.event_date, event.event_date_wtz = schema.validate_date(request_data.get(u'event_date'), + request_data.get(u'event_tz')) + return event except ValidationError as e: raise BusinessProcessingError('Data error', data=e.normalized_messages()) @@ -48,10 +52,6 @@ def events_create(case_identifier, request_json) -> CasesEvent: request_data = call_modules_hook('on_preload_event_create', data=request_json, caseid=case_identifier) event = _load(request_data) - # TODO this should probably rather be done in the API layer - event_schema = EventSchema() - event.event_date, event.event_date_wtz = event_schema.validate_date(request_data.get(u'event_date'), - request_data.get(u'event_tz')) event.case_id = case_identifier event.event_added = datetime.utcnow() @@ -92,3 +92,38 @@ def events_get(identifier) -> CasesEvent: if not event: raise ObjectNotFoundError() return event + + +def events_update(event: CasesEvent, request_json: dict) -> CasesEvent: + try: + request_data = call_modules_hook('on_preload_event_update', data=request_json, caseid=event.case_id) + + request_data['event_id'] = event.event_id + event = _load(request_data, instance=event) + + add_obj_history_entry(event, 'updated') + + update_timeline_state(caseid=event.case_id) + db.session.commit() + + save_event_category(event.event_id, request_data.get('event_category_id')) + + setattr(event, 'event_category_id', request_data.get('event_category_id')) + + success, log = update_event_assets(event.event_id, event.case_id, request_data.get('event_assets'), + request_data.get('event_iocs'), request_data.get('event_sync_iocs_assets')) + if not success: + raise BusinessProcessingError('Error while saving linked assets', data=log) + + success, log = update_event_iocs(event_id=event.event_id, + caseid=event.case_id, + iocs_list=request_data.get('event_iocs')) + if not success: + raise BusinessProcessingError('Error while saving linked iocs', data=log) + + event = call_modules_hook('on_postload_event_update', data=event, caseid=event.case_id) + + track_activity(f"updated event \"{event.event_title}\"", caseid=event.case_id) + return event + except marshmallow.exceptions.ValidationError as e: + raise BusinessProcessingError('Data error', data=e.normalized_messages()) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index ed74f9536..f742c2c2d 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -170,7 +170,6 @@ def test_get_event_should_return_children_when_event_is_parent_of_another_event( self.assertEqual(child_identifier, response['children'][0]['event_id']) def test_update_event_should_return_200(self): - case_identifier = self._subject.create_dummy_case() case_identifier = self._subject.create_dummy_case() body = {'event_title': 'title', 'event_category_id': 1, 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', @@ -179,3 +178,16 @@ def test_update_event_should_return_200(self): identifier = response['event_id'] response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', {}) self.assertEqual(200, response.status_code) + + def test_update_event_should_change_event_title(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() + self.assertEqual('new title', response['event_title']) From 20cde85935b14d272cf15f83cbc918d33865f96c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:38:21 +0200 Subject: [PATCH 03/28] Updated test --- tests/tests_rest_events.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index f742c2c2d..9143114bf 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -176,7 +176,10 @@ def test_update_event_should_return_200(self): 'event_assets': [], 'event_iocs': []} response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() identifier = response['event_id'] - response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', {}) + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) self.assertEqual(200, response.status_code) def test_update_event_should_change_event_title(self): From 2c1b9b25d751c5bfee9870ac10e1a1949a96b101 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:41:58 +0200 Subject: [PATCH 04/28] Syntax formatting --- source/app/iris_engine/utils/collab.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/source/app/iris_engine/utils/collab.py b/source/app/iris_engine/utils/collab.py index 15d02cb5c..1c3771fc3 100644 --- a/source/app/iris_engine/utils/collab.py +++ b/source/app/iris_engine/utils/collab.py @@ -11,13 +11,10 @@ def collab_notify(case_id: int, request_sid: int = None ): room = f"case-{case_id}" - app.socket_io.emit('case-obj-notif', - json.dumps({ - 'object_id': object_id, - 'action_type': action_type, - 'object_type': object_type, - 'object_data': object_data - }), - room=room, - to=room, - skip_sid=request_sid) + data = json.dumps({ + 'object_id': object_id, + 'action_type': action_type, + 'object_type': object_type, + 'object_data': object_data + }) + app.socket_io.emit('case-obj-notif', data, room=room, to=room, skip_sid=request_sid) From b93b90eb50e71c408649ff7828f1c26d136182d4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 13:42:40 +0200 Subject: [PATCH 05/28] Made import more precise --- source/app/iris_engine/utils/collab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/iris_engine/utils/collab.py b/source/app/iris_engine/utils/collab.py index 1c3771fc3..57f34bf3d 100644 --- a/source/app/iris_engine/utils/collab.py +++ b/source/app/iris_engine/utils/collab.py @@ -1,6 +1,6 @@ import json -import app +from app import socket_io def collab_notify(case_id: int, @@ -17,4 +17,4 @@ def collab_notify(case_id: int, 'object_type': object_type, 'object_data': object_data }) - app.socket_io.emit('case-obj-notif', data, room=room, to=room, skip_sid=request_sid) + socket_io.emit('case-obj-notif', data, room=room, to=room, skip_sid=request_sid) From 686b1cd737bfbd8ec171f5e4ed70c1d09201ce4c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 4 Apr 2025 15:43:28 +0200 Subject: [PATCH 06/28] Correctly renamed method --- source/app/blueprints/rest/v2/case_objects/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index 55188efb5..eca81eeea 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -42,7 +42,7 @@ @case_events_blueprint.post('') @ac_api_requires() -def create_evidence(case_identifier): +def create_event(case_identifier): if not cases_exists(case_identifier): return response_api_not_found() if not ac_fast_check_current_user_has_case_access(case_identifier, [CaseAccessLevel.full_access]): From 1436664abaf0d9d99f4992378f6333ebffe42148 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:40:27 +0200 Subject: [PATCH 07/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should send a socket_io message --- source/app/__init__.py | 2 ++ .../blueprints/rest/v2/case_objects/events.py | 3 ++ source/app/iris_engine/utils/collab.py | 9 ++--- tests/iris.py | 7 +++- tests/requirements.txt | 1 + tests/socket_io_client.py | 34 +++++++++++++++++++ tests/tests_rest_events.py | 24 +++++++++++++ 7 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 tests/socket_io_client.py diff --git a/source/app/__init__.py b/source/app/__init__.py index 9b7e756c4..8f5f87dcf 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -170,3 +170,5 @@ def after_request(response): lm.user_loader(load_user) lm.request_loader(load_user_from_request) + +from app.blueprints.socket_io_event_handlers.collab import socket_join_case_obj_notif diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index eca81eeea..07c05ff06 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -34,6 +34,7 @@ from app.business.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access +from app.iris_engine.utils.collab import collab_notify from app.models.authorization import CaseAccessLevel @@ -86,6 +87,8 @@ def update_event(case_identifier, identifier): schema = EventSchema() result = schema.dump(event) + collab_notify(case_identifier, 'events', 'updated', identifier, object_data=result) + return response_api_success(result) diff --git a/source/app/iris_engine/utils/collab.py b/source/app/iris_engine/utils/collab.py index 57f34bf3d..3fa90c879 100644 --- a/source/app/iris_engine/utils/collab.py +++ b/source/app/iris_engine/utils/collab.py @@ -3,13 +3,8 @@ from app import socket_io -def collab_notify(case_id: int, - object_type: str, - action_type: str, - object_id, - object_data: json = None, - request_sid: int = None - ): +def collab_notify(case_id: int, object_type: str, action_type: str, object_id, + object_data: json = None, request_sid: int = None): room = f"case-{case_id}" data = json.dumps({ 'object_id': object_id, diff --git a/tests/iris.py b/tests/iris.py index fee320958..f764eb314 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -16,11 +16,12 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from uuid import uuid4 from pathlib import Path from docker_compose import DockerCompose from rest_api import RestApi from user import User -from uuid import uuid4 +from socket_io_client import SocketIOClient API_URL = 'http://127.0.0.1:8000' # TODO SSOT: this should be directly read from the .env file @@ -38,6 +39,10 @@ def __init__(self): # TODO remove this field and use _administrator instead self._api = RestApi(API_URL, _API_KEY) self._administrator = User(API_URL, _API_KEY, _ADMINISTRATOR_USER_IDENTIFIER) + self._socket_io_client = SocketIOClient(API_URL, _API_KEY) + + def get_socket_io_client(self): + return self._socket_io_client def create(self, path, body, query_parameters=None): return self._api.post(path, body, query_parameters) diff --git a/tests/requirements.txt b/tests/requirements.txt index 4d6d5ef1a..ece97a948 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1 +1,2 @@ requests >= 2.31.0, < 3.0.0 +python-socketio[client] \ No newline at end of file diff --git a/tests/socket_io_client.py b/tests/socket_io_client.py new file mode 100644 index 000000000..806f2af02 --- /dev/null +++ b/tests/socket_io_client.py @@ -0,0 +1,34 @@ +# IRIS Source Code +# Copyright (C) 2023 - DFIR-IRIS +# contact@dfir-iris.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from socketio import SimpleClient + + +class SocketIOClient: + + def __init__(self, url, api_key): + self._url = url + self._api_key = api_key + self._client = SimpleClient() + + def __enter__(self): + self._client.connect(self._url, headers={'Authorization': f'Bearer {self._api_key}'}) + return self._client + + def __exit__(self, type, value, traceback): + self._client.disconnect() diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 9143114bf..f715a01ab 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -17,7 +17,11 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from unittest import TestCase +from socketio import SimpleClient +import json + from iris import Iris +from iris import _API_KEY _IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 @@ -194,3 +198,23 @@ def test_update_event_should_change_event_title(self): 'event_assets': [], 'event_iocs': []} response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() self.assertEqual('new title', response['event_title']) + + def test_update_event_should_change_send_socket_io_message(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + + with self._subject.get_socket_io_client() as socket_io_client: + socket_io_client.emit('join-case-obj-notif', {'channel': f'case-{case_identifier}'}) + + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() + + message = socket_io_client.receive(timeout=60) + message_content = json.loads(message[1]) + self.assertEqual(identifier, message_content['object_id']) From 4dbdb39b27f57decda647b1a649df62cd5a6e583 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Tue, 8 Apr 2025 17:50:09 +0200 Subject: [PATCH 08/28] Simplified test using socket_io --- tests/iris.py | 6 +++--- tests/socket_io_client.py | 14 ++++++++++--- tests/socket_io_context_manager.py | 32 ++++++++++++++++++++++++++++++ tests/tests_rest_events.py | 10 +++------- 4 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 tests/socket_io_context_manager.py diff --git a/tests/iris.py b/tests/iris.py index f764eb314..0aa46be9d 100644 --- a/tests/iris.py +++ b/tests/iris.py @@ -21,7 +21,7 @@ from docker_compose import DockerCompose from rest_api import RestApi from user import User -from socket_io_client import SocketIOClient +from socket_io_context_manager import SocketIOContextManager API_URL = 'http://127.0.0.1:8000' # TODO SSOT: this should be directly read from the .env file @@ -39,9 +39,9 @@ def __init__(self): # TODO remove this field and use _administrator instead self._api = RestApi(API_URL, _API_KEY) self._administrator = User(API_URL, _API_KEY, _ADMINISTRATOR_USER_IDENTIFIER) - self._socket_io_client = SocketIOClient(API_URL, _API_KEY) + self._socket_io_client = SocketIOContextManager(API_URL, _API_KEY) - def get_socket_io_client(self): + def get_socket_io_client(self) -> SocketIOContextManager: return self._socket_io_client def create(self, path, body, query_parameters=None): diff --git a/tests/socket_io_client.py b/tests/socket_io_client.py index 806f2af02..d474a2252 100644 --- a/tests/socket_io_client.py +++ b/tests/socket_io_client.py @@ -16,6 +16,7 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from json import loads from socketio import SimpleClient @@ -26,9 +27,16 @@ def __init__(self, url, api_key): self._api_key = api_key self._client = SimpleClient() - def __enter__(self): + def connect(self): self._client.connect(self._url, headers={'Authorization': f'Bearer {self._api_key}'}) - return self._client - def __exit__(self, type, value, traceback): + def emit(self, event, channel): + self._client.emit(event, {'channel': channel}) + + def receive(self): + message = self._client.receive(timeout=60) + print(message) + return loads(message[1]) + + def disconnect(self): self._client.disconnect() diff --git a/tests/socket_io_context_manager.py b/tests/socket_io_context_manager.py new file mode 100644 index 000000000..b6fa2f11f --- /dev/null +++ b/tests/socket_io_context_manager.py @@ -0,0 +1,32 @@ +# IRIS Source Code +# Copyright (C) 2023 - DFIR-IRIS +# contact@dfir-iris.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +from socket_io_client import SocketIOClient + + +class SocketIOContextManager: + + def __init__(self, url, api_key): + self._client = SocketIOClient(url, api_key) + + def __enter__(self) -> SocketIOClient: + self._client.connect() + return self._client + + def __exit__(self, type, value, traceback): + self._client.disconnect() diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index f715a01ab..9f7b92a8a 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -17,11 +17,8 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from unittest import TestCase -from socketio import SimpleClient -import json from iris import Iris -from iris import _API_KEY _IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 @@ -208,13 +205,12 @@ def test_update_event_should_change_send_socket_io_message(self): identifier = response['event_id'] with self._subject.get_socket_io_client() as socket_io_client: - socket_io_client.emit('join-case-obj-notif', {'channel': f'case-{case_identifier}'}) + socket_io_client.emit('join-case-obj-notif', f'case-{case_identifier}') body = {'event_title': 'new title', 'event_category_id': 1, 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', 'event_assets': [], 'event_iocs': []} self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() - message = socket_io_client.receive(timeout=60) - message_content = json.loads(message[1]) - self.assertEqual(identifier, message_content['object_id']) + message = socket_io_client.receive() + self.assertEqual(identifier, message['object_id']) From 3d9f519a7e29065e7df4b26a51dd059fdb907d20 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:12:23 +0200 Subject: [PATCH 09/28] [FIX] Put back missing socket event handlers on notes --- source/app/__init__.py | 1 + tests/socket_io_client.py | 6 +++--- tests/tests_rest_events.py | 4 ++++ tests/tests_rest_notes.py | 9 +++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index 8f5f87dcf..c36af10e6 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -171,4 +171,5 @@ def after_request(response): lm.user_loader(load_user) lm.request_loader(load_user_from_request) +from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import socket_join_overview from app.blueprints.socket_io_event_handlers.collab import socket_join_case_obj_notif diff --git a/tests/socket_io_client.py b/tests/socket_io_client.py index d474a2252..50252b450 100644 --- a/tests/socket_io_client.py +++ b/tests/socket_io_client.py @@ -16,7 +16,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -from json import loads from socketio import SimpleClient @@ -31,12 +30,13 @@ def connect(self): self._client.connect(self._url, headers={'Authorization': f'Bearer {self._api_key}'}) def emit(self, event, channel): + print(f'==> {event}/{channel}') self._client.emit(event, {'channel': channel}) def receive(self): message = self._client.receive(timeout=60) - print(message) - return loads(message[1]) + print(f'<== {message[0]}/{message[1]}') + return message[1] def disconnect(self): self._client.disconnect() diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 9f7b92a8a..fbed7245f 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -17,6 +17,7 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from unittest import TestCase +from json import loads from iris import Iris @@ -213,4 +214,7 @@ def test_update_event_should_change_send_socket_io_message(self): self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() message = socket_io_client.receive() + + # TODO ideally, this should not be necessary... Change in the code (careful, API impact) + message = loads(message) self.assertEqual(identifier, message['object_id']) diff --git a/tests/tests_rest_notes.py b/tests/tests_rest_notes.py index 5647cc750..089004487 100644 --- a/tests/tests_rest_notes.py +++ b/tests/tests_rest_notes.py @@ -256,3 +256,12 @@ def test_get_note_should_return_404_when_note_is_deleted(self): self._subject.delete(f'/api/v2/cases/{case_identifier}/notes/{identifier}') response = self._subject.get(f'/api/v2/cases/{case_identifier}/notes/{identifier}') self.assertEqual(404, response.status_code) + + def test_socket_io_join_notes_overview_should_not_fail(self): + case_identifier = self._subject.create_dummy_case() + + with self._subject.get_socket_io_client() as socket_io_client: + socket_io_client.emit('join-notes-overview', f'case-{case_identifier}-notes') + message = socket_io_client.receive() + self.assertEqual('administrator', message['user']) + From f33cd2de7058dc0cff9822fd8be3e8b55fd19a3d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Tue, 8 Apr 2025 18:27:17 +0200 Subject: [PATCH 10/28] [FIX] Put back missing socket event handlers --- source/app/__init__.py | 6 ++++-- tests/socket_io_client.py | 2 +- tests/tests_rest_events.py | 8 ++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index c36af10e6..abdbc6dd8 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -171,5 +171,7 @@ def after_request(response): lm.user_loader(load_user) lm.request_loader(load_user_from_request) -from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import socket_join_overview -from app.blueprints.socket_io_event_handlers.collab import socket_join_case_obj_notif +# TODO should not disable ruff, but the socket io event handlers must be initialized somehow +from app.blueprints.socket_io_event_handlers.case_event_handlers import get_message # noqa: F401 +from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import socket_join_overview # noqa: F401 +from app.blueprints.socket_io_event_handlers.collab import socket_join_case_obj_notif # noqa: F401 diff --git a/tests/socket_io_client.py b/tests/socket_io_client.py index 50252b450..03bcec3bc 100644 --- a/tests/socket_io_client.py +++ b/tests/socket_io_client.py @@ -34,7 +34,7 @@ def emit(self, event, channel): self._client.emit(event, {'channel': channel}) def receive(self): - message = self._client.receive(timeout=60) + message = self._client.receive(timeout=20) print(f'<== {message[0]}/{message[1]}') return message[1] diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index fbed7245f..63b32bae7 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -218,3 +218,11 @@ def test_update_event_should_change_send_socket_io_message(self): # TODO ideally, this should not be necessary... Change in the code (careful, API impact) message = loads(message) self.assertEqual(identifier, message['object_id']) + + def test_socket_io_join_should_not_fail(self): + case_identifier = self._subject.create_dummy_case() + + with self._subject.get_socket_io_client() as socket_io_client: + socket_io_client.emit('join', f'case-{case_identifier}') + message = socket_io_client.receive() + self.assertEqual('administrator just joined', message['message']) From 5260b941b0d2cda58c1b3af5d9f920a957b5babb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:30:59 +0200 Subject: [PATCH 11/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should return 403 when user has no access to case --- .../blueprints/rest/v2/case_objects/events.py | 3 +++ tests/tests_rest_events.py | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index 07c05ff06..f04d69895 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -83,6 +83,9 @@ def get_event(case_identifier, identifier): @ac_api_requires() def update_event(case_identifier, identifier): event = events_get(identifier) + if not ac_fast_check_current_user_has_case_access(event.case_id, [CaseAccessLevel.full_access]): + return ac_api_return_access_denied(caseid=event.case_id) + event = events_update(event, request.get_json()) schema = EventSchema() diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 63b32bae7..21bfd6f0d 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -226,3 +226,19 @@ def test_socket_io_join_should_not_fail(self): socket_io_client.emit('join', f'case-{case_identifier}') message = socket_io_client.receive() self.assertEqual('administrator just joined', message['message']) + + def test_update_event_should_return_403_when_user_has_no_permission_to_access_case(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + + user = self._subject.create_dummy_user() + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = user.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) + self.assertEqual(403, response.status_code) + From 639e4776a3f310c4d04550cffedbde9b5ae11f5d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:36:56 +0200 Subject: [PATCH 12/28] POST /api/v2/cases/{case_identifier}/events should send socketIO notification --- .../app/blueprints/rest/v2/case_objects/events.py | 6 +++++- source/app/iris_engine/utils/collab.py | 13 ++++++++++++- tests/tests_rest_events.py | 15 +++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index f04d69895..b04d93f13 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -35,6 +35,7 @@ from app.business.cases import cases_exists from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access from app.iris_engine.utils.collab import collab_notify +from app.iris_engine.utils.collab import notify from app.models.authorization import CaseAccessLevel @@ -52,7 +53,10 @@ def create_event(case_identifier): try: event = events_create(case_identifier, request.get_json()) schema = EventSchema() - return response_api_created(schema.dump(event)) + result = schema.dump(event) + notify(case_identifier, 'events', 'updated', event.event_id, object_data=result) + + return response_api_created(result) except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) diff --git a/source/app/iris_engine/utils/collab.py b/source/app/iris_engine/utils/collab.py index 3fa90c879..1c9e863d5 100644 --- a/source/app/iris_engine/utils/collab.py +++ b/source/app/iris_engine/utils/collab.py @@ -5,7 +5,7 @@ def collab_notify(case_id: int, object_type: str, action_type: str, object_id, object_data: json = None, request_sid: int = None): - room = f"case-{case_id}" + room = f'case-{case_id}' data = json.dumps({ 'object_id': object_id, 'action_type': action_type, @@ -13,3 +13,14 @@ def collab_notify(case_id: int, object_type: str, action_type: str, object_id, 'object_data': object_data }) socket_io.emit('case-obj-notif', data, room=room, to=room, skip_sid=request_sid) + + +def notify(case_identifier: int, object_type: str, action_type: str, object_id, object_data: json = None): + room = f'case-{case_identifier}' + data = { + 'object_id': object_id, + 'action_type': action_type, + 'object_type': object_type, + 'object_data': object_data + } + socket_io.emit('case-obj-notif', data, room=room, to=room) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 21bfd6f0d..e9e773185 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -87,6 +87,21 @@ def test_create_event_should_set_event_parent_id_when_provided(self): response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() self.assertEqual(identifier, response['parent_event_id']) + def test_create_event_should_change_send_socket_io_message(self): + case_identifier = self._subject.create_dummy_case() + + with self._subject.get_socket_io_client() as socket_io_client: + socket_io_client.emit('join-case-obj-notif', f'case-{case_identifier}') + + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + + message = socket_io_client.receive() + self.assertEqual(identifier, message['object_id']) + def test_get_event_should_return_200(self): case_identifier = self._subject.create_dummy_case() body = {'event_title': 'title', 'event_category_id': 1, From ce7ebd92d987564eb439c6110470266623bd804d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:41:34 +0200 Subject: [PATCH 13/28] Socket IO event sent during event update should directly be a json instead of a string to decode --- source/app/blueprints/rest/v2/case_objects/events.py | 3 +-- tests/tests_rest_events.py | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index b04d93f13..ec70391c6 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -34,7 +34,6 @@ from app.business.errors import ObjectNotFoundError from app.business.cases import cases_exists from app.iris_engine.access_control.utils import ac_fast_check_current_user_has_case_access -from app.iris_engine.utils.collab import collab_notify from app.iris_engine.utils.collab import notify from app.models.authorization import CaseAccessLevel @@ -94,7 +93,7 @@ def update_event(case_identifier, identifier): schema = EventSchema() result = schema.dump(event) - collab_notify(case_identifier, 'events', 'updated', identifier, object_data=result) + notify(case_identifier, 'events', 'updated', identifier, object_data=result) return response_api_success(result) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index e9e773185..03efd240f 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -17,7 +17,6 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from unittest import TestCase -from json import loads from iris import Iris @@ -230,8 +229,6 @@ def test_update_event_should_change_send_socket_io_message(self): message = socket_io_client.receive() - # TODO ideally, this should not be necessary... Change in the code (careful, API impact) - message = loads(message) self.assertEqual(identifier, message['object_id']) def test_socket_io_join_should_not_fail(self): From c8842cbf700343f82b8f9094be4ce94834d3faca Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:45:48 +0200 Subject: [PATCH 14/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should return 404 when event is not found --- .../blueprints/rest/v2/case_objects/events.py | 20 +++++++++++-------- tests/tests_rest_events.py | 7 +++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index ec70391c6..cae71b90e 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -85,17 +85,21 @@ def get_event(case_identifier, identifier): @case_events_blueprint.put('/') @ac_api_requires() def update_event(case_identifier, identifier): - event = events_get(identifier) - if not ac_fast_check_current_user_has_case_access(event.case_id, [CaseAccessLevel.full_access]): - return ac_api_return_access_denied(caseid=event.case_id) - event = events_update(event, request.get_json()) + try: + event = events_get(identifier) + if not ac_fast_check_current_user_has_case_access(event.case_id, [CaseAccessLevel.full_access]): + return ac_api_return_access_denied(caseid=event.case_id) - schema = EventSchema() - result = schema.dump(event) - notify(case_identifier, 'events', 'updated', identifier, object_data=result) + event = events_update(event, request.get_json()) - return response_api_success(result) + schema = EventSchema() + result = schema.dump(event) + notify(case_identifier, 'events', 'updated', identifier, object_data=result) + + return response_api_success(result) + except ObjectNotFoundError: + return response_api_not_found() def _check_event_and_case_identifier_match(event: CasesEvent, case_identifier): diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 03efd240f..23ab7ebdc 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -254,3 +254,10 @@ def test_update_event_should_return_403_when_user_has_no_permission_to_access_ca response = user.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) self.assertEqual(403, response.status_code) + def test_update_event_should_return_404_when_event_does_not_exist(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}', body) + self.assertEqual(404, response.status_code) From bfc0e085f1d2936f1ea376152a44eb4d827e686e Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:50:24 +0200 Subject: [PATCH 15/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should return 404 when case is not found --- .../app/blueprints/rest/v2/case_objects/events.py | 2 ++ tests/tests_rest_events.py | 13 +++++++++++++ 2 files changed, 15 insertions(+) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index cae71b90e..45ec05377 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -85,6 +85,8 @@ def get_event(case_identifier, identifier): @case_events_blueprint.put('/') @ac_api_requires() def update_event(case_identifier, identifier): + if not cases_exists(case_identifier): + return response_api_not_found() try: event = events_get(identifier) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 23ab7ebdc..0d4d9ec07 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -261,3 +261,16 @@ def test_update_event_should_return_404_when_event_does_not_exist(self): 'event_assets': [], 'event_iocs': []} response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}', body) self.assertEqual(404, response.status_code) + + def test_update_event_should_return_404_when_case_does_not_exist(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}/events/{identifier}', body) + self.assertEqual(404, response.status_code) From 3a6d66533057c479c9bbd467c3c02595a9e161bb Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:17:35 +0200 Subject: [PATCH 16/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should return 400 when the date format is incorrect --- .../app/blueprints/rest/v2/case_objects/events.py | 2 ++ tests/tests_rest_events.py | 13 +++++++++++++ tests/tests_rest_evidences.py | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index 45ec05377..ba2ea0d57 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -102,6 +102,8 @@ def update_event(case_identifier, identifier): return response_api_success(result) except ObjectNotFoundError: return response_api_not_found() + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) def _check_event_and_case_identifier_match(event: CasesEvent, case_identifier): diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 0d4d9ec07..4cb26fac2 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -274,3 +274,16 @@ def test_update_event_should_return_404_when_case_does_not_exist(self): 'event_assets': [], 'event_iocs': []} response = self._subject.update(f'/api/v2/cases/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}/events/{identifier}', body) self.assertEqual(404, response.status_code) + + def test_update_event_should_return_400_when_event_date_format_is_incorrect(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '1744181930.204785', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) + self.assertEqual(400, response.status_code) diff --git a/tests/tests_rest_evidences.py b/tests/tests_rest_evidences.py index 698453143..a4ee16134 100644 --- a/tests/tests_rest_evidences.py +++ b/tests/tests_rest_evidences.py @@ -262,8 +262,8 @@ def test_update_evidence_should_return_400_when_case_identifier_does_not_match_e body = {'filename': 'filename'} response = self._subject.create(f'/api/v2/cases/{case_identifier}/evidences', body).json() identifier = response['id'] - body = {'filename': 'filename2'} case_identifier2 = self._subject.create_dummy_case() + body = {'filename': 'filename2'} response = self._subject.update(f'/api/v2/cases/{case_identifier2}/evidences/{identifier}', body) self.assertEqual(400, response.status_code) From bcc48f8f97ff91f738a63be2dfe21bd0c83e7028 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:25:26 +0200 Subject: [PATCH 17/28] PUT /api/v2/cases/{case_identifier}/events/{identifier} should return 400 when case_identifier and the event case identifier do not match --- .../app/blueprints/rest/v2/case_objects/events.py | 3 ++- tests/tests_rest_events.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index ba2ea0d57..665c030e2 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -92,7 +92,8 @@ def update_event(case_identifier, identifier): event = events_get(identifier) if not ac_fast_check_current_user_has_case_access(event.case_id, [CaseAccessLevel.full_access]): return ac_api_return_access_denied(caseid=event.case_id) - + _check_event_and_case_identifier_match(event, case_identifier) + event = events_update(event, request.get_json()) schema = EventSchema() diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index 4cb26fac2..b89bf56ce 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -287,3 +287,17 @@ def test_update_event_should_return_400_when_event_date_format_is_incorrect(self 'event_assets': [], 'event_iocs': []} response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) self.assertEqual(400, response.status_code) + + def test_update_event_should_return_400_when_case_identifier_does_not_match_event_case(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + case_identifier2 = self._subject.create_dummy_case() + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier2}/events/{identifier}', body) + self.assertEqual(400, response.status_code) From e433e5a6f719fcca2020236634138a063d1c6a1f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:26:47 +0200 Subject: [PATCH 18/28] Deprecated POST /case/timeline/events/update/{cur_id} in favor of PUT /api/v2/cases/{case_identifier}/events/{identifier} --- source/app/blueprints/rest/case/case_timeline_routes.py | 1 + source/app/blueprints/rest/v2/case_objects/events.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/case/case_timeline_routes.py b/source/app/blueprints/rest/case/case_timeline_routes.py index 1766ec137..af08122e2 100644 --- a/source/app/blueprints/rest/case/case_timeline_routes.py +++ b/source/app/blueprints/rest/case/case_timeline_routes.py @@ -685,6 +685,7 @@ def event_view(cur_id, caseid): @case_timeline_rest_blueprint.route('/case/timeline/events/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/cases/{case_identifier}/events/{identifier}') @ac_requires_case_identifier(CaseAccessLevel.full_access) @ac_api_requires() def case_edit_event(cur_id, caseid): diff --git a/source/app/blueprints/rest/v2/case_objects/events.py b/source/app/blueprints/rest/v2/case_objects/events.py index 665c030e2..c89c0a077 100644 --- a/source/app/blueprints/rest/v2/case_objects/events.py +++ b/source/app/blueprints/rest/v2/case_objects/events.py @@ -93,7 +93,7 @@ def update_event(case_identifier, identifier): if not ac_fast_check_current_user_has_case_access(event.case_id, [CaseAccessLevel.full_access]): return ac_api_return_access_denied(caseid=event.case_id) _check_event_and_case_identifier_match(event, case_identifier) - + event = events_update(event, request.get_json()) schema = EventSchema() From 0b5c53363bb03f09a77a2cf89af36ef653b3769b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 09:55:33 +0200 Subject: [PATCH 19/28] PUT /api/v2/cases/{case_identifier}/events/{identifier}: added tests when fields are missing --- tests/tests_rest_events.py | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index b89bf56ce..bb9efff2d 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -301,3 +301,42 @@ def test_update_event_should_return_400_when_case_identifier_does_not_match_even 'event_assets': [], 'event_iocs': []} response = self._subject.update(f'/api/v2/cases/{case_identifier2}/events/{identifier}', body) self.assertEqual(400, response.status_code) + + def test_update_event_should_return_400_when_field_event_category_id_is_missing(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) + self.assertEqual(400, response.status_code) + + def test_update_event_should_return_400_when_field_event_assets_is_missing(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_iocs': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) + self.assertEqual(400, response.status_code) + + def test_update_event_should_return_400_when_field_event_iocs_is_missing(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': []} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) + self.assertEqual(400, response.status_code) From dccef513ac0a8d7f9818a0719b1ae584cd2be140 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 10:01:24 +0200 Subject: [PATCH 20/28] PUT /api/v2/cases/{case_identifier}/events/{identifier}: added test with parent_event_id --- tests/tests_rest_events.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/tests_rest_events.py b/tests/tests_rest_events.py index bb9efff2d..c049b37b8 100644 --- a/tests/tests_rest_events.py +++ b/tests/tests_rest_events.py @@ -340,3 +340,22 @@ def test_update_event_should_return_400_when_field_event_iocs_is_missing(self): 'event_assets': []} response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body) self.assertEqual(400, response.status_code) + + def test_update_event_should_set_event_parent_id_when_provided(self): + case_identifier = self._subject.create_dummy_case() + body = {'event_title': 'title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + parent_event_identifier = response['event_id'] + body = {'event_title': 'title2', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': []} + response = self._subject.create(f'/api/v2/cases/{case_identifier}/events', body).json() + identifier = response['event_id'] + body = {'event_title': 'new title', 'event_category_id': 1, + 'event_date': '2025-03-26T00:00:00.000', 'event_tz': '+00:00', + 'event_assets': [], 'event_iocs': [], + 'parent_event_id': parent_event_identifier} + response = self._subject.update(f'/api/v2/cases/{case_identifier}/events/{identifier}', body).json() + self.assertEqual(parent_event_identifier, response['parent_event_id']) From 99fb8482e7036fa8e26f60c443beee496d38c1e8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:26:49 +0200 Subject: [PATCH 21/28] Grouped socket_io event handlers --- .../socket_io_event_handlers/collab.py | 26 ++++++++++++++++++ source/app/iris_engine/updater/updater.py | 27 ------------------- 2 files changed, 26 insertions(+), 27 deletions(-) diff --git a/source/app/blueprints/socket_io_event_handlers/collab.py b/source/app/blueprints/socket_io_event_handlers/collab.py index 320811bc6..57a1a72bf 100644 --- a/source/app/blueprints/socket_io_event_handlers/collab.py +++ b/source/app/blueprints/socket_io_event_handlers/collab.py @@ -15,9 +15,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +from flask_login import current_user +from flask_socketio import emit from flask_socketio import join_room from app import socket_io +from app import app from app.blueprints.access_controls import ac_socket_requires from app.models.authorization import CaseAccessLevel @@ -27,3 +30,26 @@ def socket_join_case_obj_notif(data): room = data['channel'] join_room(room=room) + +@socket_io.on('join-update', namespace='/server-updates') +def get_message(data): + + room = data['channel'] + join_room(room=room) + + emit('join', {'message': f"{current_user.user} just joined", 'is_error': False}, room=room, + namespace='/server-updates') + + +@socket_io.on('update_ping', namespace='/server-updates') +def socket_on_update_ping(msg): + + emit('update_ping', {'message': "Server connected", 'is_error': False}, + namespace='/server-updates') + + +@socket_io.on('update_get_current_version', namespace='/server-updates') +def socket_on_update_do_reboot(msg): + + socket_io.emit('update_current_version', {"version": app.config.get('IRIS_VERSION')}, to='iris_update_status', + namespace='/server-updates') diff --git a/source/app/iris_engine/updater/updater.py b/source/app/iris_engine/updater/updater.py index 680d83fc6..d3b068dfd 100644 --- a/source/app/iris_engine/updater/updater.py +++ b/source/app/iris_engine/updater/updater.py @@ -26,9 +26,6 @@ import time from celery.schedules import crontab from datetime import datetime -from flask_login import current_user -from flask_socketio import emit -from flask_socketio import join_room from packaging import version from pathlib import Path @@ -70,30 +67,6 @@ def update_log_error(status): update_log_to_socket(status, is_error=True) -@socket_io.on('join-update', namespace='/server-updates') -def get_message(data): - - room = data['channel'] - join_room(room=room) - - emit('join', {'message': f"{current_user.user} just joined", 'is_error': False}, room=room, - namespace='/server-updates') - - -@socket_io.on('update_ping', namespace='/server-updates') -def socket_on_update_ping(msg): - - emit('update_ping', {'message': "Server connected", 'is_error': False}, - namespace='/server-updates') - - -@socket_io.on('update_get_current_version', namespace='/server-updates') -def socket_on_update_do_reboot(msg): - - socket_io.emit('update_current_version', {"version": app.config.get('IRIS_VERSION')}, to='iris_update_status', - namespace='/server-updates') - - def notify_server_ready_to_reboot(): socket_io.emit('server_ready_to_reboot', {}, to='iris_update_status', namespace='/server-updates') From 4bacdeed06fc05a2c4f552c12c87fb30dd960446 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 13:41:35 +0200 Subject: [PATCH 22/28] Grouped update socketIO event handlers in namespace app.blueprints.socket_io_event_handlers --- source/app/__init__.py | 2 +- .../socket_io_event_handlers/case_event_handlers.py | 7 +++++++ .../{collab.py => update_event_handlers.py} | 8 -------- 3 files changed, 8 insertions(+), 9 deletions(-) rename source/app/blueprints/socket_io_event_handlers/{collab.py => update_event_handlers.py} (85%) diff --git a/source/app/__init__.py b/source/app/__init__.py index abdbc6dd8..899b754d5 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -174,4 +174,4 @@ def after_request(response): # TODO should not disable ruff, but the socket io event handlers must be initialized somehow from app.blueprints.socket_io_event_handlers.case_event_handlers import get_message # noqa: F401 from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import socket_join_overview # noqa: F401 -from app.blueprints.socket_io_event_handlers.collab import socket_join_case_obj_notif # noqa: F401 +from app.blueprints.socket_io_event_handlers.update_event_handlers import socket_on_update_do_reboot # noqa: F401 diff --git a/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py index e8061f334..5cad122f7 100644 --- a/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py @@ -56,3 +56,10 @@ def get_message(data): room = data['channel'] join_room(room=room) emit('join', {'message': f"{current_user.user} just joined"}, room=room) + + +@socket_io.on('join-case-obj-notif') +@ac_socket_requires(CaseAccessLevel.full_access) +def socket_join_case_obj_notif(data): + room = data['channel'] + join_room(room=room) diff --git a/source/app/blueprints/socket_io_event_handlers/collab.py b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py similarity index 85% rename from source/app/blueprints/socket_io_event_handlers/collab.py rename to source/app/blueprints/socket_io_event_handlers/update_event_handlers.py index 57a1a72bf..ebe964e92 100644 --- a/source/app/blueprints/socket_io_event_handlers/collab.py +++ b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py @@ -21,16 +21,8 @@ from app import socket_io from app import app -from app.blueprints.access_controls import ac_socket_requires -from app.models.authorization import CaseAccessLevel -@socket_io.on('join-case-obj-notif') -@ac_socket_requires(CaseAccessLevel.full_access) -def socket_join_case_obj_notif(data): - room = data['channel'] - join_room(room=room) - @socket_io.on('join-update', namespace='/server-updates') def get_message(data): From 06dae269ba30e2048adc7ed9d9eec62982d02c7b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:04:01 +0200 Subject: [PATCH 23/28] Introduced methods to register socketIO event handlers, so as to avoir ruff NOQA directive --- source/app/__init__.py | 11 ++++++---- .../case_event_handlers.py | 13 +++++++----- .../case_notes_event_handlers.py | 21 +++++++++++-------- .../update_event_handlers.py | 12 +++++------ 4 files changed, 33 insertions(+), 24 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index 899b754d5..955e57e91 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -171,7 +171,10 @@ def after_request(response): lm.user_loader(load_user) lm.request_loader(load_user_from_request) -# TODO should not disable ruff, but the socket io event handlers must be initialized somehow -from app.blueprints.socket_io_event_handlers.case_event_handlers import get_message # noqa: F401 -from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import socket_join_overview # noqa: F401 -from app.blueprints.socket_io_event_handlers.update_event_handlers import socket_on_update_do_reboot # noqa: F401 +from app.blueprints.socket_io_event_handlers.case_event_handlers import register_case_event_handlers +from app.blueprints.socket_io_event_handlers.case_notes_event_handlers import register_notes_event_handlers +from app.blueprints.socket_io_event_handlers.update_event_handlers import register_update_event_handlers + +register_case_event_handlers() +register_notes_event_handlers() +register_update_event_handlers() \ No newline at end of file diff --git a/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py index 5cad122f7..6a82f4fdd 100644 --- a/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/case_event_handlers.py @@ -26,7 +26,6 @@ from app.models.authorization import CaseAccessLevel -@socket_io.on('change') @ac_socket_requires(CaseAccessLevel.full_access) def socket_summary_onchange(data): @@ -34,7 +33,6 @@ def socket_summary_onchange(data): emit('change', data, to=data['channel'], skip_sid=request.sid) -@socket_io.on('save') @ac_socket_requires(CaseAccessLevel.full_access) def socket_summary_onsave(data): @@ -42,14 +40,12 @@ def socket_summary_onsave(data): emit('save', data, to=data['channel'], skip_sid=request.sid) -@socket_io.on('clear_buffer') @ac_socket_requires(CaseAccessLevel.full_access) def socket_summary_on_clear_buffer(message): emit('clear_buffer', message) -@socket_io.on('join') @ac_socket_requires(CaseAccessLevel.full_access) def get_message(data): @@ -58,8 +54,15 @@ def get_message(data): emit('join', {'message': f"{current_user.user} just joined"}, room=room) -@socket_io.on('join-case-obj-notif') @ac_socket_requires(CaseAccessLevel.full_access) def socket_join_case_obj_notif(data): room = data['channel'] join_room(room=room) + + +def register_case_event_handlers(): + socket_io.on_event('change', socket_summary_onchange) + socket_io.on_event('save', socket_summary_onsave) + socket_io.on_event('clear_buffer', socket_summary_on_clear_buffer) + socket_io.on_event('join', get_message) + socket_io.on_event('join-case-obj-notif', socket_join_case_obj_notif) diff --git a/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py index fdbff4206..b4faa3adf 100644 --- a/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/case_notes_event_handlers.py @@ -26,7 +26,6 @@ from app.models.authorization import CaseAccessLevel -@socket_io.on('change-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_change_note(data): @@ -34,7 +33,6 @@ def socket_change_note(data): emit('change-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel']) -@socket_io.on('save-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_save_note(data): @@ -42,14 +40,12 @@ def socket_save_note(data): emit('save-note', data, to=data['channel'], skip_sid=request.sid, room=data['channel']) -@socket_io.on('clear_buffer-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_clear_buffer_note(message): emit('clear_buffer-note', message, room=message['channel']) -@socket_io.on('join-notes') @ac_socket_requires(CaseAccessLevel.full_access) def socket_join_note(data): @@ -62,28 +58,24 @@ def socket_join_note(data): }, room=room) -@socket_io.on('ping-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_ping_note(data): emit('ping-note', {"user": current_user.name, "note_id": data['note_id']}, room=data['channel']) -@socket_io.on('pong-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_pong_note(data): emit('pong-note', {"user": current_user.name, "note_id": data['note_id']}, room=data['channel']) -@socket_io.on('overview-map-note') @ac_socket_requires(CaseAccessLevel.full_access) def socket_overview_map_note(data): emit('overview-map-note', {"user": current_user.user, "note_id": data['note_id']}, room=data['channel']) -@socket_io.on('join-notes-overview') @ac_socket_requires(CaseAccessLevel.full_access) def socket_join_overview(data): @@ -96,7 +88,18 @@ def socket_join_overview(data): }, room=room) -@socket_io.on('disconnect') @ac_socket_requires(CaseAccessLevel.full_access) def socket_disconnect(data): emit('disconnect', current_user.user, broadcast=True) + + +def register_notes_event_handlers(): + socket_io.on_event('change-note', socket_change_note) + socket_io.on_event('save-note', socket_save_note) + socket_io.on_event('clear_buffer-note', socket_clear_buffer_note) + socket_io.on_event('join-notes', socket_join_note) + socket_io.on_event('ping-note', socket_ping_note) + socket_io.on_event('pong-note', socket_pong_note) + socket_io.on_event('overview-map-note', socket_overview_map_note) + socket_io.on_event('join-notes-overview', socket_join_overview) + socket_io.on_event('disconnect', socket_disconnect) diff --git a/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py index ebe964e92..866ea3d2d 100644 --- a/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py @@ -23,9 +23,7 @@ from app import app -@socket_io.on('join-update', namespace='/server-updates') def get_message(data): - room = data['channel'] join_room(room=room) @@ -33,15 +31,17 @@ def get_message(data): namespace='/server-updates') -@socket_io.on('update_ping', namespace='/server-updates') def socket_on_update_ping(msg): - emit('update_ping', {'message': "Server connected", 'is_error': False}, namespace='/server-updates') -@socket_io.on('update_get_current_version', namespace='/server-updates') def socket_on_update_do_reboot(msg): - socket_io.emit('update_current_version', {"version": app.config.get('IRIS_VERSION')}, to='iris_update_status', namespace='/server-updates') + + +def register_update_event_handlers(): + socket_io.on_event('join-update', get_message, namespace='/server-updates') + socket_io.on_event('update_ping', socket_on_update_ping, namespace='/server-updates') + socket_io.on_event('update_get_current_version', socket_on_update_do_reboot, namespace='/server-updates') \ No newline at end of file From 1c216dafa785e4ab70d13634d54450b62a61e1de Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:05:08 +0200 Subject: [PATCH 24/28] Fixed ruff --- source/app/__init__.py | 2 +- .../socket_io_event_handlers/update_event_handlers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/__init__.py b/source/app/__init__.py index 955e57e91..61e3b5a7e 100644 --- a/source/app/__init__.py +++ b/source/app/__init__.py @@ -177,4 +177,4 @@ def after_request(response): register_case_event_handlers() register_notes_event_handlers() -register_update_event_handlers() \ No newline at end of file +register_update_event_handlers() diff --git a/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py index 866ea3d2d..ea727760c 100644 --- a/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py +++ b/source/app/blueprints/socket_io_event_handlers/update_event_handlers.py @@ -44,4 +44,4 @@ def socket_on_update_do_reboot(msg): def register_update_event_handlers(): socket_io.on_event('join-update', get_message, namespace='/server-updates') socket_io.on_event('update_ping', socket_on_update_ping, namespace='/server-updates') - socket_io.on_event('update_get_current_version', socket_on_update_do_reboot, namespace='/server-updates') \ No newline at end of file + socket_io.on_event('update_get_current_version', socket_on_update_do_reboot, namespace='/server-updates') From f75838837767b82abdde541db7ea4655918cadfe Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:10:26 +0200 Subject: [PATCH 25/28] Import logger directly --- source/app/blueprints/rest/v2/auth.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/app/blueprints/rest/v2/auth.py b/source/app/blueprints/rest/v2/auth.py index 9e99bff74..26b062edb 100644 --- a/source/app/blueprints/rest/v2/auth.py +++ b/source/app/blueprints/rest/v2/auth.py @@ -25,6 +25,7 @@ from app import app from app import db from app import oidc_client +from app.logger import logger from app.blueprints.access_controls import is_authentication_ldap from app.blueprints.access_controls import is_authentication_oidc from app.blueprints.access_controls import not_authenticated_redirection_url @@ -37,18 +38,16 @@ auth_blueprint = Blueprint('auth', __name__, url_prefix='/auth') -log = app.logger - @auth_blueprint.post('/login') def login(): """ Login endpoint. Handles taking user/pass combo and authenticating a local session or returning an error. """ - log.info('Authenticating user') + logger.info('Authenticating user') if current_user.is_authenticated: - log.info('User already authenticated - redirecting') - log.debug(f'User {current_user.user} already logged in') + logger.info('User already authenticated - redirecting') + logger.debug(f'User {current_user.user} already logged in') user = return_authed_user_info(user_id=current_user.id) return response_api_success(data=user) From 87992cee917c9fd7786f1834009490a17fc53851 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:12:32 +0200 Subject: [PATCH 26/28] Fixed an occurence of ruff RET505 --- source/app/models/models.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/source/app/models/models.py b/source/app/models/models.py index aececb1d5..53c17887a 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -983,15 +983,13 @@ def create_safe_attr(session, attribute_display_name, attribute_description, att CustomAttribute.attribute_description == attribute_description, CustomAttribute.attribute_for == attribute_for ).first() - if cat: - return False - else: - instance = CustomAttribute() - instance.attribute_display_name = attribute_display_name - instance.attribute_description = attribute_description - instance.attribute_for = attribute_for - instance.attribute_content = attribute_content - session.add(instance) - session.commit() - return True + return + + instance = CustomAttribute() + instance.attribute_display_name = attribute_display_name + instance.attribute_description = attribute_description + instance.attribute_for = attribute_for + instance.attribute_content = attribute_content + session.add(instance) + session.commit() From 0f514fa8e3efa67fb71a310bbf56917ee456d7ab Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:18:58 +0200 Subject: [PATCH 27/28] Added type --- source/app/business/cases.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 7bc500611..4a5e9a5f9 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -17,27 +17,20 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import datetime -from app.logger import logger import traceback - from flask_login import current_user - from marshmallow.exceptions import ValidationError from app import db - +from app.logger import logger from app.util import add_obj_history_entry from app.schema.marshables import CaseSchema - from app.models.models import ReviewStatusList - from app.business.errors import BusinessProcessingError from app.business.iocs import iocs_exports_to_json - from app.iris_engine.module_handler.module_handler import call_modules_hook from app.iris_engine.utils.tracker import track_activity from app.iris_engine.access_control.utils import ac_set_new_case_access - from app.datamgmt.case.case_db import case_db_exists from app.datamgmt.case.case_db import save_case_tags from app.datamgmt.case.case_db import register_case_protagonists @@ -60,9 +53,10 @@ from app.datamgmt.reporter.report_db import export_case_tasks_json from app.datamgmt.reporter.report_db import export_case_comments_json from app.datamgmt.reporter.report_db import export_case_notes_json +from app.models.cases import Cases -def _load(request_data, **kwargs): +def _load(request_data, **kwargs) -> Cases: try: add_case_schema = CaseSchema() return add_case_schema.load(request_data, **kwargs) From 89d5bfb1fd5f5daaf975f5c4be99dcceff0edd47 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:19:48 +0200 Subject: [PATCH 28/28] Fixed an occurence of Ruff RET505 warning --- source/app/models/models.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/source/app/models/models.py b/source/app/models/models.py index 53c17887a..faabf33c6 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -680,12 +680,11 @@ def __init__(self, tag_title, namespace=None): def save(self): existing_tag = self.get_by_title(self.tag_title) - if existing_tag is not None: + if existing_tag: return existing_tag - else: - db.session.add(self) - db.session.commit() - return self + db.session.add(self) + db.session.commit() + return self @classmethod def get_by_title(cls, tag_title):