From 9a33abff7c66f92bf9113aa712ddc1902c2ece45 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:04:05 +0200 Subject: [PATCH 01/67] Started implementation of POST /api/v2/alerts-filters --- source/app/blueprints/rest/api_v2_routes.py | 2 + .../app/blueprints/rest/v2/alerts_filters.py | 56 ++++++++++++++++++ tests/tests_rest_alerts_filters.py | 58 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 source/app/blueprints/rest/v2/alerts_filters.py create mode 100644 tests/tests_rest_alerts_filters.py diff --git a/source/app/blueprints/rest/api_v2_routes.py b/source/app/blueprints/rest/api_v2_routes.py index 3fa9c7c5b..782dac1b7 100644 --- a/source/app/blueprints/rest/api_v2_routes.py +++ b/source/app/blueprints/rest/api_v2_routes.py @@ -31,6 +31,7 @@ from app.blueprints.rest.v2.tags import tags_blueprint from app.blueprints.rest.v2.tasks import tasks_blueprint from app.blueprints.rest.v2.profile import profile_blueprint +from app.blueprints.rest.v2.alerts_filters import alerts_filters_blueprint # Create root /api/v2 blueprint @@ -50,3 +51,4 @@ rest_v2_blueprint.register_blueprint(manage_v2_blueprint) rest_v2_blueprint.register_blueprint(tags_blueprint) rest_v2_blueprint.register_blueprint(profile_blueprint) +rest_v2_blueprint.register_blueprint(alerts_filters_blueprint) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py new file mode 100644 index 000000000..caa3647a1 --- /dev/null +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -0,0 +1,56 @@ +# IRIS Source Code +# Copyright (C) 2024 - 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 flask import Blueprint +from flask import request +from marshmallow import ValidationError + +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.rest.endpoints import response_api_created +from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.iris_user import iris_current_user +from app.schema.marshables import SavedFilterSchema + + +class AlertsFiltersOperations: + + def __init__(self): + self._schema = SavedFilterSchema() + + def create(self): + saved_filter_schema = SavedFilterSchema() + + request_data = request.get_json() + request_data ['created_by'] = iris_current_user.id + + try: + new_saved_filter = saved_filter_schema.load(request_data) + return response_api_created(saved_filter_schema.dump(new_saved_filter)) + + except ValidationError as e: + return response_api_error('Data error', e.messages) + + +alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') +alerts_filters_operations = AlertsFiltersOperations() + + +@alerts_filters_blueprint.post('') +@ac_api_requires() +def create_alert_filter(): + return alerts_filters_operations.create() diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py new file mode 100644 index 000000000..35f36f98a --- /dev/null +++ b/tests/tests_rest_alerts_filters.py @@ -0,0 +1,58 @@ +# 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 unittest import TestCase +from iris import Iris + + +class TestsRestAlertsFilters(TestCase): + + def setUp(self) -> None: + self._subject = Iris() + + def tearDown(self): + self._subject.clear_database() + + def test_create_alert_filter_should_return_201(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data' :{ + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + }, + } + response = self._subject.create('/api/v2/alerts-filters', body) + self.assertEqual(201, response.status_code) From b7088fd7803420b452a48e4c854b48977c667d72 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:06:01 +0200 Subject: [PATCH 02/67] Fixed analysis check --- source/app/blueprints/rest/v2/alerts_filters.py | 2 +- tests/tests_rest_alerts_filters.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index caa3647a1..db9497a39 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -34,7 +34,7 @@ def __init__(self): def create(self): saved_filter_schema = SavedFilterSchema() - + request_data = request.get_json() request_data ['created_by'] = iris_current_user.id diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 35f36f98a..41f91e714 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -34,7 +34,7 @@ def test_create_alert_filter_should_return_201(self): 'filter_type': 'alerts', 'filter_name': 'filter name', 'filter_description': 'filter description', - 'filter_data' :{ + 'filter_data' : { 'alert_tilte': 'filter name', 'alert_description': '', 'alert_source': '', From 103ed2c9c023e2272d61d15ef9a1e3c0169bf7d8 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:09:21 +0200 Subject: [PATCH 03/67] Added function _load --- source/app/blueprints/rest/v2/alerts_filters.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index db9497a39..07fe23561 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -32,15 +32,16 @@ class AlertsFiltersOperations: def __init__(self): self._schema = SavedFilterSchema() - def create(self): - saved_filter_schema = SavedFilterSchema() + def _load(self, request_data): + return self._schema.load(request_data) + def create(self): request_data = request.get_json() request_data ['created_by'] = iris_current_user.id try: - new_saved_filter = saved_filter_schema.load(request_data) - return response_api_created(saved_filter_schema.dump(new_saved_filter)) + new_saved_filter = self._load(request_data) + return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: return response_api_error('Data error', e.messages) From 82bfec63e2c918159d6b8a50eeb8d894df6710da Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:21:44 +0200 Subject: [PATCH 04/67] Deprecated endpoint POST /filters/add in favor of POST /api/v2/alerts-filters --- source/app/blueprints/rest/filters_routes.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 0971a1b49..a742e71e6 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -20,19 +20,21 @@ from werkzeug import Response from app import db -from app.blueprints.iris_user import iris_current_user from app.datamgmt.filters.filters_db import get_filter_by_id from app.datamgmt.filters.filters_db import list_filters_by_type from app.iris_engine.utils.tracker import track_activity from app.schema.marshables import SavedFilterSchema +from app.blueprints.iris_user import iris_current_user from app.blueprints.access_controls import ac_api_requires from app.blueprints.responses import response_success from app.blueprints.responses import response_error +from app.blueprints.rest.endpoints import endpoint_deprecated saved_filters_rest_blueprint = Blueprint('saved_filters_rest', __name__) @saved_filters_rest_blueprint.route('/filters/add', methods=['POST']) +@endpoint_deprecated('POST', '/api/v2/alerts-filters') @ac_api_requires() def filters_add_route() -> Response: """ From 5b1290eb2cfb5a809eadb7f34ad79c56ee6d8330 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:25:46 +0200 Subject: [PATCH 05/67] Added test create alert when filter data is missing --- tests/tests_rest_alerts_filters.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 41f91e714..0093fc251 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -52,7 +52,17 @@ def test_create_alert_filter_should_return_201(self): 'case_id': '', 'custom_conditions': '', - }, + } } response = self._subject.create('/api/v2/alerts-filters', body) self.assertEqual(201, response.status_code) + + def test_create_alert_filter_should_return_400_when_filter_data_is_missing(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + } + response = self._subject.create('/api/v2/alerts-filters', body) + self.assertEqual(400, response.status_code) From ea45b99a9fcf1775fb9a3c31b4f45d06e57df37e Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:40:00 +0200 Subject: [PATCH 06/67] Added test test_create_alert_filter_should_return_filter_type --- tests/tests_rest_alerts_filters.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 0093fc251..d44769d08 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -66,3 +66,33 @@ def test_create_alert_filter_should_return_400_when_filter_data_is_missing(self) } response = self._subject.create('/api/v2/alerts-filters', body) self.assertEqual(400, response.status_code) + + def test_create_alert_filter_should_return_filter_type(self): + filter_type = 'alerts' + body = { + 'filter_is_private': 'true', + 'filter_type': filter_type, + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(filter_type, response['filter_type']) From 4c5ddd6a41b83c78fca6293911932dc0a6b904a3 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:41:40 +0200 Subject: [PATCH 07/67] Added test_create_alert_filter_should_return_filter_name --- tests/tests_rest_alerts_filters.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index d44769d08..77a7f9334 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -96,3 +96,33 @@ def test_create_alert_filter_should_return_filter_type(self): } response = self._subject.create('/api/v2/alerts-filters', body).json() self.assertEqual(filter_type, response['filter_type']) + + def test_create_alert_filter_should_return_filter_name(self): + filter_name = 'name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': filter_name, + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(filter_name, response['filter_name']) From 0a0f63d9fc25a0e8951267904e95c6cb26cfed9f Mon Sep 17 00:00:00 2001 From: Elise17 Date: Wed, 8 Oct 2025 14:43:59 +0200 Subject: [PATCH 08/67] Added test_create_alert_filter_should_return_in_filter_data_alert_title --- tests/tests_rest_alerts_filters.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 77a7f9334..7546f74a4 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -126,3 +126,33 @@ def test_create_alert_filter_should_return_filter_name(self): } response = self._subject.create('/api/v2/alerts-filters', body).json() self.assertEqual(filter_name, response['filter_name']) + + def test_create_alert_filter_should_return_in_filter_data_alert_title(self): + alert_title = 'alert_title' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter_name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': alert_title, + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + response = self._subject.create('/api/v2/alerts-filters', body).json() + self.assertEqual(alert_title, response['filter_data']['alert_title']) From af0bf2b60e5afd39d2ed644e59d51324f14b8a28 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:11:06 +0200 Subject: [PATCH 09/67] Started implementation of GET /api/v2/alerts-filters/{identifier} --- .../app/blueprints/rest/v2/alerts_filters.py | 23 +++++++++++++ source/app/business/alerts_filters.py | 24 ++++++++++++++ tests/tests_rest_alerts_filters.py | 32 +++++++++++++++++++ 3 files changed, 79 insertions(+) create mode 100644 source/app/business/alerts_filters.py diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 07fe23561..f04cf104a 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -23,8 +23,15 @@ from app.blueprints.access_controls import ac_api_requires from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.iris_user import iris_current_user + + from app.schema.marshables import SavedFilterSchema +from app.business.errors import ObjectNotFoundError +from app.business.alerts_filters import add_alerts_filters +from app.datamgmt.filters.filters_db import get_filter_by_id class AlertsFiltersOperations: @@ -41,10 +48,20 @@ def create(self): try: new_saved_filter = self._load(request_data) + add_alerts_filters(new_saved_filter) return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: return response_api_error('Data error', e.messages) + + def get(self, identifier): + try: + saved_filter = get_filter_by_id(identifier) + return response_api_success(self._schema.dump(saved_filter)) + + except ObjectNotFoundError: + return response_api_not_found() + alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') @@ -55,3 +72,9 @@ def create(self): @ac_api_requires() def create_alert_filter(): return alerts_filters_operations.create() + +@alerts_filters_blueprint.get('/') +@ac_api_requires() +def get_alert(identifier): + return alerts_filters_operations.get(identifier) + diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py new file mode 100644 index 000000000..2feed9dbc --- /dev/null +++ b/source/app/business/alerts_filters.py @@ -0,0 +1,24 @@ +# IRIS Source Code +# Copyright (C) 2024 - 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 app import db + + +def add_alerts_filters(new_saved_filter): + db.session.add(new_saved_filter) + db.session.commit() diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 7546f74a4..b49894a32 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -156,3 +156,35 @@ def test_create_alert_filter_should_return_in_filter_data_alert_title(self): } response = self._subject.create('/api/v2/alerts-filters', body).json() self.assertEqual(alert_title, response['filter_data']['alert_title']) + + def test_get_alert_filter_should_return_200(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + user = self._subject.create_dummy_user() + response = user.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(200, response.status_code) From 0c911feaa74e06fc452d75c49c6590eda123cf85 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:13:22 +0200 Subject: [PATCH 10/67] Fixed check analysis --- source/app/blueprints/rest/v2/alerts_filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index f04cf104a..4de21684f 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -53,7 +53,7 @@ def create(self): except ValidationError as e: return response_api_error('Data error', e.messages) - + def get(self, identifier): try: saved_filter = get_filter_by_id(identifier) @@ -63,7 +63,6 @@ def get(self, identifier): return response_api_not_found() - alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') alerts_filters_operations = AlertsFiltersOperations() @@ -73,6 +72,7 @@ def get(self, identifier): def create_alert_filter(): return alerts_filters_operations.create() + @alerts_filters_blueprint.get('/') @ac_api_requires() def get_alert(identifier): From 8559ad7e7877f041f6185add9a6481ccd277d37c Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:21:27 +0200 Subject: [PATCH 11/67] Fixed importation problem --- source/app/blueprints/rest/v2/alerts_filters.py | 8 ++++---- source/app/business/alerts_filters.py | 7 +++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 4de21684f..da77d71d4 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -30,8 +30,8 @@ from app.schema.marshables import SavedFilterSchema from app.business.errors import ObjectNotFoundError -from app.business.alerts_filters import add_alerts_filters -from app.datamgmt.filters.filters_db import get_filter_by_id +from app.business.alerts_filters import alerts_filters_add +from app.business.alerts_filters import alerts_filters_get class AlertsFiltersOperations: @@ -48,7 +48,7 @@ def create(self): try: new_saved_filter = self._load(request_data) - add_alerts_filters(new_saved_filter) + alerts_filters_add(new_saved_filter) return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: @@ -56,7 +56,7 @@ def create(self): def get(self, identifier): try: - saved_filter = get_filter_by_id(identifier) + saved_filter = alerts_filters_get(identifier) return response_api_success(self._schema.dump(saved_filter)) except ObjectNotFoundError: diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 2feed9dbc..6b847ac63 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -17,8 +17,11 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from app import db +from app.datamgmt.filters.filters_db import get_filter_by_id - -def add_alerts_filters(new_saved_filter): +def alerts_filters_add(new_saved_filter): db.session.add(new_saved_filter) db.session.commit() + +def alerts_filters_get(identifier): + return get_filter_by_id(identifier) From 7440b09f42b285df66319cfe231121b66494f528 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 18:22:59 +0200 Subject: [PATCH 12/67] Fixed static analysis --- source/app/business/alerts_filters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 6b847ac63..3d295028d 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -19,9 +19,11 @@ from app import db from app.datamgmt.filters.filters_db import get_filter_by_id + def alerts_filters_add(new_saved_filter): db.session.add(new_saved_filter) db.session.commit() + def alerts_filters_get(identifier): - return get_filter_by_id(identifier) + return get_filter_by_id(identifier) From 0690eb01a6a0e22db711d326982d20b64d49a877 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 19:02:09 +0200 Subject: [PATCH 13/67] Removed user in test --- tests/tests_rest_alerts_filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index b49894a32..3fa25aa3d 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -183,8 +183,8 @@ def test_get_alert_filter_should_return_200(self): } } - user = self._subject.create_dummy_user() - response = user.create('/api/v2/alerts-filters', body).json() + + response = self._subject.create('/api/v2/alerts-filters', body).json() identifier = response['filter_id'] response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') self.assertEqual(200, response.status_code) From 1129b73b40a485359b594d5497c11390bc8f22cf Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 20:18:30 +0200 Subject: [PATCH 14/67] Added test_get_alert_filter_should_return_filter_name --- tests/tests_rest_alerts_filters.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 3fa25aa3d..65ca4673a 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -188,3 +188,36 @@ def test_get_alert_filter_should_return_200(self): identifier = response['filter_id'] response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') self.assertEqual(200, response.status_code) + + def test_get_alert_filter_should_return_filter_name(self): + filter_name = 'filter name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': filter_name, + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}').json() + self.assertEqual(filter_name, response['filter_name']) From 97a89e5fc8d003e51ef71b468b026d3a81987998 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 20:20:17 +0200 Subject: [PATCH 15/67] Deprecated endpoint GET /filters/{identifier} --- source/app/blueprints/rest/filters_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index a742e71e6..b4203d4a4 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -130,6 +130,7 @@ def filters_delete_route(filter_id) -> Response: @saved_filters_rest_blueprint.route('/filters/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/alerts-filters') @ac_api_requires() def filters_get_route(filter_id) -> Response: """ From 622b46a34f7122a6205074378e3f87c115e94e52 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 20:32:53 +0200 Subject: [PATCH 16/67] Added test_get_alert_filter_should_return_404 --- source/app/blueprints/rest/v2/alerts_filters.py | 11 +++++------ source/app/business/alerts_filters.py | 11 +++++++---- tests/tests_rest_alerts_filters.py | 6 ++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index da77d71d4..ff948b2be 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -30,8 +30,8 @@ from app.schema.marshables import SavedFilterSchema from app.business.errors import ObjectNotFoundError -from app.business.alerts_filters import alerts_filters_add -from app.business.alerts_filters import alerts_filters_get +from app.business.alerts_filters import alert_filter_add +from app.business.alerts_filters import alert_filter_get class AlertsFiltersOperations: @@ -48,17 +48,16 @@ def create(self): try: new_saved_filter = self._load(request_data) - alerts_filters_add(new_saved_filter) + alert_filter_add(new_saved_filter) return response_api_created(self._schema.dump(new_saved_filter)) except ValidationError as e: return response_api_error('Data error', e.messages) - + def get(self, identifier): try: - saved_filter = alerts_filters_get(identifier) + saved_filter = alert_filter_get(identifier) return response_api_success(self._schema.dump(saved_filter)) - except ObjectNotFoundError: return response_api_not_found() diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 3d295028d..98709534e 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -18,12 +18,15 @@ from app import db from app.datamgmt.filters.filters_db import get_filter_by_id +from app.business.errors import ObjectNotFoundError - -def alerts_filters_add(new_saved_filter): +def alert_filter_add(new_saved_filter): db.session.add(new_saved_filter) db.session.commit() -def alerts_filters_get(identifier): - return get_filter_by_id(identifier) +def alert_filter_get(identifier): + alert_filter = get_filter_by_id(identifier) + if not alert_filter: + raise ObjectNotFoundError() + return alert_filter diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 65ca4673a..9709ddee5 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -19,6 +19,8 @@ from unittest import TestCase from iris import Iris +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 + class TestsRestAlertsFilters(TestCase): @@ -221,3 +223,7 @@ def test_get_alert_filter_should_return_filter_name(self): identifier = response['filter_id'] response = self._subject.get(f'/api/v2/alerts-filters/{identifier}').json() self.assertEqual(filter_name, response['filter_name']) + + def test_get_alert_filter_should_return_404_when_alert_filter_not_found(self): + response = self._subject.get(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) From 9a1fffd2821c7316f7a83acbcd62484f6a6a394a Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 20:41:25 +0200 Subject: [PATCH 17/67] Added test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filter --- tests/tests_rest_alerts_filters.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 9709ddee5..d84ca1015 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -227,3 +227,36 @@ def test_get_alert_filter_should_return_filter_name(self): def test_get_alert_filter_should_return_404_when_alert_filter_not_found(self): response = self._subject.get(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') self.assertEqual(404, response.status_code) + + def test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filter(self): + user = self._subject.create_dummy_user() + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter_name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = user.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(404, response.status_code) From e4b499894ab7ba9ec2e478c9e1810b5ed3f84b0f Mon Sep 17 00:00:00 2001 From: Elise17 Date: Fri, 17 Oct 2025 20:42:59 +0200 Subject: [PATCH 18/67] Fixed static analysis --- source/app/blueprints/rest/v2/alerts_filters.py | 2 +- source/app/business/alerts_filters.py | 1 + tests/tests_rest_alerts_filters.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index ff948b2be..38318796e 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -53,7 +53,7 @@ def create(self): except ValidationError as e: return response_api_error('Data error', e.messages) - + def get(self, identifier): try: saved_filter = alert_filter_get(identifier) diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 98709534e..f1cb6b9cd 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -20,6 +20,7 @@ from app.datamgmt.filters.filters_db import get_filter_by_id from app.business.errors import ObjectNotFoundError + def alert_filter_add(new_saved_filter): db.session.add(new_saved_filter) db.session.commit() diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index d84ca1015..0326b7d28 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -223,7 +223,7 @@ def test_get_alert_filter_should_return_filter_name(self): identifier = response['filter_id'] response = self._subject.get(f'/api/v2/alerts-filters/{identifier}').json() self.assertEqual(filter_name, response['filter_name']) - + def test_get_alert_filter_should_return_404_when_alert_filter_not_found(self): response = self._subject.get(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') self.assertEqual(404, response.status_code) From 69e38c82ea165dc8370418e288a7b664eca9c3d7 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 11:25:47 +0200 Subject: [PATCH 19/67] Started implementation of PUT /api/v2/alerts-filters/{identifier} --- .../app/blueprints/rest/v2/alerts_filters.py | 23 ++++++++++-- source/app/business/alerts_filters.py | 3 ++ tests/tests_rest_alerts_filters.py | 35 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 38318796e..31b1ae161 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -32,6 +32,7 @@ from app.business.errors import ObjectNotFoundError from app.business.alerts_filters import alert_filter_add from app.business.alerts_filters import alert_filter_get +from app.business.alerts_filters import alert_filter_update class AlertsFiltersOperations: @@ -39,8 +40,8 @@ class AlertsFiltersOperations: def __init__(self): self._schema = SavedFilterSchema() - def _load(self, request_data): - return self._schema.load(request_data) + def _load(self, request_data, **kwargs): + return self._schema.load(request_data, **kwargs) def create(self): request_data = request.get_json() @@ -61,6 +62,17 @@ def get(self, identifier): except ObjectNotFoundError: return response_api_not_found() + def put(self, identifier): + request_data = request.get_json() + + try: + saved_filter = alert_filter_get(identifier) + new_saved_filter = self._load(request_data, instance=saved_filter, partial=True) + alert_filter_update() + return response_api_success(self._schema.dump(new_saved_filter)) + except ObjectNotFoundError: + return response_api_not_found() + alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') alerts_filters_operations = AlertsFiltersOperations() @@ -74,6 +86,11 @@ def create_alert_filter(): @alerts_filters_blueprint.get('/') @ac_api_requires() -def get_alert(identifier): +def get_alert_filter(identifier): return alerts_filters_operations.get(identifier) + +@alerts_filters_blueprint.put('/') +@ac_api_requires() +def update_alert_filter(identifier): + return alerts_filters_operations.put(identifier) diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index f1cb6b9cd..18f6d8a2c 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -31,3 +31,6 @@ def alert_filter_get(identifier): if not alert_filter: raise ObjectNotFoundError() return alert_filter + +def alert_filter_update(): + db.session.commit() diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 0326b7d28..510a254cb 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -260,3 +260,38 @@ def test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filt identifier = response['filter_id'] response = user.get(f'/api/v2/alerts-filters/{identifier}') self.assertEqual(404, response.status_code) + + def test_update_alert_filter_should_return_200(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': 'filter name', + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body) + self.assertEqual(200, response.status_code) From ec809ef7f4055b7cbe94995eef39b08a957e6ddd Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 11:27:23 +0200 Subject: [PATCH 20/67] Fixed static analysis --- source/app/business/alerts_filters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index 18f6d8a2c..aa4a32785 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -32,5 +32,6 @@ def alert_filter_get(identifier): raise ObjectNotFoundError() return alert_filter + def alert_filter_update(): db.session.commit() From f438d3bacd4d182754c780e571b618b48f5a3b28 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 13:49:14 +0200 Subject: [PATCH 21/67] Added test_update_alert_filter_should_return_filter_name --- tests/tests_rest_alerts_filters.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 510a254cb..597469158 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -295,3 +295,39 @@ def test_update_alert_filter_should_return_200(self): } response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body) self.assertEqual(200, response.status_code) + + def test_update_alert_filter_should_return_filter_name(self): + filter_name = 'new name' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': filter_name, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_name, response['filter_name']) From 930e94c6e26d3910d6399c51f8c4b1d5f99eed51 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 13:50:59 +0200 Subject: [PATCH 22/67] Added test_update_alert_filter_should_return_filter_description --- tests/tests_rest_alerts_filters.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 597469158..8d858eb48 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -331,3 +331,39 @@ def test_update_alert_filter_should_return_filter_name(self): } response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() self.assertEqual(filter_name, response['filter_name']) + + def test_update_alert_filter_should_return_filter_description(self): + filter_description = 'new filter description' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_description': filter_description, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_description, response['filter_description']) From 120bed514f0f8e35c80ba52085e6dfd954292d8e Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 13:53:13 +0200 Subject: [PATCH 23/67] Added test_update_alert_filter_should_return_filter_type --- tests/tests_rest_alerts_filters.py | 36 ++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 8d858eb48..18f598f35 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -367,3 +367,39 @@ def test_update_alert_filter_should_return_filter_description(self): } response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() self.assertEqual(filter_description, response['filter_description']) + + def test_update_alert_filter_should_return_filter_type(self): + filter_type = 'new filter type' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_tilte': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_type': filter_type, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(filter_type, response['filter_type']) From 48c0957a609052358724c649b2322fee49b07414 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 13:59:32 +0200 Subject: [PATCH 24/67] Added test_update_alert_filter_should_return_filter_data_alert_title --- tests/tests_rest_alerts_filters.py | 56 ++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 10 deletions(-) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 18f598f35..928efc776 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -37,7 +37,7 @@ def test_create_alert_filter_should_return_201(self): 'filter_name': 'filter name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -77,7 +77,7 @@ def test_create_alert_filter_should_return_filter_type(self): 'filter_name': 'filter name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -107,7 +107,7 @@ def test_create_alert_filter_should_return_filter_name(self): 'filter_name': filter_name, 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -166,7 +166,7 @@ def test_get_alert_filter_should_return_200(self): 'filter_name': 'filter name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -199,7 +199,7 @@ def test_get_alert_filter_should_return_filter_name(self): 'filter_name': filter_name, 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -236,7 +236,7 @@ def test_get_alert_filter_should_return_404_when_user_has_not_created_alert_filt 'filter_name': 'filter_name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -268,7 +268,7 @@ def test_update_alert_filter_should_return_200(self): 'filter_name': 'filter name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -304,7 +304,7 @@ def test_update_alert_filter_should_return_filter_name(self): 'filter_name': 'old name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -340,7 +340,7 @@ def test_update_alert_filter_should_return_filter_description(self): 'filter_name': 'old name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -376,7 +376,7 @@ def test_update_alert_filter_should_return_filter_type(self): 'filter_name': 'old name', 'filter_description': 'filter description', 'filter_data' : { - 'alert_tilte': 'filter name', + 'alert_title': 'filter name', 'alert_description': '', 'alert_source': '', 'alert_tags': '', @@ -403,3 +403,39 @@ def test_update_alert_filter_should_return_filter_type(self): } response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() self.assertEqual(filter_type, response['filter_type']) + + def test_update_alert_filter_should_return_filter_data_alert_title(self): + alert_title = 'new alert title' + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_data': { 'alert_title' : alert_title }, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() + self.assertEqual(alert_title, response['filter_data']['alert_title']) From 657d441e29156df3edffdc370f1af329318bf9da Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 14:01:55 +0200 Subject: [PATCH 25/67] Added test_update_alert_filter_should_return_404_when_alert_filter_is_not_found --- tests/tests_rest_alerts_filters.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 928efc776..1bccbd4c6 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -439,3 +439,38 @@ def test_update_alert_filter_should_return_filter_data_alert_title(self): } response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body).json() self.assertEqual(alert_title, response['filter_data']['alert_title']) + + def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_data': { 'alert_title' : 'alert_title' }, + } + response = self._subject.update(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}', body) + self.assertEqual(404, response.status_code) From 1008f2917caf92214463a0647245bfb1759ce0a8 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 14:02:15 +0200 Subject: [PATCH 26/67] Removed identifietr in test_update_alert_filter_should_return_404_when_alert_filter_is_not_found --- tests/tests_rest_alerts_filters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 1bccbd4c6..208c8aff7 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -468,7 +468,6 @@ def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(se } response = self._subject.create('/api/v2/alerts-filters', body).json() - identifier = response['filter_id'] body = { 'filter_data': { 'alert_title' : 'alert_title' }, } From 78234efe52455e42c651919e72566b702375dfba Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 14:06:41 +0200 Subject: [PATCH 27/67] Deprecated endpoint POST /filters/update/{identifier} --- source/app/blueprints/rest/filters_routes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index b4203d4a4..6bd10a1a5 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -68,6 +68,7 @@ def filters_add_route() -> Response: @saved_filters_rest_blueprint.route('/filters/update/', methods=['POST']) +@endpoint_deprecated('PUT', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_update_route(filter_id) -> Response: """ @@ -130,7 +131,7 @@ def filters_delete_route(filter_id) -> Response: @saved_filters_rest_blueprint.route('/filters/', methods=['GET']) -@endpoint_deprecated('GET', '/api/v2/alerts-filters') +@endpoint_deprecated('GET', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_get_route(filter_id) -> Response: """ From eb541cbbca2f0f157c005ec6563a702bdc21011b Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 14:58:59 +0200 Subject: [PATCH 28/67] Started implmentation of endpoint DELETE /api/v2/alerts-filters/{identifier} --- .../app/blueprints/rest/v2/alerts_filters.py | 15 +++++++++ source/app/business/alerts_filters.py | 5 +++ tests/tests_rest_alerts_filters.py | 32 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 31b1ae161..9b32222fb 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -25,6 +25,7 @@ from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_not_found +from app.blueprints.rest.endpoints import response_api_deleted from app.blueprints.iris_user import iris_current_user @@ -33,6 +34,7 @@ from app.business.alerts_filters import alert_filter_add from app.business.alerts_filters import alert_filter_get from app.business.alerts_filters import alert_filter_update +from app.business.alerts_filters import alert_filter_delete class AlertsFiltersOperations: @@ -73,6 +75,14 @@ def put(self, identifier): except ObjectNotFoundError: return response_api_not_found() + def delete(self, identifier): + try: + saved_filter = alert_filter_get(identifier) + alert_filter_delete(saved_filter) + return response_api_deleted() + except ObjectNotFoundError: + return response_api_not_found() + alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') alerts_filters_operations = AlertsFiltersOperations() @@ -94,3 +104,8 @@ def get_alert_filter(identifier): @ac_api_requires() def update_alert_filter(identifier): return alerts_filters_operations.put(identifier) + +@alerts_filters_blueprint.delete('/') +@ac_api_requires() +def delete_alert_filter(identifier): + return alerts_filters_operations.delete(identifier) diff --git a/source/app/business/alerts_filters.py b/source/app/business/alerts_filters.py index aa4a32785..808707355 100644 --- a/source/app/business/alerts_filters.py +++ b/source/app/business/alerts_filters.py @@ -35,3 +35,8 @@ def alert_filter_get(identifier): def alert_filter_update(): db.session.commit() + + +def alert_filter_delete(saved_filter): + db.session.delete(saved_filter) + db.session.commit() diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 208c8aff7..0061812be 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -473,3 +473,35 @@ def test_update_alert_filter_should_return_404_when_alert_filter_is_not_found(se } response = self._subject.update(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}', body) self.assertEqual(404, response.status_code) + + def test_delete_alert_filter_should_return_204(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + response = self._subject.delete(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(204, response.status_code) From 745739cfa9606939cd7f20731c10f71090d38549 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 15:00:42 +0200 Subject: [PATCH 29/67] Added test_delete_alert_filter_should_return_404_when_alert_not_found --- tests/tests_rest_alerts_filters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 0061812be..59a8722cb 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -505,3 +505,7 @@ def test_delete_alert_filter_should_return_204(self): identifier = response['filter_id'] response = self._subject.delete(f'/api/v2/alerts-filters/{identifier}') self.assertEqual(204, response.status_code) + + def test_delete_alert_filter_should_return_404_when_alert_not_found(self): + response = self._subject.delete(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) From 74bf4ea1f7bd462a6d984e51c62e4506fd28e438 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 15:02:18 +0200 Subject: [PATCH 30/67] test_get_alert_filter_should_return_404_after_delete_alert_filter --- tests/tests_rest_alerts_filters.py | 33 ++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 59a8722cb..3d7635ade 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -509,3 +509,36 @@ def test_delete_alert_filter_should_return_204(self): def test_delete_alert_filter_should_return_404_when_alert_not_found(self): response = self._subject.delete(f'/api/v2/alerts-filters/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') self.assertEqual(404, response.status_code) + + def test_get_alert_filter_should_return_404_after_delete_alert_filter(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'old name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + self._subject.delete(f'/api/v2/alerts-filters/{identifier}') + response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') + self.assertEqual(404, response.status_code) From 157d7492ed409ece052924ebd891504703cbe81a Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 15:04:17 +0200 Subject: [PATCH 31/67] Deprecated endpoint DELETE /api/v2/alerts-filters/{identifier} --- source/app/blueprints/rest/filters_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/filters_routes.py b/source/app/blueprints/rest/filters_routes.py index 6bd10a1a5..3b67847a2 100644 --- a/source/app/blueprints/rest/filters_routes.py +++ b/source/app/blueprints/rest/filters_routes.py @@ -102,6 +102,7 @@ def filters_update_route(filter_id) -> Response: @saved_filters_rest_blueprint.route('/filters/delete/', methods=['POST']) +@endpoint_deprecated('DELETE', '/api/v2/alerts-filters/{identifier}') @ac_api_requires() def filters_delete_route(filter_id) -> Response: """ From 9a5a4c70426ab3a74ee4b4836a194540e4adcd72 Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 15:09:23 +0200 Subject: [PATCH 32/67] Fixed check analysis --- source/app/blueprints/rest/v2/alerts_filters.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 9b32222fb..8bec58bc6 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -105,6 +105,7 @@ def get_alert_filter(identifier): def update_alert_filter(identifier): return alerts_filters_operations.put(identifier) + @alerts_filters_blueprint.delete('/') @ac_api_requires() def delete_alert_filter(identifier): From cb3ec88d2e09aa5b3502bbc84dad143eb07d206d Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 17:17:30 +0200 Subject: [PATCH 33/67] Changed indentation and added ValidationError --- source/app/blueprints/rest/v2/alerts_filters.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 8bec58bc6..2916ec393 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -30,6 +30,7 @@ from app.schema.marshables import SavedFilterSchema +from app.business.errors import BusinessProcessingError from app.business.errors import ObjectNotFoundError from app.business.alerts_filters import alert_filter_add from app.business.alerts_filters import alert_filter_get @@ -57,10 +58,14 @@ def create(self): except ValidationError as e: return response_api_error('Data error', e.messages) + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + def get(self, identifier): try: saved_filter = alert_filter_get(identifier) return response_api_success(self._schema.dump(saved_filter)) + except ObjectNotFoundError: return response_api_not_found() @@ -72,6 +77,10 @@ def put(self, identifier): new_saved_filter = self._load(request_data, instance=saved_filter, partial=True) alert_filter_update() return response_api_success(self._schema.dump(new_saved_filter)) + + except ValidationError as e: + return response_api_error('Data error', data=e.messages) + except ObjectNotFoundError: return response_api_not_found() @@ -80,6 +89,7 @@ def delete(self, identifier): saved_filter = alert_filter_get(identifier) alert_filter_delete(saved_filter) return response_api_deleted() + except ObjectNotFoundError: return response_api_not_found() From 8d345f355caaa0d82a01a23c3c6c00c8f71d0b3a Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 17:23:27 +0200 Subject: [PATCH 34/67] Changed indentation and added ValidationError --- source/app/blueprints/rest/v2/alerts_filters.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts_filters.py b/source/app/blueprints/rest/v2/alerts_filters.py index 2916ec393..5fcf2bad1 100644 --- a/source/app/blueprints/rest/v2/alerts_filters.py +++ b/source/app/blueprints/rest/v2/alerts_filters.py @@ -65,10 +65,13 @@ def get(self, identifier): try: saved_filter = alert_filter_get(identifier) return response_api_success(self._schema.dump(saved_filter)) - + except ObjectNotFoundError: return response_api_not_found() + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + def put(self, identifier): request_data = request.get_json() @@ -77,22 +80,28 @@ def put(self, identifier): new_saved_filter = self._load(request_data, instance=saved_filter, partial=True) alert_filter_update() return response_api_success(self._schema.dump(new_saved_filter)) - + except ValidationError as e: return response_api_error('Data error', data=e.messages) - + except ObjectNotFoundError: return response_api_not_found() + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + def delete(self, identifier): try: saved_filter = alert_filter_get(identifier) alert_filter_delete(saved_filter) return response_api_deleted() - + except ObjectNotFoundError: return response_api_not_found() + except BusinessProcessingError as e: + return response_api_error(e.get_message(), data=e.get_data()) + alerts_filters_blueprint = Blueprint('alerts_filters_rest_v2', __name__, url_prefix='/alerts-filters') alerts_filters_operations = AlertsFiltersOperations() From b68b34c713d339618db96717429bdf8b077e038f Mon Sep 17 00:00:00 2001 From: Elise17 Date: Tue, 21 Oct 2025 17:27:15 +0200 Subject: [PATCH 35/67] Added new test test_update_alert_filter_should_return_400 --- tests/tests_rest_alerts_filters.py | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/tests_rest_alerts_filters.py b/tests/tests_rest_alerts_filters.py index 3d7635ade..9ed244f1b 100644 --- a/tests/tests_rest_alerts_filters.py +++ b/tests/tests_rest_alerts_filters.py @@ -542,3 +542,38 @@ def test_get_alert_filter_should_return_404_after_delete_alert_filter(self): self._subject.delete(f'/api/v2/alerts-filters/{identifier}') response = self._subject.get(f'/api/v2/alerts-filters/{identifier}') self.assertEqual(404, response.status_code) + + def test_update_alert_filter_should_return_400(self): + body = { + 'filter_is_private': 'true', + 'filter_type': 'alerts', + 'filter_name': 'filter name', + 'filter_description': 'filter description', + 'filter_data' : { + 'alert_title': 'filter name', + 'alert_description': '', + 'alert_source': '', + 'alert_tags': '', + 'alert_severity_id': '', + 'alert_start_date': '', + 'source_start_date': '', + 'source_end_date': '', + 'creation_end_date': '', + 'creation_start_date': '', + 'alert_assets': '', + 'alert_iocs': '', + 'alert_ids': '', + 'source_reference': '', + 'case_id': '', + 'custom_conditions': '', + + } + } + + response = self._subject.create('/api/v2/alerts-filters', body).json() + identifier = response['filter_id'] + body = { + 'filter_name': 1, + } + response = self._subject.update(f'/api/v2/alerts-filters/{identifier}', body) + self.assertEqual(400, response.status_code) From 2dbfb95366bce1060224e2654ce1461c66076d71 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:24:27 +0200 Subject: [PATCH 36/67] Renamed method --- source/app/blueprints/rest/v2/alerts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/alerts.py b/source/app/blueprints/rest/v2/alerts.py index afd2eea6d..0451f1932 100644 --- a/source/app/blueprints/rest/v2/alerts.py +++ b/source/app/blueprints/rest/v2/alerts.py @@ -157,7 +157,7 @@ def create(self): except BusinessProcessingError as e: return response_api_error(e.get_message(), data=e.get_data()) - def get(self, identifier): + def read(self, identifier): try: alert = alerts_get(iris_current_user, identifier) @@ -255,7 +255,7 @@ def create_alert(): @alerts_blueprint.get('/') @ac_api_requires(Permissions.alerts_read) def get_alert(identifier): - return alerts_operations.get(identifier) + return alerts_operations.read(identifier) @alerts_blueprint.put('/') From d1986e9cbaed0e2bc7ecd26a828b84d397db4363 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:26:27 +0200 Subject: [PATCH 37/67] Renamed method --- source/app/blueprints/rest/v2/case_routes/events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_routes/events.py b/source/app/blueprints/rest/v2/case_routes/events.py index 5de3d9d62..dc7bca28c 100644 --- a/source/app/blueprints/rest/v2/case_routes/events.py +++ b/source/app/blueprints/rest/v2/case_routes/events.py @@ -82,7 +82,7 @@ def create(self, case_identifier): except ValidationError as e: return response_api_error('Data error', data=e.normalized_messages()) - def get(self, case_identifier, identifier): + def read(self, case_identifier, identifier): if not cases_exists(case_identifier): return response_api_not_found() @@ -163,7 +163,7 @@ def create_event(case_identifier): @case_events_blueprint.get('/') @ac_api_requires() def get_event(case_identifier, identifier): - return events.get(case_identifier, identifier) + return events.read(case_identifier, identifier) @case_events_blueprint.put('/') From 7a8cfc5985fe9396f503973cba91d47a6af0a4c6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:35:26 +0200 Subject: [PATCH 38/67] Aligned method call --- source/app/business/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/business/events.py b/source/app/business/events.py index 9e9f5e2f6..50b103375 100644 --- a/source/app/business/events.py +++ b/source/app/business/events.py @@ -44,7 +44,7 @@ def events_create(case_identifier, event: CasesEvent, event_category_id, event_a add_obj_history_entry(event, 'created') db.session.add(event) - update_timeline_state(caseid=case_identifier) + update_timeline_state(case_identifier) db.session.commit() save_event_category(event.event_id, event_category_id) From ddef8923b88991b49232f8e4c93d6e3b313dfb5f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:44:31 +0200 Subject: [PATCH 39/67] Started implementation of GET /api/v2/manage/customers/{identifier} --- source/app/blueprints/rest/v2/case_routes/events.py | 3 ++- .../blueprints/rest/v2/manage_routes/customers.py | 13 +++++++++++++ source/app/business/customers.py | 10 ++++++++++ source/app/datamgmt/client/client_db.py | 6 +++--- tests/tests_rest_customers.py | 7 +++++++ 5 files changed, 35 insertions(+), 4 deletions(-) diff --git a/source/app/blueprints/rest/v2/case_routes/events.py b/source/app/blueprints/rest/v2/case_routes/events.py index dc7bca28c..e92e8185a 100644 --- a/source/app/blueprints/rest/v2/case_routes/events.py +++ b/source/app/blueprints/rest/v2/case_routes/events.py @@ -20,7 +20,8 @@ from flask import request from marshmallow.exceptions import ValidationError -from app.blueprints.access_controls import ac_api_requires, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_api_requires +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_success from app.blueprints.rest.endpoints import response_api_deleted diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 830a7a0de..f02fd59f1 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -22,10 +22,12 @@ from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_error +from app.blueprints.rest.endpoints import response_api_success from app.blueprints.access_controls import ac_api_requires from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema from app.business.customers import customers_create +from app.business.customers import customers_get from app.blueprints.iris_user import iris_current_user @@ -44,6 +46,11 @@ def create(self): except ValidationError as e: return response_api_error('Data error', data=e.messages) + def read(self, identifier): + customer = customers_get(identifier) + result = self._schema.dump(customer) + return response_api_success(result) + customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') @@ -54,3 +61,9 @@ def create(self): @ac_api_requires(Permissions.customers_write) def create_customer(): return customers.create() + + +@customers_blueprint.get('/') +@ac_api_requires() +def get_event(identifier): + return customers.read(identifier) diff --git a/source/app/business/customers.py b/source/app/business/customers.py index cacb7805d..d8c09f855 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -20,9 +20,19 @@ from app.models.models import Client from app.iris_engine.utils.tracker import track_activity from app.datamgmt.manage.manage_users_db import add_user_to_customer +from app.datamgmt.client.client_db import get_client +from app.business.errors import ObjectNotFoundError def customers_create(user, customer: Client): create_client(customer) track_activity(f'Added customer {customer.name}', ctx_less=True) add_user_to_customer(user.id, customer.client_id) + + +def customers_get(identifier) -> Client: + customer = get_client(identifier) + if not customer: + raise ObjectNotFoundError() + return customer + diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index ed2f745d0..90be32230 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -20,6 +20,7 @@ from sqlalchemy import func from sqlalchemy import and_ from typing import List +from typing import Optional from app import db from app.datamgmt.exceptions.ElementExceptions import ElementInUseException @@ -59,9 +60,8 @@ def get_client_list(current_user_id: int = None, return output -def get_client(client_id: int) -> Client: - client = Client.query.filter(Client.client_id == client_id).first() - return client +def get_client(client_id: int) -> Optional[Client]: + return Client.query.filter(Client.client_id == client_id).first() def get_client_api(client_id: str) -> Client: diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 502a60ddc..423bf4bbd 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -72,3 +72,10 @@ def test_create_customer_should_add_user_to_the_customer(self): for customer in response['user_customers']: user_customers_identifiers.append(customer['customer_id']) self.assertIn(identifier, user_customers_identifiers) + + def test_get_customer_should_return_200(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + response = self._subject.get(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(200, response.status_code) From 6096f3bab4f69fefe7478b8acf5e5adff507c5e8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:36:26 +0200 Subject: [PATCH 40/67] Moved code down into persistence layer --- source/app/business/access_controls.py | 4 +-- .../manage/manage_access_control_db.py | 32 ++++++++++++++++--- .../app/iris_engine/access_control/utils.py | 29 +++-------------- source/app/post_init.py | 8 ++--- 4 files changed, 37 insertions(+), 36 deletions(-) diff --git a/source/app/business/access_controls.py b/source/app/business/access_controls.py index f340f711a..ef91d8ba2 100644 --- a/source/app/business/access_controls.py +++ b/source/app/business/access_controls.py @@ -20,7 +20,7 @@ from app.datamgmt.manage.manage_access_control_db import get_case_effective_access from app.datamgmt.manage.manage_access_control_db import remove_duplicate_user_case_effective_accesses -from app.datamgmt.manage.manage_access_control_db import set_user_case_effective_access +from app.datamgmt.manage.manage_access_control_db import add_user_case_effective_access from app.datamgmt.manage.manage_access_control_db import check_ua_case_client from app.datamgmt.manage.manage_access_control_db import user_has_client_access from app.logger import logger @@ -64,7 +64,7 @@ def set_case_effective_access_for_user(user_id, case_id, access_level: int): if remove_duplicate_user_case_effective_accesses(user_id, case_id): logger.error(f'Multiple access found for user {user_id} and case {case_id}') - set_user_case_effective_access(access_level, case_id, user_id) + add_user_case_effective_access(user_id, case_id, access_level) def ac_fast_check_user_has_case_access(user_id, cid, expected_access_levels: list[CaseAccessLevel]): diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index cb65fdc18..0c45e5fa1 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -16,10 +16,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import and_ -from app import ac_current_user_has_permission +from app import ac_current_user_has_permission, db from app import db from app.models.cases import Cases -from app.models.authorization import Group +from app.models.authorization import Group, UserCaseEffectiveAccess from app.models.authorization import UserClient from app.models.authorization import UserCaseEffectiveAccess from app.models.authorization import Permissions @@ -193,12 +193,34 @@ def remove_duplicate_user_case_effective_accesses(user_id, case_id): return True -def set_user_case_effective_access(access_level, case_id, user_id): +def add_user_case_effective_access(user_identifier, case_identifier, access_level): uac = UserCaseEffectiveAccess.query.where(and_( - UserCaseEffectiveAccess.user_id == user_id, - UserCaseEffectiveAccess.case_id == case_id + UserCaseEffectiveAccess.user_id == user_identifier, + UserCaseEffectiveAccess.case_id == case_identifier )).first() if uac: uac = uac[0] uac.access_level = access_level db.session.commit() + + +def add_several_user_effective_access(user_identifiers, case_identifier, access_level): + """ + Directly add a set of effective user access + """ + + UserCaseEffectiveAccess.query.filter( + UserCaseEffectiveAccess.case_id == case_identifier, + UserCaseEffectiveAccess.user_id.in_(user_identifiers) + ).delete() + + access_to_add = [] + for user_id in user_identifiers: + ucea = UserCaseEffectiveAccess() + ucea.user_id = user_id + ucea.case_id = case_identifier + ucea.access_level = access_level + access_to_add.append(ucea) + + db.session.add_all(access_to_add) + db.session.commit() diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 176e46956..7888534fd 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -3,6 +3,7 @@ from app import db from app.business.access_controls import set_case_effective_access_for_user +from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.logger import logger from app.blueprints.iris_user import iris_current_user from app.models.cases import Cases @@ -320,33 +321,11 @@ def ac_add_users_multi_effective_access(users_list, cases_list, access_level): Add multiple users to multiple cases with a specific access level """ for case_id in cases_list: - ac_add_user_effective_access(users_list, case_id=case_id, access_level=access_level) + add_several_user_effective_access(users_list, case_identifier=case_id, access_level=access_level) return -def ac_add_user_effective_access(users_list, case_id, access_level): - """ - Directly add a set of effective user access - """ - - UserCaseEffectiveAccess.query.filter( - UserCaseEffectiveAccess.case_id == case_id, - UserCaseEffectiveAccess.user_id.in_(users_list) - ).delete() - - access_to_add = [] - for user_id in users_list: - ucea = UserCaseEffectiveAccess() - ucea.user_id = user_id - ucea.case_id = case_id - ucea.access_level = access_level - access_to_add.append(ucea) - - db.session.add_all(access_to_add) - db.session.commit() - - def ac_add_user_effective_access_from_map(users_map, case_id): """ Directly add a set of effective user access @@ -381,7 +360,7 @@ def ac_set_new_case_access(org_members, case_id, customer_id = None): users_full_access = list(set([u.id for u in users_full]) - set(users.keys())) # Default users case access - Full access - ac_add_user_effective_access(users_full_access, case_id, CaseAccessLevel.deny_all.value) + add_several_user_effective_access(users_full_access, case_id, CaseAccessLevel.deny_all.value) # Add specific right for the user creating the case UserCaseAccess.query.filter( @@ -396,7 +375,7 @@ def ac_set_new_case_access(org_members, case_id, customer_id = None): db.session.add(uca) db.session.commit() - ac_add_user_effective_access([iris_current_user.id], case_id, CaseAccessLevel.full_access.value) + add_several_user_effective_access([iris_current_user.id], case_id, CaseAccessLevel.full_access.value) # Add customer permissions for all users belonging to the customer if customer_id: diff --git a/source/app/post_init.py b/source/app/post_init.py index af692750b..c4dd3c924 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -37,7 +37,7 @@ from app import bc from app import celery from app import db -from app.iris_engine.access_control.utils import ac_add_user_effective_access +from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases from app.iris_engine.access_control.utils import ac_get_mask_analyst from app.iris_engine.access_control.utils import ac_get_mask_full_permissions @@ -744,9 +744,9 @@ def create_safe_case(user, client, groups): add_case_access_to_group(group=group, cases_list=[case.case_id], access_level=CaseAccessLevel.full_access.value) - ac_add_user_effective_access(users_list=[user.id], - case_id=1, - access_level=CaseAccessLevel.full_access.value) + add_several_user_effective_access(user_identifiers=[user.id], + case_identifier=1, + access_level=CaseAccessLevel.full_access.value) return case From c7c77d0ecd931268d2ab255cd84bcad5a0123dec Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 08:37:06 +0200 Subject: [PATCH 41/67] Removed unused imports --- source/app/datamgmt/manage/manage_access_control_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/datamgmt/manage/manage_access_control_db.py b/source/app/datamgmt/manage/manage_access_control_db.py index 0c45e5fa1..bdddeea8d 100644 --- a/source/app/datamgmt/manage/manage_access_control_db.py +++ b/source/app/datamgmt/manage/manage_access_control_db.py @@ -16,10 +16,10 @@ # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from sqlalchemy import and_ -from app import ac_current_user_has_permission, db +from app import ac_current_user_has_permission from app import db from app.models.cases import Cases -from app.models.authorization import Group, UserCaseEffectiveAccess +from app.models.authorization import Group from app.models.authorization import UserClient from app.models.authorization import UserCaseEffectiveAccess from app.models.authorization import Permissions From 115d9aeccba22743d2e6bda6f660a00d715fa7ac Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:25:03 +0200 Subject: [PATCH 42/67] Added tow new import constraints: do not import marshables and do not import the engine from the persistence layer --- pyproject.toml | 14 ++++++++++++++ .../blueprints/pages/manage/manage_cases_routes.py | 4 +++- source/app/blueprints/rest/case/case_ioc_routes.py | 7 +++++-- .../blueprints/rest/manage/manage_case_state.py | 3 ++- source/app/datamgmt/case/case_iocs_db.py | 8 +++----- source/app/datamgmt/manage/manage_case_state_db.py | 5 +---- source/app/schema/marshables.py | 4 +++- 7 files changed, 31 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f4a5197dd..66f33cccb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,3 +42,17 @@ source_modules = "app.datamgmt.dashboard" forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true +[[tool.importlinter.contracts]] +name = "Do not import marshables from the persistence layer" +type = "forbidden" +source_modules = "app.datamgmt.manage.manage_case_state_db" +forbidden_modules = "app.schema.marshables" +allow_indirect_imports = true + +[[tool.importlinter.contracts]] +name = "Do not import the engine from the persistence layer" +type = "forbidden" +source_modules = "app.datamgmt.case" +forbidden_modules = "app.iris_engine" +allow_indirect_imports = true + diff --git a/source/app/blueprints/pages/manage/manage_cases_routes.py b/source/app/blueprints/pages/manage/manage_cases_routes.py index c5b3a691d..8a2a96725 100644 --- a/source/app/blueprints/pages/manage/manage_cases_routes.py +++ b/source/app/blueprints/pages/manage/manage_cases_routes.py @@ -41,6 +41,7 @@ from app.blueprints.access_controls import ac_api_return_access_denied, ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_requires from app.blueprints.responses import response_error +from app.schema.marshables import CaseStateSchema manage_cases_blueprint = Blueprint('manage_case', __name__, @@ -81,6 +82,7 @@ def _details_case(cur_id: int, caseid: int, url_redir: bool) -> Union[str, Respo case_classifications = get_case_classifications_list() case_states = get_case_states_list() + dumped_case_states = CaseStateSchema(many=True).dump(case_states) user_is_server_administrator = ac_current_user_has_permission(Permissions.server_administrator) customers = get_client_list(current_user_id=iris_current_user.id, @@ -92,7 +94,7 @@ def _details_case(cur_id: int, caseid: int, url_redir: bool) -> Union[str, Respo form = FlaskForm() return render_template("modal_case_info_from_case.html", data=res, form=form, protagonists=protagonists, - case_classifications=case_classifications, case_states=case_states, customers=customers, + case_classifications=case_classifications, case_states=dumped_case_states, customers=customers, severities=severities) diff --git a/source/app/blueprints/rest/case/case_ioc_routes.py b/source/app/blueprints/rest/case/case_ioc_routes.py index 7041c10a6..10f0d82b5 100644 --- a/source/app/blueprints/rest/case/case_ioc_routes.py +++ b/source/app/blueprints/rest/case/case_ioc_routes.py @@ -51,12 +51,14 @@ from app.models.authorization import CaseAccessLevel from app.schema.marshables import CommentSchema from app.schema.marshables import IocSchema -from app.blueprints.access_controls import ac_requires_case_identifier, ac_fast_check_current_user_has_case_access +from app.blueprints.access_controls import ac_requires_case_identifier +from app.blueprints.access_controls import ac_fast_check_current_user_has_case_access from app.blueprints.access_controls import ac_api_requires from app.blueprints.access_controls import ac_api_return_access_denied from app.blueprints.responses import response_error from app.blueprints.responses import response_success from app.iris_engine.module_handler.module_handler import call_deprecated_on_preload_modules_hook +from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access case_ioc_rest_blueprint = Blueprint('case_ioc_rest', __name__) @@ -74,7 +76,8 @@ def case_list_ioc(caseid): out = ioc._asdict() # Get links of the IoCs seen in other cases - ial = get_ioc_links(ioc.ioc_id) + user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) + ial = get_ioc_links(ioc.ioc_id, user_search_limitations) out['link'] = [row._asdict() for row in ial] # Legacy, must be changed next version diff --git a/source/app/blueprints/rest/manage/manage_case_state.py b/source/app/blueprints/rest/manage/manage_case_state.py index 57b8461db..cc0618dfc 100644 --- a/source/app/blueprints/rest/manage/manage_case_state.py +++ b/source/app/blueprints/rest/manage/manage_case_state.py @@ -45,7 +45,8 @@ def list_case_state() -> Response: Flask Response object """ - l_cl = get_case_states_list() + case_states = get_case_states_list() + l_cl = CaseStateSchema(many=True).dump(case_states) return response_success("", data=l_cl) diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index b95d110b3..a7069febf 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -23,7 +23,6 @@ from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_ioc_state -from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access from app.models.alerts import Alert from app.models.cases import Cases from app.models.cases import CasesEvent @@ -119,12 +118,11 @@ def get_detailed_iocs(caseid): return detailed_iocs -def get_ioc_links(ioc_id): - search_condition = and_(Cases.case_id.in_([])) - - user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) +def get_ioc_links(ioc_id, user_search_limitations): if user_search_limitations: search_condition = and_(Cases.case_id.in_(user_search_limitations)) + else: + search_condition = and_(Cases.case_id.in_([])) ioc = Ioc.query.filter(Ioc.ioc_id == ioc_id).first() diff --git a/source/app/datamgmt/manage/manage_case_state_db.py b/source/app/datamgmt/manage/manage_case_state_db.py index 3e6ff268f..e1d45b884 100644 --- a/source/app/datamgmt/manage/manage_case_state_db.py +++ b/source/app/datamgmt/manage/manage_case_state_db.py @@ -17,7 +17,6 @@ from typing import List from app.models.cases import CaseState -from app.schema.marshables import CaseStateSchema def get_case_states_list() -> List[dict]: @@ -26,9 +25,7 @@ def get_case_states_list() -> List[dict]: Returns: List[dict]: List of case state """ - case_state = CaseState.query.all() - - return CaseStateSchema(many=True).dump(case_state) + return CaseState.query.all() def get_case_state_by_id(cur_id: int) -> CaseState: diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index 354a8c51d..e74f41af2 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -98,6 +98,7 @@ from app.business.users import get_primary_organisation from app.business.users import get_organisations from app.datamgmt.case.assets_type import get_asset_type_by_name_case_insensitive +from app.iris_engine.access_control.utils import ac_get_fast_user_cases_access ALLOWED_EXTENSIONS = {'png', 'svg'} @@ -941,7 +942,8 @@ class IocSchemaForAPIV2(ma.SQLAlchemyAutoSchema): tlp = ma.Nested(TlpSchema) def get_link(self, ioc): - ial = get_ioc_links(ioc.ioc_id) + user_search_limitations = ac_get_fast_user_cases_access(iris_current_user.id) + ial = get_ioc_links(ioc.ioc_id, user_search_limitations) return [row._asdict() for row in ial] link = ma.Method('get_link') From 497135d501ea98e96cb0364d31b7069bd2f5fa32 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:25:31 +0200 Subject: [PATCH 43/67] One import per line --- source/app/datamgmt/case/case_iocs_db.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index a7069febf..3f9e1e836 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -28,7 +28,8 @@ from app.models.cases import CasesEvent from app.models.models import Client from app.models.models import CaseAssets -from app.models.comments import Comments, IocComments +from app.models.comments import Comments +from app.models.comments import IocComments from app.models.iocs import Ioc from app.models.models import IocType from app.models.iocs import Tlp From 26514620f9f7c4f45aa82c31139816831a5e1d18 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:26:25 +0200 Subject: [PATCH 44/67] Removed dead code --- source/app/datamgmt/case/case_iocs_db.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/source/app/datamgmt/case/case_iocs_db.py b/source/app/datamgmt/case/case_iocs_db.py index 3f9e1e836..f3a6be745 100644 --- a/source/app/datamgmt/case/case_iocs_db.py +++ b/source/app/datamgmt/case/case_iocs_db.py @@ -19,7 +19,6 @@ from sqlalchemy import and_ from app import db -from app import app from app.blueprints.iris_user import iris_current_user from app.datamgmt.filtering import get_filtered_data from app.datamgmt.states import update_ioc_state @@ -37,7 +36,6 @@ from app.models.pagination_parameters import PaginationParameters from app.util import add_obj_history_entry -log = app.logger relationship_model_map = { 'case': Cases, From 358d2d102a60e543dbb0763464112815bba5ede3 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:28:56 +0200 Subject: [PATCH 45/67] One import per line --- source/app/datamgmt/case/case_db.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index d0b3cbf59..5c73f6ce6 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -29,7 +29,8 @@ from app.models.authorization import User from app.models.cases import CaseProtagonist from app.models.cases import Cases -from app.models.models import CaseTemplateReport, ReviewStatus +from app.models.models import CaseTemplateReport +from app.models.models import ReviewStatus from app.models.models import Client from app.models.models import Languages from app.models.models import ReportType From 2c041aa9dfbe721cb12f968734fb960fc38ba7de Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:36:21 +0200 Subject: [PATCH 46/67] Following calling conventions --- source/app/post_init.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/source/app/post_init.py b/source/app/post_init.py index c4dd3c924..8d9e9672c 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -741,12 +741,8 @@ def create_safe_case(user, client, groups): # Add the specified user and groups to the case with full access level for group in groups: - add_case_access_to_group(group=group, - cases_list=[case.case_id], - access_level=CaseAccessLevel.full_access.value) - add_several_user_effective_access(user_identifiers=[user.id], - case_identifier=1, - access_level=CaseAccessLevel.full_access.value) + add_case_access_to_group(group, [case.case_id], CaseAccessLevel.full_access.value) + add_several_user_effective_access([user.id], 1, CaseAccessLevel.full_access.value) return case From 3a6204bcb4e2af759fc8d4896fa52c5f2fa34a5f Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:49:48 +0200 Subject: [PATCH 47/67] Moved code down into the business and persistence layers --- source/app/business/cases.py | 8 +++++++- source/app/datamgmt/case/case_db.py | 9 ++++++++- source/app/post_init.py | 27 +++++++++------------------ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 8fec77c50..c3de558f1 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -52,10 +52,12 @@ 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 from app.datamgmt.manage.manage_cases_db import get_filtered_cases from app.datamgmt.dashboard.dashboard_db import list_user_cases from app.datamgmt.dashboard.dashboard_db import list_user_reviews +from app.datamgmt.case.case_db import get_first_case_with_customer +from app.models.cases import Cases +from app.models.models import Client def cases_filter(current_user, pagination_parameters, name, case_identifiers, customer_identifier, @@ -98,6 +100,10 @@ def cases_get_first() -> Cases: return get_first_case() +def cases_get_first_with_customer(client: Client) -> Cases: + return get_first_case_with_customer(client.client_id) + + def cases_exists(identifier): return case_db_exists(identifier) diff --git a/source/app/datamgmt/case/case_db.py b/source/app/datamgmt/case/case_db.py index 5c73f6ce6..766ab7363 100644 --- a/source/app/datamgmt/case/case_db.py +++ b/source/app/datamgmt/case/case_db.py @@ -36,10 +36,17 @@ from app.models.models import ReportType -def get_first_case() -> Cases: +def get_first_case() -> Optional[Cases]: return Cases.query.order_by(Cases.case_id).first() +def get_first_case_with_customer(customer_identifier) -> Optional[Cases]: + case = Cases.query.filter( + Cases.client_id == customer_identifier + ).first() + return case + + def get_case_summary(caseid): case_summary = Cases.query.filter( Cases.case_id == caseid diff --git a/source/app/post_init.py b/source/app/post_init.py index 8d9e9672c..f9210a717 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -37,6 +37,7 @@ from app import bc from app import celery from app import db +from app.business.cases import cases_get_first_with_customer from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases from app.iris_engine.access_control.utils import ac_get_mask_analyst @@ -45,7 +46,8 @@ from app.iris_engine.module_handler.module_handler import instantiate_module_from_name from app.iris_engine.module_handler.module_handler import register_module from app.iris_engine.demo_builder import create_demo_users -from app.models.models import create_safe_limited, AssetsType +from app.models.models import create_safe_limited +from app.models.models import AssetsType from app.models.alerts import Severity from app.models.alerts import AlertStatus from app.models.alerts import AlertResolutionStatus @@ -696,7 +698,7 @@ def create_safe_assets(): create_asset_type_if_not_exists(db.session, AssetsType(**asse_type)) -def create_safe_client(): +def create_safe_client() -> Client: """Creates a new Client object if it does not already exist. This function creates a new Client object with the specified client name @@ -704,10 +706,7 @@ def create_safe_client(): """ # Create a new Client object if it does not already exist - client = get_or_create(db.session, Client, - name="IrisInitialClient") - - return client + return get_or_create(db.session, Client, name='IrisInitialClient') def create_safe_case(user, client, groups): @@ -719,9 +718,7 @@ def create_safe_case(user, client, groups): """ # Check if a case already exists for the client - case = Cases.query.filter( - Cases.client_id == client.client_id - ).first() + case = cases_get_first_with_customer(client) if not case: # Create a new case for the client @@ -744,8 +741,6 @@ def create_safe_case(user, client, groups): add_case_access_to_group(group, [case.case_id], CaseAccessLevel.full_access.value) add_several_user_effective_access([user.id], 1, CaseAccessLevel.full_access.value) - return case - def create_safe_report_types(): """Creates new ReportType objects if they do not already exist. @@ -1654,15 +1649,11 @@ def run(self): self._logger.info("Registering default modules") self._register_default_modules() - self._logger.info("Creating initial customer") + self._logger.info('Creating initial customer') client = create_safe_client() - self._logger.info("Creating initial case") - create_safe_case( - user=admin, - client=client, - groups=[gadm, ganalysts] - ) + self._logger.info('Creating initial case') + create_safe_case(admin, client, [gadm, ganalysts]) # Setup symlinks for custom_assets self._logger.info('Creating symlinks for custom asset icons') From acd242b87a07c0479ef8dac8e5353aa48c61bd95 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:52:26 +0200 Subject: [PATCH 48/67] Early return --- source/app/models/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/app/models/models.py b/source/app/models/models.py index a4a2f0616..17e122440 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -109,11 +109,11 @@ def get_or_create(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: return instance - else: - instance = model(**kwargs) - session.add(instance) - session.commit() - return instance + + instance = model(**kwargs) + session.add(instance) + session.commit() + return instance class Client(db.Model): From cef2751c2dec7923f15d717379211fe5f804fd7d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 10:53:11 +0200 Subject: [PATCH 49/67] Simple quotes --- source/app/post_init.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/app/post_init.py b/source/app/post_init.py index f9210a717..653856161 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -1323,8 +1323,8 @@ def _create_safe_auth_model(self): """ # Create new Organisation object - def_org = get_or_create(db.session, Organisation, org_name="Default Org", - org_description="Default Organisation") + def_org = get_or_create(db.session, Organisation, org_name='Default Org', + org_description='Default Organisation') # Create new Administrator Group object try: From 3fe05d19d69ec01a32d6588f180eef1c06ba77d4 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:06:59 +0200 Subject: [PATCH 50/67] Introduced method to get a customer by its name --- .../pages/manage/manage_customers_routes.py | 4 ++-- .../blueprints/rest/manage/manage_customers_routes.py | 6 +++--- source/app/business/customers.py | 11 +++++++++-- source/app/datamgmt/client/client_db.py | 9 ++++++--- source/app/post_init.py | 8 +++++++- 5 files changed, 27 insertions(+), 11 deletions(-) diff --git a/source/app/blueprints/pages/manage/manage_customers_routes.py b/source/app/blueprints/pages/manage/manage_customers_routes.py index 695b6c27e..e13e8716e 100644 --- a/source/app/blueprints/pages/manage/manage_customers_routes.py +++ b/source/app/blueprints/pages/manage/manage_customers_routes.py @@ -22,7 +22,7 @@ from flask import url_for from flask_wtf import FlaskForm -from app.datamgmt.client.client_db import get_client +from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_client_api from app.datamgmt.client.client_db import get_client_contact from app.datamgmt.client.client_db import get_client_contacts @@ -115,7 +115,7 @@ def view_customer_modal(client_id, caseid, url_redir): return redirect(url_for('manage_customers.manage_customers', cid=caseid)) form = AddCustomerForm() - customer = get_client(client_id) + customer = get_customer(client_id) if not customer: return response_error("Invalid Customer ID") diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index a0678ea95..c7ccddb7c 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -29,7 +29,7 @@ from app.datamgmt.client.client_db import create_contact from app.datamgmt.client.client_db import delete_client from app.datamgmt.client.client_db import delete_contact -from app.datamgmt.client.client_db import get_client +from app.datamgmt.client.client_db import get_customer from app.datamgmt.client.client_db import get_client_api from app.datamgmt.client.client_db import get_client_cases from app.datamgmt.client.client_db import get_client_contacts @@ -81,7 +81,7 @@ def customer_update_contact(client_id, contact_id): if not request.is_json: return response_error("Invalid request") - if not get_client(client_id): + if not get_customer(client_id): return response_error(f"Invalid Customer ID {client_id}") try: @@ -110,7 +110,7 @@ def customer_add_contact(client_id): if not request.is_json: return response_error("Invalid request") - if not get_client(client_id): + if not get_customer(client_id): return response_error(f"Invalid Customer ID {client_id}") try: diff --git a/source/app/business/customers.py b/source/app/business/customers.py index d8c09f855..dc2e0164a 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -20,7 +20,8 @@ from app.models.models import Client from app.iris_engine.utils.tracker import track_activity from app.datamgmt.manage.manage_users_db import add_user_to_customer -from app.datamgmt.client.client_db import get_client +from app.datamgmt.client.client_db import get_customer +from app.datamgmt.client.client_db import get_customer_by_name from app.business.errors import ObjectNotFoundError @@ -31,8 +32,14 @@ def customers_create(user, customer: Client): def customers_get(identifier) -> Client: - customer = get_client(identifier) + customer = get_customer(identifier) if not customer: raise ObjectNotFoundError() return customer + +def customers_get_by_name(name) -> Client: + customer = get_customer_by_name(name) + if not customer: + raise ObjectNotFoundError() + return customer diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 90be32230..7ed6ef28b 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -60,7 +60,7 @@ def get_client_list(current_user_id: int = None, return output -def get_client(client_id: int) -> Optional[Client]: +def get_customer(client_id: int) -> Optional[Client]: return Client.query.filter(Client.client_id == client_id).first() @@ -101,7 +101,6 @@ def get_client_cases(client_id: int): def create_client(customer: Client): - db.session.add(customer) db.session.commit() @@ -166,7 +165,7 @@ def update_contact(data, contact_id, customer_id) -> Contact: def update_client(client_id: int, data) -> Client: # TODO: Possible reuse somewhere else ... - client = get_client(client_id) + client = get_customer(client_id) if not client: raise ElementNotFoundException('No Customer found with this uuid.') @@ -213,3 +212,7 @@ def get_case_client(case_id: int) -> Client: ).first() return client + + +def get_customer_by_name(name) -> Client: + return db.session.query(Client).filter_by(name=name).first() \ No newline at end of file diff --git a/source/app/post_init.py b/source/app/post_init.py index 653856161..4dd954d08 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -38,6 +38,7 @@ from app import celery from app import db from app.business.cases import cases_get_first_with_customer +from app.business.errors import ObjectNotFoundError from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases from app.iris_engine.access_control.utils import ac_get_mask_analyst @@ -76,6 +77,7 @@ from app.models.models import create_safe from app.models.models import create_safe_attr from app.business.asset_types import create_asset_type_if_not_exists +from app.business.customers import customers_get_by_name from app.models.models import get_or_create from app.datamgmt.iris_engine.modules_db import iris_module_disable_by_id from app.datamgmt.manage.manage_groups_db import add_case_access_to_group @@ -84,6 +86,7 @@ from app.datamgmt.manage.manage_groups_db import get_group_by_name +_INITIAL_CLIENT_NAME = 'IrisInitialClient' _ASSET_TYPES = [ {'asset_name': 'Account', 'asset_description': 'Generic Account', 'asset_icon_not_compromised': 'user.png', 'asset_icon_compromised': 'ioc_user.png'}, @@ -706,7 +709,10 @@ def create_safe_client() -> Client: """ # Create a new Client object if it does not already exist - return get_or_create(db.session, Client, name='IrisInitialClient') + try: + return customers_get_by_name(_INITIAL_CLIENT_NAME) + except ObjectNotFoundError: + return get_or_create(db.session, Client, name=_INITIAL_CLIENT_NAME) def create_safe_case(user, client, groups): From 99842fca853da703c8818b11ad102c3d7a15e5d5 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:14:19 +0200 Subject: [PATCH 51/67] Ruff warning --- source/app/datamgmt/client/client_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 7ed6ef28b..6d46dba0c 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -215,4 +215,4 @@ def get_case_client(case_id: int) -> Client: def get_customer_by_name(name) -> Client: - return db.session.query(Client).filter_by(name=name).first() \ No newline at end of file + return db.session.query(Client).filter_by(name=name).first() From 50b4982591e6438193b4e6a26646d67b6348b16b Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:19:55 +0200 Subject: [PATCH 52/67] Added method to create a customer in the business layer --- .../blueprints/rest/manage/manage_customers_routes.py | 4 ++-- .../app/blueprints/rest/v2/manage_routes/customers.py | 4 ++-- source/app/business/customers.py | 11 ++++++++--- source/app/datamgmt/client/client_db.py | 2 +- source/app/post_init.py | 11 +++++++---- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index c7ccddb7c..efe0bdfa1 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -25,7 +25,7 @@ from app import ac_current_user_has_permission from app.blueprints.access_controls import ac_api_requires from app.blueprints.iris_user import iris_current_user -from app.datamgmt.client.client_db import create_client +from app.datamgmt.client.client_db import create_customer from app.datamgmt.client.client_db import create_contact from app.datamgmt.client.client_db import delete_client from app.datamgmt.client.client_db import delete_contact @@ -250,7 +250,7 @@ def add_customers(): try: customer = customer_schema.load(request.json) - create_client(customer) + create_customer(customer) except ValidationError as e: return response_error(msg='Error adding customer', data=e.messages) except Exception as e: diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index f02fd59f1..264f940c5 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -26,7 +26,7 @@ from app.blueprints.access_controls import ac_api_requires from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema -from app.business.customers import customers_create +from app.business.customers import customers_create_with_user from app.business.customers import customers_get from app.blueprints.iris_user import iris_current_user @@ -40,7 +40,7 @@ def create(self): try: request_data = request.get_json() customer = self._schema.load(request_data) - customers_create(iris_current_user, customer) + customers_create_with_user(iris_current_user, customer) result = self._schema.dump(customer) return response_api_created(result) except ValidationError as e: diff --git a/source/app/business/customers.py b/source/app/business/customers.py index dc2e0164a..a38639317 100644 --- a/source/app/business/customers.py +++ b/source/app/business/customers.py @@ -16,7 +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 app.datamgmt.client.client_db import create_client +from app.datamgmt.client.client_db import create_customer from app.models.models import Client from app.iris_engine.utils.tracker import track_activity from app.datamgmt.manage.manage_users_db import add_user_to_customer @@ -25,8 +25,13 @@ from app.business.errors import ObjectNotFoundError -def customers_create(user, customer: Client): - create_client(customer) +# TODO maybe this method should be removed and always create a customer with at least a user +def customers_create(customer: Client): + create_customer(customer) + + +def customers_create_with_user(user, customer: Client): + create_customer(customer) track_activity(f'Added customer {customer.name}', ctx_less=True) add_user_to_customer(user.id, customer.client_id) diff --git a/source/app/datamgmt/client/client_db.py b/source/app/datamgmt/client/client_db.py index 6d46dba0c..1f250cb11 100644 --- a/source/app/datamgmt/client/client_db.py +++ b/source/app/datamgmt/client/client_db.py @@ -100,7 +100,7 @@ def get_client_cases(client_id: int): return cases_list -def create_client(customer: Client): +def create_customer(customer: Client): db.session.add(customer) db.session.commit() diff --git a/source/app/post_init.py b/source/app/post_init.py index 4dd954d08..8a858c320 100644 --- a/source/app/post_init.py +++ b/source/app/post_init.py @@ -37,8 +37,6 @@ from app import bc from app import celery from app import db -from app.business.cases import cases_get_first_with_customer -from app.business.errors import ObjectNotFoundError from app.datamgmt.manage.manage_access_control_db import add_several_user_effective_access from app.iris_engine.demo_builder import create_demo_cases from app.iris_engine.access_control.utils import ac_get_mask_analyst @@ -76,9 +74,12 @@ from app.models.iocs import Tlp from app.models.models import create_safe from app.models.models import create_safe_attr +from app.models.models import get_or_create from app.business.asset_types import create_asset_type_if_not_exists from app.business.customers import customers_get_by_name -from app.models.models import get_or_create +from app.business.customers import customers_create +from app.business.cases import cases_get_first_with_customer +from app.business.errors import ObjectNotFoundError from app.datamgmt.iris_engine.modules_db import iris_module_disable_by_id from app.datamgmt.manage.manage_groups_db import add_case_access_to_group from app.datamgmt.manage.manage_users_db import add_user_to_group @@ -712,7 +713,9 @@ def create_safe_client() -> Client: try: return customers_get_by_name(_INITIAL_CLIENT_NAME) except ObjectNotFoundError: - return get_or_create(db.session, Client, name=_INITIAL_CLIENT_NAME) + customer = Client(name=_INITIAL_CLIENT_NAME) + customers_create(customer) + return customer def create_safe_case(user, client, groups): From 328247272232d368f33bc50c6c1a2e498433bc93 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 11:20:31 +0200 Subject: [PATCH 53/67] Added a TODO --- source/app/models/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/models/models.py b/source/app/models/models.py index 17e122440..fca339676 100644 --- a/source/app/models/models.py +++ b/source/app/models/models.py @@ -105,6 +105,7 @@ def create_safe_limited(session, model, keywords_list, **kwargs): return True +# TODO try to remove this method: too generic def get_or_create(session, model, **kwargs): instance = session.query(model).filter_by(**kwargs).first() if instance: From 77918403fc42b9b98695d9f8d56eb3cdc88ccbde Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 13:26:58 +0200 Subject: [PATCH 54/67] Do not import iris_current_user from app.business.cases --- pyproject.toml | 2 +- source/app/blueprints/graphql/cases.py | 2 +- .../blueprints/rest/manage/manage_cases_routes.py | 2 +- source/app/blueprints/rest/v2/cases.py | 2 +- source/app/blueprints/rest/v2/dashboard.py | 5 +++-- source/app/business/cases.py | 13 ++++++------- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66f33cccb..284baedb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ allow_indirect_imports = true [[tool.importlinter.contracts]] name = "Do not import API layer from the business layer" type = "forbidden" -source_modules = ["app.business.access_controls", "app.business.assets"] +source_modules = ["app.business.access_controls", "app.business.assets", "app.business.cases"] forbidden_modules = "app.blueprints.iris_user" allow_indirect_imports = true diff --git a/source/app/blueprints/graphql/cases.py b/source/app/blueprints/graphql/cases.py index 83d569f7e..d565ef94c 100644 --- a/source/app/blueprints/graphql/cases.py +++ b/source/app/blueprints/graphql/cases.py @@ -115,7 +115,7 @@ def mutate(root, info, name, description, client_id, soc_id=None, classification schema = CaseSchema() case = schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - result = cases_create(case, case_template_id) + result = cases_create(iris_current_user, case, case_template_id) return CaseCreate(case=result) diff --git a/source/app/blueprints/rest/manage/manage_cases_routes.py b/source/app/blueprints/rest/manage/manage_cases_routes.py index 96b13a798..e4f7a985a 100644 --- a/source/app/blueprints/rest/manage/manage_cases_routes.py +++ b/source/app/blueprints/rest/manage/manage_cases_routes.py @@ -251,7 +251,7 @@ def api_add_case(): request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) case = case_schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - result = cases_create(case, case_template_id) + result = cases_create(iris_current_user, case, case_template_id) return response_success('Case created', data=case_schema.dump(result)) except ValidationError as e: raise response_error('Data error', e.messages) diff --git a/source/app/blueprints/rest/v2/cases.py b/source/app/blueprints/rest/v2/cases.py index 0df9d52e6..62286641c 100644 --- a/source/app/blueprints/rest/v2/cases.py +++ b/source/app/blueprints/rest/v2/cases.py @@ -104,7 +104,7 @@ def create(self): request_data = call_deprecated_on_preload_modules_hook('case_create', request.get_json(), None) case = self._schema.load(request_data) case_template_id = request_data.pop('case_template_id', None) - case = cases_create(case, case_template_id) + case = cases_create(iris_current_user, case, case_template_id) result = self._schema.dump(case) return response_api_created(result) except ValidationError as e: diff --git a/source/app/blueprints/rest/v2/dashboard.py b/source/app/blueprints/rest/v2/dashboard.py index d026805c7..2bed0ad3e 100644 --- a/source/app/blueprints/rest/v2/dashboard.py +++ b/source/app/blueprints/rest/v2/dashboard.py @@ -20,6 +20,7 @@ from flask import request from app.blueprints.access_controls import ac_api_requires +from app.blueprints.iris_user import iris_current_user from app.blueprints.rest.endpoints import response_api_success from app.business.cases import cases_filter_by_user from app.business.cases import cases_filter_by_reviewer @@ -39,7 +40,7 @@ @ac_api_requires() def list_own_cases(): show_closed = request.args.get('show_closed', 'false', type=str).lower() - cases = cases_filter_by_user(show_closed == 'true') + cases = cases_filter_by_user(iris_current_user, show_closed == 'true') return response_api_success(data=CaseDetailsSchema(many=True).dump(cases)) @@ -58,7 +59,7 @@ def list_own_tasks(): @dashboard_blueprint.route('/reviews/list', methods=['GET']) @ac_api_requires() def list_own_reviews(): - reviews = cases_filter_by_reviewer() + reviews = cases_filter_by_reviewer(iris_current_user) return response_api_success( data=CaseSchema( many=True, diff --git a/source/app/business/cases.py b/source/app/business/cases.py index c3de558f1..5ecb883f7 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -20,7 +20,6 @@ import traceback from app import db -from app.blueprints.iris_user import iris_current_user from app.logger import logger from app.util import add_obj_history_entry from app.models.models import ReviewStatusList @@ -81,12 +80,12 @@ def cases_filter(current_user, pagination_parameters, name, case_identifiers, cu is_open=is_open) -def cases_filter_by_user(show_all: bool): - return list_user_cases(iris_current_user.id, show_all) +def cases_filter_by_user(user, show_all: bool): + return list_user_cases(user.id, show_all) -def cases_filter_by_reviewer(): - return list_user_reviews(iris_current_user.id) +def cases_filter_by_reviewer(user): + return list_user_reviews(user.id) def cases_get_by_identifier(case_identifier) -> Cases: @@ -108,8 +107,8 @@ def cases_exists(identifier): return case_db_exists(identifier) -def cases_create(case: Cases, case_template_id) -> Cases: - case.owner_id = iris_current_user.id +def cases_create(user, case: Cases, case_template_id) -> Cases: + case.owner_id = user.id case.severity_id = 4 if case_template_id and len(case_template_id) > 0: From 573a84200d88452a7f3fa1d7d845c7a56d61eef6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:19:14 +0200 Subject: [PATCH 55/67] Removed unnecessary method parameter --- source/app/blueprints/rest/alerts_routes.py | 4 ++-- source/app/business/cases.py | 2 +- source/app/iris_engine/access_control/utils.py | 14 ++++++++------ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 87ee0e236..09a36bb9b 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -606,7 +606,7 @@ def alerts_escalate_route(alert_id) -> Response: if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(case.case_id, case.client_id) case = call_modules_hook('on_postload_case_create', data=case) @@ -884,7 +884,7 @@ def alerts_batch_escalate_route() -> Response: if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(case.case_id, case.client_id) case = call_modules_hook('on_postload_case_create', data=case) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 5ecb883f7..62f173147 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -130,7 +130,7 @@ def cases_create(user, case: Cases, case_template_id) -> Cases: logger.error(e.__str__()) raise BusinessProcessingError(f'Unexpected error when loading template {case_template_id} to new case.') - ac_set_new_case_access(None, case.case_id, case.client_id) + ac_set_new_case_access(case.case_id, case.client_id) # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument case = call_modules_hook('on_postload_case_create', case, None) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 7888534fd..d97f97e6e 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -347,14 +347,16 @@ def ac_add_user_effective_access_from_map(users_map, case_id): db.session.commit() -def ac_set_new_case_access(org_members, case_id, customer_id = None): +def ac_set_new_case_access(case_id, customer_id = None): """ Set a new case access """ + user = iris_current_user + users = ac_apply_autofollow_groups_access(case_id) - if iris_current_user.id in users: - del users[iris_current_user.id] + if user.id in users: + del users[user.id] users_full = User.query.with_entities(User.id).all() users_full_access = list(set([u.id for u in users_full]) - set(users.keys())) @@ -365,17 +367,17 @@ def ac_set_new_case_access(org_members, case_id, customer_id = None): # Add specific right for the user creating the case UserCaseAccess.query.filter( UserCaseAccess.case_id == case_id, - UserCaseAccess.user_id == iris_current_user.id + UserCaseAccess.user_id == user.id ).delete() db.session.commit() uca = UserCaseAccess() uca.case_id = case_id - uca.user_id = iris_current_user.id + uca.user_id = user.id uca.access_level = CaseAccessLevel.full_access.value db.session.add(uca) db.session.commit() - add_several_user_effective_access([iris_current_user.id], case_id, CaseAccessLevel.full_access.value) + add_several_user_effective_access([user.id], case_id, CaseAccessLevel.full_access.value) # Add customer permissions for all users belonging to the customer if customer_id: From 5411f32406b093974601ca0beacb3a186acd257a Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:19:55 +0200 Subject: [PATCH 56/67] Parameter need not be optional --- source/app/iris_engine/access_control/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index d97f97e6e..a5139aff1 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -347,7 +347,7 @@ def ac_add_user_effective_access_from_map(users_map, case_id): db.session.commit() -def ac_set_new_case_access(case_id, customer_id = None): +def ac_set_new_case_access(case_id, customer_id): """ Set a new case access """ From c1a2036b9c29683e35efac7904ae033cabc3c6a6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:50:33 +0200 Subject: [PATCH 57/67] Extracted method --- .../app/iris_engine/access_control/utils.py | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index a5139aff1..12334891d 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -364,18 +364,7 @@ def ac_set_new_case_access(case_id, customer_id): # Default users case access - Full access add_several_user_effective_access(users_full_access, case_id, CaseAccessLevel.deny_all.value) - # Add specific right for the user creating the case - UserCaseAccess.query.filter( - UserCaseAccess.case_id == case_id, - UserCaseAccess.user_id == user.id - ).delete() - db.session.commit() - uca = UserCaseAccess() - uca.case_id = case_id - uca.user_id = user.id - uca.access_level = CaseAccessLevel.full_access.value - db.session.add(uca) - db.session.commit() + set_user_case_access(user, case_id) add_several_user_effective_access([user.id], case_id, CaseAccessLevel.full_access.value) @@ -391,6 +380,22 @@ def ac_set_new_case_access(case_id, customer_id): ac_add_user_effective_access_from_map(users_map, case_id) +# TODO try to move down into app.datamgmt.manage.manage_users_db +def set_user_case_access(user, case_id): + # Add specific right for the user creating the case + UserCaseAccess.query.filter( + UserCaseAccess.case_id == case_id, + UserCaseAccess.user_id == user.id + ).delete() + db.session.commit() + uca = UserCaseAccess() + uca.case_id = case_id + uca.user_id = user.id + uca.access_level = CaseAccessLevel.full_access.value + db.session.add(uca) + db.session.commit() + + def ac_apply_autofollow_groups_access(case_id): """ Apply a direct effective user access to users within a group From 410cdace4b16296d48971562f6f4b014c71acbbf Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:52:08 +0200 Subject: [PATCH 58/67] Extracted method --- source/app/iris_engine/access_control/utils.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 12334891d..6dd645138 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -370,16 +370,22 @@ def ac_set_new_case_access(case_id, customer_id): # Add customer permissions for all users belonging to the customer if customer_id: - users_client = UserClient.query.filter( - UserClient.client_id == customer_id - ).with_entities( - UserClient.user_id, - UserClient.access_level - ).all() + users_client = get_user_access_levels_by_customer(customer_id) users_map = { u.user_id: u.access_level for u in users_client } ac_add_user_effective_access_from_map(users_map, case_id) +# TODO move down into app.datamgmt.manage.manage_access_control_db +def get_user_access_levels_by_customer(customer_id): + users_client = UserClient.query.filter( + UserClient.client_id == customer_id + ).with_entities( + UserClient.user_id, + UserClient.access_level + ).all() + return users_client + + # TODO try to move down into app.datamgmt.manage.manage_users_db def set_user_case_access(user, case_id): # Add specific right for the user creating the case From f009aba44234b489cbc163386f76bd84897a2d70 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:53:44 +0200 Subject: [PATCH 59/67] Removed one call to iris_current_user from iris_engine --- source/app/blueprints/rest/alerts_routes.py | 4 ++-- source/app/business/cases.py | 2 +- source/app/iris_engine/access_control/utils.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/source/app/blueprints/rest/alerts_routes.py b/source/app/blueprints/rest/alerts_routes.py index 09a36bb9b..4f73a7538 100644 --- a/source/app/blueprints/rest/alerts_routes.py +++ b/source/app/blueprints/rest/alerts_routes.py @@ -606,7 +606,7 @@ def alerts_escalate_route(alert_id) -> Response: if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(case.case_id, case.client_id) + ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) case = call_modules_hook('on_postload_case_create', data=case) @@ -884,7 +884,7 @@ def alerts_batch_escalate_route() -> Response: if not case: return response_error('Failed to create case from alert') - ac_set_new_case_access(case.case_id, case.client_id) + ac_set_new_case_access(iris_current_user, case.case_id, case.client_id) case = call_modules_hook('on_postload_case_create', data=case) diff --git a/source/app/business/cases.py b/source/app/business/cases.py index 62f173147..38b2b5512 100644 --- a/source/app/business/cases.py +++ b/source/app/business/cases.py @@ -130,7 +130,7 @@ def cases_create(user, case: Cases, case_template_id) -> Cases: logger.error(e.__str__()) raise BusinessProcessingError(f'Unexpected error when loading template {case_template_id} to new case.') - ac_set_new_case_access(case.case_id, case.client_id) + ac_set_new_case_access(user, case.case_id, case.client_id) # TODO remove caseid doesn't seems to be useful for call_modules_hook => remove argument case = call_modules_hook('on_postload_case_create', case, None) diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index 6dd645138..cc8ef8c54 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -347,13 +347,11 @@ def ac_add_user_effective_access_from_map(users_map, case_id): db.session.commit() -def ac_set_new_case_access(case_id, customer_id): +def ac_set_new_case_access(user, case_id, customer_id): """ Set a new case access """ - user = iris_current_user - users = ac_apply_autofollow_groups_access(case_id) if user.id in users: del users[user.id] From e1e3691a82ea69a44683f7a0a0bd1fc7f14d2a8c Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:58:14 +0200 Subject: [PATCH 60/67] Moved code down into models --- source/app/datamgmt/manage/manage_groups_db.py | 4 ++-- source/app/datamgmt/manage/manage_users_db.py | 3 +-- source/app/iris_engine/access_control/utils.py | 11 ----------- source/app/models/authorization.py | 11 +++++++++++ 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/source/app/datamgmt/manage/manage_groups_db.py b/source/app/datamgmt/manage/manage_groups_db.py index 84139a3b8..e4013a9c8 100644 --- a/source/app/datamgmt/manage/manage_groups_db.py +++ b/source/app/datamgmt/manage/manage_groups_db.py @@ -20,12 +20,12 @@ from app.blueprints.iris_user import iris_current_user from app.datamgmt.case.case_db import get_case from app.datamgmt.manage.manage_cases_db import list_cases_id -from app.iris_engine.access_control.utils import ac_access_level_mask_from_val_list, ac_ldp_group_removal +from app.iris_engine.access_control.utils import ac_ldp_group_removal from app.iris_engine.access_control.utils import ac_access_level_to_list from app.iris_engine.access_control.utils import ac_auto_update_user_effective_access from app.iris_engine.access_control.utils import ac_permission_to_list from app.models.cases import Cases -from app.models.authorization import Group +from app.models.authorization import Group, ac_access_level_mask_from_val_list from app.models.authorization import GroupCaseAccess from app.models.authorization import User from app.models.authorization import UserGroup diff --git a/source/app/datamgmt/manage/manage_users_db.py b/source/app/datamgmt/manage/manage_users_db.py index 7a536dcf6..e266a17ee 100644 --- a/source/app/datamgmt/manage/manage_users_db.py +++ b/source/app/datamgmt/manage/manage_users_db.py @@ -29,7 +29,6 @@ from app import db from app.datamgmt.case.case_db import get_case from app.datamgmt.conversions import convert_sort_direction -from app.iris_engine.access_control.utils import ac_access_level_mask_from_val_list from app.iris_engine.access_control.utils import ac_ldp_group_removal from app.iris_engine.access_control.utils import ac_access_level_to_list from app.iris_engine.access_control.utils import ac_auto_update_user_effective_access @@ -38,7 +37,7 @@ from app.models.cases import Cases from app.models.models import Client from app.models.models import UserActivity -from app.models.authorization import CaseAccessLevel +from app.models.authorization import CaseAccessLevel, ac_access_level_mask_from_val_list from app.models.authorization import UserClient from app.models.authorization import Group from app.models.authorization import Organisation diff --git a/source/app/iris_engine/access_control/utils.py b/source/app/iris_engine/access_control/utils.py index cc8ef8c54..6907b0e18 100644 --- a/source/app/iris_engine/access_control/utils.py +++ b/source/app/iris_engine/access_control/utils.py @@ -837,17 +837,6 @@ def ac_access_level_to_list(access_level): return access_levels -def ac_access_level_mask_from_val_list(access_levels): - """ - Return an access level mask from a list of access levels - """ - am = 0 - for acc in access_levels: - am |= int(acc) - - return am - - def ac_user_has_permission(user, permission): """ Return True if user has permission diff --git a/source/app/models/authorization.py b/source/app/models/authorization.py index 444cc1a27..51a831e1f 100644 --- a/source/app/models/authorization.py +++ b/source/app/models/authorization.py @@ -257,3 +257,14 @@ def save(self): def ac_flag_match_mask(flag, mask): return (flag & mask) == mask + + +def ac_access_level_mask_from_val_list(access_levels) -> int: + """ + Return an access level mask from a list of access levels + """ + am = 0 + for acc in access_levels: + am |= int(acc) + + return am From 1754dbfc0b0a480e1975196edf9a785a62924729 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:19:18 +0200 Subject: [PATCH 61/67] Moved test .env file at the top level as .env.tests.model --- tests/data/basic.env => .env.tests.model | 0 .github/workflows/ci.yml | 16 +++++----------- tests/README.md | 23 ++++++++++++++++++++++- 3 files changed, 27 insertions(+), 12 deletions(-) rename tests/data/basic.env => .env.tests.model (100%) diff --git a/tests/data/basic.env b/.env.tests.model similarity index 100% rename from tests/data/basic.env rename to .env.tests.model diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index acc75a319..ea55d631d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -131,10 +131,8 @@ jobs: uses: actions/checkout@v4 - name: Start development server run: | - # Even though, we use --env-file option when running docker compose, this is still necessary, because the compose has a env_file attribute :( - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env - docker compose --file docker-compose.dev.yml --env-file tests/data/basic.env up --detach --wait + cp .env.tests.model .env + docker compose --file docker-compose.dev.yml up --detach --wait - name: Generate GraphQL documentation run: | npx spectaql@^3.0.2 source/spectaql/config.yml @@ -170,9 +168,7 @@ jobs: uses: actions/checkout@v4 - name: Start development server run: | - # Even though, we use --env-file option when running docker compose, this is still necessary, because the compose has a env_file attribute :( - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env + cp .env.tests.model .env docker compose --file docker-compose.dev.yml up --detach --wait - name: Inspect development server start failure if: ${{ failure() || cancelled() }} @@ -226,8 +222,7 @@ jobs: - name: Check out iris uses: actions/checkout@v4 - name: Set up .env file - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env? - run: cp tests/data/basic.env .env + run: cp .env.tests.model .env - name: Run tests working-directory: tests_database_migration run: | @@ -277,8 +272,7 @@ jobs: run: npx playwright install chromium firefox - name: Start development server run: | - # TODO should move basic.env file, which is in directory tests, up. It's used in several places. Maybe, rename it into dev.env - cp tests/data/basic.env .env + cp .env.tests.model .env docker compose --file docker-compose.dev.yml up --detach --wait - name: Run end to end tests working-directory: e2e diff --git a/tests/README.md b/tests/README.md index 6f398cbac..b32de4125 100644 --- a/tests/README.md +++ b/tests/README.md @@ -13,6 +13,12 @@ First activate the virtual environment: source ./venv/bin/activate ``` +Then start the development configuration of DFIR-IRIS server: +``` +cp ../.env.tests.model ../.env +docker compose --file ../docker-compose.dev.yml up --detach --wait +``` + Then run: ``` python -m unittest --verbose @@ -20,5 +26,20 @@ python -m unittest --verbose To execute only one test, suffix with the fully qualified test name. Example: ``` -python -m unittest tests_rest.TestsRest.test_create_asset_should_not_fail +python -m unittest tests_rest_assets.TestsRestAssets.test_create_asset_should_return_201 +``` + +Tip: this is a way to spped up the develop/run test loop. To restart only the `app` docker, do: +``` +docker compose stop app && docker compose --file ../docker-compose.dev.yml start app +``` + +Finally, stop the development server: +``` +docker compose down +``` + +Tip: if you want to clear database data: +``` +docker volume rm iris-web_db_data ``` From 7ceee5942b222158f36bea3b40fc85eed2565875 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:09:42 +0200 Subject: [PATCH 62/67] Renamed method to follow conventions --- source/app/blueprints/rest/v2/manage_routes/groups.py | 4 ++-- source/app/blueprints/rest/v2/manage_routes/users.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/groups.py b/source/app/blueprints/rest/v2/manage_routes/groups.py index ba6ba727d..12df18c42 100644 --- a/source/app/blueprints/rest/v2/manage_routes/groups.py +++ b/source/app/blueprints/rest/v2/manage_routes/groups.py @@ -54,7 +54,7 @@ def create(self): except ValidationError as e: return response_api_error('Data error', data=e.messages) - def get(self, identifier): + def read(self, identifier): try: group = groups_get(identifier) result = self._schema.dump(group) @@ -99,7 +99,7 @@ def create_groups_blueprint(): create_group = wrap_with_permission_checks(groups.create, Permissions.server_administrator) blueprint.add_url_rule('', view_func=create_group, methods=['POST']) - get_group = wrap_with_permission_checks(groups.get, Permissions.server_administrator) + get_group = wrap_with_permission_checks(groups.read, Permissions.server_administrator) blueprint.add_url_rule('/', view_func=get_group, methods=['GET']) update_group = wrap_with_permission_checks(groups.update, Permissions.server_administrator) diff --git a/source/app/blueprints/rest/v2/manage_routes/users.py b/source/app/blueprints/rest/v2/manage_routes/users.py index e2d7c4d9e..ac267997a 100644 --- a/source/app/blueprints/rest/v2/manage_routes/users.py +++ b/source/app/blueprints/rest/v2/manage_routes/users.py @@ -54,7 +54,6 @@ def create(self): return response_api_error('Data error', data=e.messages) def read(self, identifier): - try: user = users_get(identifier) result = self._schema.dump(user) From e73af4dc5ee32c1969fe20240ec0987fc6bcf56d Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:24:57 +0200 Subject: [PATCH 63/67] GET /api/v2/customers/{identifier} should return 404 when it doesn't exist --- .../app/blueprints/rest/v2/manage_routes/customers.py | 11 ++++++++--- tests/tests_rest_customers.py | 6 ++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 264f940c5..08764a5b8 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -23,9 +23,11 @@ from app.blueprints.rest.endpoints import response_api_created from app.blueprints.rest.endpoints import response_api_error from app.blueprints.rest.endpoints import response_api_success +from app.blueprints.rest.endpoints import response_api_not_found from app.blueprints.access_controls import ac_api_requires from app.models.authorization import Permissions from app.schema.marshables import CustomerSchema +from app.business.errors import ObjectNotFoundError from app.business.customers import customers_create_with_user from app.business.customers import customers_get from app.blueprints.iris_user import iris_current_user @@ -47,9 +49,12 @@ def create(self): return response_api_error('Data error', data=e.messages) def read(self, identifier): - customer = customers_get(identifier) - result = self._schema.dump(customer) - return response_api_success(result) + try: + customer = customers_get(identifier) + result = self._schema.dump(customer) + return response_api_success(result) + except ObjectNotFoundError: + return response_api_not_found() customers_blueprint = Blueprint('customers_rest_v2', __name__, url_prefix='/customers') diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 423bf4bbd..6a4391ef1 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -21,6 +21,8 @@ from iris import IRIS_PERMISSION_CUSTOMERS_WRITE from iris import ADMINISTRATOR_USER_IDENTIFIER +_IDENTIFIER_FOR_NONEXISTENT_OBJECT = 123456789 + class TestsRestCustomers(TestCase): @@ -79,3 +81,7 @@ def test_get_customer_should_return_200(self): identifier = response['customer_id'] response = self._subject.get(f'/api/v2/manage/customers/{identifier}') self.assertEqual(200, response.status_code) + + def test_get_customer_should_return_404_when_customer_does_not_exist(self): + response = self._subject.get(f'/api/v2/manage/customers/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') + self.assertEqual(404, response.status_code) From 30f49e2936e70ea6a9cd399124565e7555036da8 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 08:50:58 +0200 Subject: [PATCH 64/67] Renamed test --- tests/tests_rest_comments.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_rest_comments.py b/tests/tests_rest_comments.py index dd9cbf4cf..484ebf939 100644 --- a/tests/tests_rest_comments.py +++ b/tests/tests_rest_comments.py @@ -537,7 +537,7 @@ def test_get_iocs_comment_should_return_200(self): response = self._subject.get(f'/api/v2/iocs/{object_identifier}/comments/{identifier}', {}) self.assertEqual(200, response.status_code) - def test_get_notes_comment_should_return_200(self): + def test_get_notes_comments_should_return_200_when_there_is_a_comment(self): case_identifier = self._subject.create_dummy_case() response = self._subject.create(f'/api/v2/cases/{case_identifier}/notes-directories', {'name': 'directory_name'}).json() From 31e84954a63b955964857389a4633d2c61c15577 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:44:59 +0200 Subject: [PATCH 65/67] Deprecate GET /manage/customers/{client_id} --- source/app/blueprints/rest/manage/manage_customers_routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/source/app/blueprints/rest/manage/manage_customers_routes.py b/source/app/blueprints/rest/manage/manage_customers_routes.py index efe0bdfa1..817cdf86e 100644 --- a/source/app/blueprints/rest/manage/manage_customers_routes.py +++ b/source/app/blueprints/rest/manage/manage_customers_routes.py @@ -62,6 +62,7 @@ def list_customers(): @manage_customers_rest_blueprint.route('/manage/customers/', methods=['GET']) +@endpoint_deprecated('GET', '/api/v2/manage/customers/{identifier}') @ac_api_requires(Permissions.customers_read) @ac_api_requires_client_access() def view_customer(client_id): From f3163c8ce9cdc9d6663463e5fd2ce797cbe25117 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 10:49:20 +0200 Subject: [PATCH 66/67] GET /api/v2/manage/customers/{identifier} returns 403 when user has no customers_read permission --- source/app/blueprints/rest/v2/manage_routes/customers.py | 2 +- tests/tests_rest_customers.py | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/source/app/blueprints/rest/v2/manage_routes/customers.py b/source/app/blueprints/rest/v2/manage_routes/customers.py index 08764a5b8..57c42582b 100644 --- a/source/app/blueprints/rest/v2/manage_routes/customers.py +++ b/source/app/blueprints/rest/v2/manage_routes/customers.py @@ -69,6 +69,6 @@ def create_customer(): @customers_blueprint.get('/') -@ac_api_requires() +@ac_api_requires(Permissions.customers_read) def get_event(identifier): return customers.read(identifier) diff --git a/tests/tests_rest_customers.py b/tests/tests_rest_customers.py index 6a4391ef1..1615fa36f 100644 --- a/tests/tests_rest_customers.py +++ b/tests/tests_rest_customers.py @@ -85,3 +85,12 @@ def test_get_customer_should_return_200(self): def test_get_customer_should_return_404_when_customer_does_not_exist(self): response = self._subject.get(f'/api/v2/manage/customers/{_IDENTIFIER_FOR_NONEXISTENT_OBJECT}') self.assertEqual(404, response.status_code) + + def test_get_customer_should_return_405_when_user_has_no_permission_to_read_customers(self): + body = {'customer_name': 'customer'} + response = self._subject.create('/api/v2/manage/customers', body).json() + identifier = response['customer_id'] + + user = self._subject.create_dummy_user() + response = user.get(f'/api/v2/manage/customers/{identifier}') + self.assertEqual(403, response.status_code) From df5de768d8a164ff75b7f7c1a47bf90660bbd5e6 Mon Sep 17 00:00:00 2001 From: c8y3 <25362953+c8y3@users.noreply.github.com> Date: Fri, 24 Oct 2025 11:03:41 +0200 Subject: [PATCH 67/67] Removed methods which seem to be dead --- source/app/schema/marshables.py | 34 --------------------------------- 1 file changed, 34 deletions(-) diff --git a/source/app/schema/marshables.py b/source/app/schema/marshables.py index e74f41af2..a45ab7777 100644 --- a/source/app/schema/marshables.py +++ b/source/app/schema/marshables.py @@ -2186,40 +2186,6 @@ class Meta: unknown = EXCLUDE -def validate_ioc_type(type_id: int) -> None: - """Validates the IOC type ID. - - This function validates the IOC type ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - type_id: The IOC type ID to validate. - - Raises: - ValidationError: If the IOC type ID is invalid. - - """ - if not IocType.query.get(type_id): - raise ValidationError("Invalid ioc_type ID") - - -def validate_ioc_tlp(tlp_id: int) -> None: - """Validates the IOC TLP ID. - - This function validates the IOC TLP ID by checking if it exists in the database. - If the ID is invalid, it raises a validation error. - - Args: - tlp_id: The IOC TLP ID to validate. - - Raises: - ValidationError: If the IOC TLP ID is invalid. - - """ - if not Tlp.query.get(tlp_id): - raise ValidationError("Invalid ioc_tlp ID") - - def validate_asset_type(asset_id: int) -> None: """Validates the asset type ID.